berkbirkan's picture
Deploy dynamic traditional and agentic RAG demo
8ef9ed7 verified
Raw
History Blame Contribute Delete
3.36 kB
"""Paragraph-aware dynamic chunking with bounded overlap."""
from __future__ import annotations
import hashlib
import re
from collections.abc import Callable
from dynamic_rag.models import Chunk, Document
class MixedChunker:
def __init__(self, token_count: Callable[[str], int], *, target_tokens: int = 384, overlap_tokens: int = 48):
if not 0 <= overlap_tokens < target_tokens:
raise ValueError("overlap_tokens must satisfy 0 <= overlap < target")
self.token_count = token_count
self.target_tokens = target_tokens
self.overlap_tokens = overlap_tokens
def _units(self, text: str) -> list[str]:
paragraphs = [p.strip() for p in re.split(r"\n\s*\n", text) if p.strip()]
units = []
for paragraph in paragraphs:
if self.token_count(paragraph) <= self.target_tokens:
units.append(paragraph)
else:
units.extend(s.strip() for s in re.split(r"(?<=[.!?])\s+", paragraph) if s.strip())
return units
def split(self, documents: list[Document]) -> list[Chunk]:
output = []
for document in documents:
parent_id = hashlib.sha256(f"{document.source}\0{document.text}".encode()).hexdigest()[:20]
current: list[str] = []
for unit in self._units(document.text):
candidate = "\n\n".join(current + [unit])
if current and self.token_count(candidate) > self.target_tokens:
self._append(output, document, parent_id, current)
overlap = []
for prior in reversed(current):
if self.token_count("\n\n".join([prior] + overlap)) > self.overlap_tokens:
break
overlap.insert(0, prior)
current = overlap
if current and self.token_count("\n\n".join(current + [unit])) > self.target_tokens:
current = []
if self.token_count(unit) > self.target_tokens:
words = unit.split()
while words:
take = []
while words and self.token_count(" ".join(take + [words[0]])) <= self.target_tokens:
take.append(words.pop(0))
if not take:
take.append(words.pop(0))
if take:
if current:
self._append(output, document, parent_id, current)
current = []
self._append(output, document, parent_id, [" ".join(take)])
continue
current.append(unit)
if current:
self._append(output, document, parent_id, current)
return output
def _append(self, output: list[Chunk], document: Document, parent_id: str, units: list[str]) -> None:
text = "\n\n".join(units).strip()
if not text:
return
index = sum(chunk.parent_id == parent_id for chunk in output)
chunk_id = hashlib.sha256(f"{parent_id}\0{index}\0{text}".encode()).hexdigest()[:24]
output.append(Chunk(chunk_id, parent_id, text, document.source, document.title, index, document.metadata))