Spaces:
Sleeping
Sleeping
| """ | |
| Text Splitting Module | |
| Splits documents into chunks for embedding and retrieval | |
| """ | |
| from pathlib import Path | |
| from typing import List, Dict | |
| import logging | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| from config import ( | |
| PROCESSED_DOCS_DIR, | |
| TEXT_SPLITTER_CONFIG, | |
| ) | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class DocumentChunker: | |
| """ | |
| Handles document chunking with configurable parameters | |
| """ | |
| def __init__( | |
| self, | |
| chunk_size: int = TEXT_SPLITTER_CONFIG["chunk_size"], | |
| chunk_overlap: int = TEXT_SPLITTER_CONFIG["chunk_overlap"], | |
| separators: List[str] = TEXT_SPLITTER_CONFIG["separators"], | |
| keep_separator: bool = TEXT_SPLITTER_CONFIG["keep_separator"], | |
| ): | |
| """ | |
| Initialize the document chunker | |
| Args: | |
| chunk_size: Maximum size of each chunk in characters | |
| chunk_overlap: Number of characters to overlap between chunks | |
| separators: List of separator strings to split on | |
| keep_separator: Whether to keep the separator in the chunks | |
| """ | |
| self.splitter = RecursiveCharacterTextSplitter( | |
| chunk_size=chunk_size, | |
| chunk_overlap=chunk_overlap, | |
| separators=separators, | |
| keep_separator=keep_separator, | |
| ) | |
| logger.info( | |
| f"Initialized text splitter: chunk_size={chunk_size}, " | |
| f"chunk_overlap={chunk_overlap}" | |
| ) | |
| def split_text(self, text: str) -> List[str]: | |
| """ | |
| Split text into chunks | |
| Args: | |
| text: Input text to split | |
| Returns: | |
| List of text chunks | |
| """ | |
| try: | |
| chunks = self.splitter.split_text(text) | |
| logger.debug(f"Split text into {len(chunks)} chunks") | |
| return chunks | |
| except Exception as e: | |
| logger.error(f"Error splitting text: {e}") | |
| raise | |
| def split_document(self, file_path: Path) -> List[Dict[str, str]]: | |
| """ | |
| Split a markdown document into chunks with metadata | |
| Args: | |
| file_path: Path to the markdown file | |
| Returns: | |
| List of dictionaries containing chunk text and metadata | |
| """ | |
| try: | |
| # Read the file | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| text = f.read() | |
| # Split into chunks | |
| chunks = self.split_text(text) | |
| # Add metadata to each chunk | |
| chunk_data = [] | |
| for idx, chunk in enumerate(chunks): | |
| chunk_data.append({ | |
| "text": chunk, | |
| "source": file_path.name, | |
| "chunk_id": idx, | |
| "total_chunks": len(chunks), | |
| }) | |
| logger.info(f"Split {file_path.name} into {len(chunks)} chunks") | |
| return chunk_data | |
| except Exception as e: | |
| logger.error(f"Error splitting document {file_path}: {e}") | |
| raise | |
| def process_all_documents() -> List[Dict[str, str]]: | |
| """ | |
| Process all markdown documents in the processed_docs directory | |
| Returns: | |
| List of all chunks with metadata from all documents | |
| """ | |
| chunker = DocumentChunker() | |
| all_chunks = [] | |
| if not PROCESSED_DOCS_DIR.exists(): | |
| logger.error(f"Processed documents directory not found: {PROCESSED_DOCS_DIR}") | |
| return all_chunks | |
| # Process all markdown files | |
| markdown_files = list(PROCESSED_DOCS_DIR.glob("*.md")) | |
| if not markdown_files: | |
| logger.warning("No markdown files found in processed_docs directory") | |
| return all_chunks | |
| logger.info(f"Processing {len(markdown_files)} documents...") | |
| for file_path in markdown_files: | |
| try: | |
| chunks = chunker.split_document(file_path) | |
| all_chunks.extend(chunks) | |
| logger.info(f"Added {len(chunks)} chunks from {file_path.name}") | |
| except Exception as e: | |
| logger.error(f"Failed to process {file_path.name}: {e}") | |
| continue | |
| logger.info(f"Total chunks processed: {len(all_chunks)}") | |
| return all_chunks | |
| def get_chunk_statistics(chunks: List[Dict[str, str]]) -> Dict: | |
| """ | |
| Calculate statistics about the chunks | |
| Args: | |
| chunks: List of chunk dictionaries | |
| Returns: | |
| Dictionary with statistics | |
| """ | |
| if not chunks: | |
| return { | |
| "total_chunks": 0, | |
| "total_characters": 0, | |
| "avg_chunk_size": 0, | |
| "min_chunk_size": 0, | |
| "max_chunk_size": 0, | |
| "sources": [], | |
| } | |
| chunk_sizes = [len(chunk["text"]) for chunk in chunks] | |
| sources = list(set(chunk["source"] for chunk in chunks)) | |
| return { | |
| "total_chunks": len(chunks), | |
| "total_characters": sum(chunk_sizes), | |
| "avg_chunk_size": sum(chunk_sizes) / len(chunk_sizes), | |
| "min_chunk_size": min(chunk_sizes), | |
| "max_chunk_size": max(chunk_sizes), | |
| "sources": sources, | |
| "num_sources": len(sources), | |
| } | |
| if __name__ == "__main__": | |
| # Test the text splitting | |
| logger.info("Testing text splitting...") | |
| chunks = process_all_documents() | |
| if chunks: | |
| stats = get_chunk_statistics(chunks) | |
| logger.info(f"Chunk statistics: {stats}") | |
| # Display first chunk as example | |
| if chunks: | |
| logger.info("\nExample chunk:") | |
| logger.info(f"Source: {chunks[0]['source']}") | |
| logger.info(f"Chunk ID: {chunks[0]['chunk_id']}") | |
| logger.info(f"Text preview: {chunks[0]['text'][:200]}...") | |
| else: | |
| logger.warning("No chunks created. Make sure documents are converted first.") | |