Download _backend/utils/document_management.py from IOE-IFAD/test: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/spaces/IOE-IFAD/test/resolve/main/_backend/utils/document_management.py
- Command line
-
hf download hf://spaces/IOE-IFAD/test/_backend/utils/document_management.py
-
curl -L -o document_management.py https://huggingface.co/spaces/IOE-IFAD/test/resolve/main/_backend/utils/document_management.py
10.5 kB
| import pandas as pd | |
| import os | |
| from utils.text_splitter import srt_chunker as chun | |
| from utils.vector_db import VectorDB | |
| import fitz | |
| from datetime import datetime | |
| import re | |
| def load_document(file_path, raw_text=False): | |
| file_extension = os.path.splitext(file_path)[1].lower() | |
| if file_extension == '.pdf': | |
| return load_pdf(file_path) | |
| elif file_extension == '.csv': | |
| return load_csv(file_path) | |
| elif file_extension == '.srt': | |
| with open(file_path, 'r', encoding='utf-8') as file: | |
| content = file.read() | |
| paragraphs = content.split('\n\n') | |
| documents = [{"text": paragraph, "paragraph_number": index + 1} | |
| for index, paragraph in enumerate(paragraphs) if paragraph.strip()] | |
| return documents | |
| elif file_extension == '.md': | |
| if raw_text: | |
| with open(file_path, 'r', encoding='utf-8') as file: | |
| content = file.read() | |
| return content | |
| else: | |
| with open(file_path, 'r', encoding='utf-8') as file: | |
| content = file.read() | |
| paragraphs = content.split('\n\n') | |
| documents = [{"text": paragraph, "paragraph_number": index + 1} | |
| for index, paragraph in enumerate(paragraphs) if paragraph.strip()] | |
| return documents | |
| else: | |
| raise ValueError(f"Unsupported file type: {file_extension}") | |
| def load_pdf(file_path): | |
| """ | |
| Load a PDF file and extract its text content into paragraphs. | |
| Args: | |
| file_path (str): The path to the PDF file. | |
| Returns: | |
| list[dict]: A list of dictionaries, each containing a paragraph of text and its paragraph number. | |
| """ | |
| paragraphs = [] | |
| pdf_document = fitz.open(file_path) | |
| for page in pdf_document: | |
| output = page.get_text("blocks") | |
| previous_block_id = None | |
| for block in output: | |
| if block[6] == 0: # Check if the block is a text block | |
| cleaned_output = block[4].strip().replace("\n", " ") | |
| if cleaned_output and len(cleaned_output) >= 5: | |
| if previous_block_id != block[5]: | |
| paragraphs.append(cleaned_output) | |
| else: | |
| paragraphs[-1] += " " + cleaned_output | |
| previous_block_id = block[5] | |
| # Adding the paragraph number to each paragraph | |
| documents = [{"text": paragraph, "paragraph_number": index + 1} | |
| for index, paragraph in enumerate(paragraphs)] | |
| return documents | |
| def load_csv(file_path): | |
| try: | |
| df = pd.read_csv(file_path) | |
| documents = [] | |
| if "Paragraph text" in df.columns: | |
| df = df.rename(columns={"Paragraph text": "text"}) | |
| df["page_number"] = df.index + 1 | |
| documents = df.to_dict(orient="records") | |
| return documents | |
| else: | |
| for index, row in df.iterrows(): | |
| content = row.to_json() | |
| documents.append({"text": content, "page_number": (index+1)}) | |
| return documents | |
| except Exception as e: | |
| print(e) | |
| return [] | |
| def upload_document_to_vectorstore(chunks: list[dict], vector_db: VectorDB): | |
| """ | |
| Uploads the chunks and their metadata to the vector database in batches. | |
| Args: | |
| chunks (list[dict]): A list of dictionaries, each containing a chunk of the document. | |
| vector_db (VectorDB): An instance of the VectorDB class to interact with the vector database. | |
| Returns: | |
| list: A list of points created in the vector store. | |
| """ | |
| batch_size = 2000 | |
| qdrant_batch_size = 500 | |
| print(f"Uploading {len(chunks)} chunks to the vector store.") | |
| embeddings = [] | |
| for batch_start in range(0, len(chunks), batch_size): | |
| batch_chunks = chunks[batch_start: batch_start + batch_size] | |
| batch_chunks = [chunk["text"] for chunk in batch_chunks] | |
| print(f"Encoding batch of {len(batch_chunks)} chunks.") | |
| batch_embeddings = vector_db.batch_encode_text(batch_chunks) | |
| embeddings.extend(batch_embeddings) | |
| points = vector_db.create_points(embeddings, chunks) | |
| print(f"Uploading {len(points)} points to the vector store.") | |
| for batch_start in range(0, len(points), qdrant_batch_size): | |
| batch_points = points[batch_start: batch_start + qdrant_batch_size] | |
| vector_db.insert_data(batch_points) | |
| print("Upload complete.") | |
| return points | |
| def document_chunker(document: list[dict], chunking_function) -> list[dict]: | |
| """ | |
| Chunk a document into smaller pieces using a provided chunking function. | |
| Args: | |
| document (list[dict]): A list of dictionaries, each containing a 'text' key with the document content. | |
| chunking_function (callable): A function that takes a string and returns a list of chunks. | |
| Returns: | |
| list[dict]: A list of dictionaries, each containing a chunk of the document. | |
| """ | |
| chunks = [] | |
| for page in document: | |
| text = page.pop("text") | |
| text_chunks = chunking_function(text) | |
| for chunk in text_chunks: | |
| chunks.append({"text": chunk, **page}) | |
| return chunks | |
| def transcript_chunker(document: list[dict], filename) -> list[dict]: | |
| """ | |
| Chunk a transcript document into smaller pieces. | |
| Args: | |
| document (list[dict]): A list of dictionaries, each containing a 'text' key with the transcript content. | |
| Returns: | |
| list[dict]: A list of dictionaries, each containing a chunk of the transcript. | |
| """ | |
| chunks = [] | |
| def transcript_formatter( | |
| transcript): return f"{transcript['timestamp']} {transcript['speaker']}: {transcript['text']}" | |
| count = 0 | |
| for page in document: | |
| text = page.pop("text") | |
| transcript = chun(text) | |
| if transcript is None: | |
| if "File name" in text: | |
| continue | |
| if chunks: | |
| chunks[-1]["text"] += text | |
| continue | |
| # Add the filename to the transcript | |
| transcript["filename"] = filename | |
| # Adding the index of the transcript to the transcript | |
| transcript["index"] = count | |
| count += 1 | |
| # Add the current transcript to the chunks | |
| chunks.append(transcript) | |
| # Add the previous message to the current message | |
| if len(chunks) > 1: | |
| chunks[-1]["previous_message"] = transcript_formatter(chunks[-2]) | |
| # Add the next message to the previous message | |
| if len(chunks) > 1: | |
| chunks[-2]["next_message"] = transcript_formatter(chunks[-1]) | |
| return chunks | |
| def merge_chunks(chunks: list[dict]) -> list[dict]: | |
| """ | |
| Merge consecutive chunks that have the same speaker | |
| Args: | |
| chunks (list[dict]): A list of dictionaries, each containing a chunk of text and its metadata. | |
| Returns: | |
| list[dict]: A list of merged chunks. | |
| """ | |
| if not chunks: | |
| return [] | |
| merged_chunks = [] | |
| current_chunk = chunks[0] | |
| for chunk in chunks[1:]: | |
| if chunk["speaker"] == current_chunk["speaker"]: | |
| # Merge the text of the current chunk with the next one | |
| current_chunk["text"] += " " + chunk["text"] | |
| else: | |
| # Add the current chunk to the merged list and start a new one | |
| merged_chunks.append(current_chunk) | |
| current_chunk = chunk | |
| # Add the last chunk | |
| merged_chunks.append(current_chunk) | |
| return merged_chunks | |
| def generate_filename_from_details(minute_block_md: str) -> str: | |
| """ | |
| Generates a filename from a minute block by extracting details | |
| from a line like "| 24 March 2025, Guoqi Wu, AVP CSD | |". | |
| Format: YYYY-MM-DD_Name1Name2_RolePart1_RolePart2_CLE.md | |
| """ | |
| lines = minute_block_md.splitlines() | |
| details_str = None | |
| if len(lines) > 1: | |
| # The line containing details for the filename is typically the second line of the block | |
| # e.g., | 24 March 2025, Guoqi Wu, AVP CSD | | | |
| header_info_line = lines[1] | |
| # Extract content from between the first and second pipe characters | |
| line_parts = header_info_line.split('|') | |
| if len(line_parts) > 2 and line_parts[1].strip(): | |
| details_str = line_parts[1].strip() | |
| else: | |
| raise ValueError(f"Could not parse details for filename from line: '{header_info_line}' in minute block.") | |
| else: | |
| raise ValueError("Minute block has insufficient lines to extract details for filename.") | |
| if not details_str: | |
| raise ValueError("Failed to extract details string from minute block.") | |
| # Regex to separate date from the rest of the details | |
| match = re.match(r"(\d{1,2}\s+[A-Za-z]+\s+\d{4}),\s*(.+)", details_str) | |
| if not match: | |
| raise ValueError(f"Details string '{details_str}' does not match expected format 'Date, Details...'") | |
| date_part_str = match.group(1) | |
| name_and_role_parts_str = match.group(2) | |
| # Convert date to YYYY-MM-DD | |
| try: | |
| dt_object = datetime.strptime(date_part_str, "%d %B %Y") | |
| formatted_date = dt_object.strftime("%Y-%m-%d") | |
| except ValueError: | |
| try: | |
| dt_object = datetime.strptime(date_part_str, "%d %b %Y") | |
| formatted_date = dt_object.strftime("%Y-%m-%d") | |
| except ValueError as e: | |
| raise ValueError(f"Date string '{date_part_str}' could not be parsed: {e}") | |
| parts = [part.replace(" ", "") for part in name_and_role_parts_str.split(", ")] | |
| combined_name_role = "_".join(parts) | |
| combined_name_role_sanitized = re.sub(r'[^\w-]', '', combined_name_role.replace("(", "").replace(")", "")) | |
| return f"{formatted_date}_{combined_name_role_sanitized}_CLE.md", (formatted_date, combined_name_role_sanitized) | |
| def load_prompt(prompt_file_name:str, working_directory) -> str: | |
| """ | |
| Load a prompt from a file in the specified working directory's prompts folder. | |
| Args: | |
| prompt_file_name (str): The name of the prompt file to load. | |
| working_directory (str): The directory where the prompt file is located. | |
| Returns: | |
| str: The content of the prompt file. | |
| """ | |
| prompt_file_path = os.path.join(working_directory, "prompts", prompt_file_name+".txt") | |
| if not os.path.exists(prompt_file_path): | |
| raise FileNotFoundError(f"Prompt file '{prompt_file_name}' at path '{prompt_file_path}' does not exist.") | |
| with open(prompt_file_path, 'r', encoding='utf-8') as file: | |
| return file.read() | |