rag-python-rag / text_splitter.py
viktor-hirenko
Initial commit: RAG system with local LLM
40e5eae
Raw
History Blame Contribute Delete
5.93 kB
"""
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.")