Spaces:
Sleeping
Sleeping
| """ | |
| Text Chunking Module | |
| Splits documents into optimal chunks for embedding and retrieval | |
| """ | |
| from typing import List | |
| from langchain.text_splitter import ( | |
| RecursiveCharacterTextSplitter, | |
| TokenTextSplitter, | |
| MarkdownTextSplitter | |
| ) | |
| from langchain.schema import Document | |
| import logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class DocumentChunker: | |
| """Handles splitting documents into chunks for embedding""" | |
| def __init__( | |
| self, | |
| chunk_size: int = 1000, | |
| chunk_overlap: int = 200, | |
| separators: List[str] = None | |
| ): | |
| """ | |
| Initialize chunker with configurable parameters | |
| Args: | |
| chunk_size: Maximum size of each chunk in characters | |
| chunk_overlap: Overlap between consecutive chunks | |
| separators: Custom separators for splitting | |
| """ | |
| self.chunk_size = chunk_size | |
| self.chunk_overlap = chunk_overlap | |
| # Default separators optimized for technical content | |
| if separators is None: | |
| separators = [ | |
| "\n\n\n", # Multiple newlines (sections) | |
| "\n\n", # Paragraphs | |
| "\n", # Lines | |
| ". ", # Sentences | |
| ", ", # Clauses | |
| " ", # Words | |
| "" # Characters | |
| ] | |
| self.text_splitter = RecursiveCharacterTextSplitter( | |
| chunk_size=chunk_size, | |
| chunk_overlap=chunk_overlap, | |
| separators=separators, | |
| length_function=len | |
| ) | |
| logger.info(f"Chunker initialized: size={chunk_size}, overlap={chunk_overlap}") | |
| def chunk_documents(self, documents: List[Document]) -> List[Document]: | |
| """ | |
| Split documents into chunks | |
| Args: | |
| documents: List of Document objects to chunk | |
| Returns: | |
| List of chunked Document objects with preserved metadata | |
| """ | |
| try: | |
| chunked_docs = self.text_splitter.split_documents(documents) | |
| # Add chunk index to metadata | |
| for idx, doc in enumerate(chunked_docs): | |
| doc.metadata["chunk_id"] = idx | |
| doc.metadata["chunk_size"] = len(doc.page_content) | |
| logger.info( | |
| f"Split {len(documents)} documents into {len(chunked_docs)} chunks" | |
| ) | |
| return chunked_docs | |
| except Exception as e: | |
| logger.error(f"Error chunking documents: {str(e)}") | |
| raise | |
| def chunk_markdown(self, documents: List[Document]) -> List[Document]: | |
| """ | |
| Split markdown documents preserving structure | |
| Args: | |
| documents: List of markdown Document objects | |
| Returns: | |
| List of chunked Documents | |
| """ | |
| md_splitter = MarkdownTextSplitter( | |
| chunk_size=self.chunk_size, | |
| chunk_overlap=self.chunk_overlap | |
| ) | |
| try: | |
| chunked_docs = md_splitter.split_documents(documents) | |
| for idx, doc in enumerate(chunked_docs): | |
| doc.metadata["chunk_id"] = idx | |
| doc.metadata["chunk_size"] = len(doc.page_content) | |
| logger.info( | |
| f"Split {len(documents)} markdown documents into {len(chunked_docs)} chunks" | |
| ) | |
| return chunked_docs | |
| except Exception as e: | |
| logger.error(f"Error chunking markdown: {str(e)}") | |
| raise | |
| def chunk_by_tokens( | |
| self, | |
| documents: List[Document], | |
| model_name: str = "gpt-3.5-turbo" | |
| ) -> List[Document]: | |
| """ | |
| Split documents by token count (useful for LLM context limits) | |
| Args: | |
| documents: List of Document objects | |
| model_name: Model name for tokenization | |
| Returns: | |
| List of chunked Documents | |
| """ | |
| token_splitter = TokenTextSplitter( | |
| chunk_size=self.chunk_size, | |
| chunk_overlap=self.chunk_overlap, | |
| model_name=model_name | |
| ) | |
| try: | |
| chunked_docs = token_splitter.split_documents(documents) | |
| for idx, doc in enumerate(chunked_docs): | |
| doc.metadata["chunk_id"] = idx | |
| logger.info( | |
| f"Split {len(documents)} documents into {len(chunked_docs)} token-based chunks" | |
| ) | |
| return chunked_docs | |
| except Exception as e: | |
| logger.error(f"Error chunking by tokens: {str(e)}") | |
| raise | |
| def get_chunk_stats(self, chunks: List[Document]) -> dict: | |
| """ | |
| Get statistics about chunks | |
| Args: | |
| chunks: List of chunked documents | |
| Returns: | |
| Dictionary with chunk statistics | |
| """ | |
| if not chunks: | |
| return {"total_chunks": 0} | |
| chunk_sizes = [len(doc.page_content) for doc in chunks] | |
| stats = { | |
| "total_chunks": len(chunks), | |
| "avg_chunk_size": sum(chunk_sizes) / len(chunk_sizes), | |
| "min_chunk_size": min(chunk_sizes), | |
| "max_chunk_size": max(chunk_sizes), | |
| "total_characters": sum(chunk_sizes) | |
| } | |
| logger.info(f"Chunk statistics: {stats}") | |
| return stats | |
| # Example usage | |
| if __name__ == "__main__": | |
| from langchain.schema import Document | |
| # Example document | |
| sample_doc = Document( | |
| page_content="This is a long document. " * 100, | |
| metadata={"source": "example.txt"} | |
| ) | |
| chunker = DocumentChunker(chunk_size=500, chunk_overlap=50) | |
| chunks = chunker.chunk_documents([sample_doc]) | |
| print(f"Created {len(chunks)} chunks") | |
| print(chunker.get_chunk_stats(chunks)) | |