Document-RAG-GPT / rag /chunker.py
merchantkevin
Changed chunking strategy, citation strategy and README
6d24e8c
Raw
History Blame Contribute Delete
2.41 kB
"""Split text into chunks. Article-structured documents are split one chunk per
article; other documents fall back to paragraph-aware packing with overlap."""
import re
from typing import List
# Matches an article header at the start of a line, e.g. "Article 21." / "Article 51A".
ARTICLE_RE = re.compile(r"(?m)^[ \t]*Article\s+\d+[A-Z]?\b")
def chunk_text(text: str, chunk_size: int = 900, overlap: int = 150) -> List[str]:
text = text.replace("\r\n", "\n").replace("\r", "\n")
starts = [m.start() for m in ARTICLE_RE.finditer(text)]
if starts:
# Article-structured document: one chunk per article.
segments: List[str] = []
if starts[0] > 0: # leading material (preamble, titles)
lead = text[:starts[0]].strip()
if lead:
segments.append(lead)
bounds = starts + [len(text)]
for i in range(len(starts)):
seg = text[bounds[i]:bounds[i + 1]].strip()
if seg:
segments.append(seg)
chunks: List[str] = []
for seg in segments: # sub-split only oversized articles
if len(seg) <= chunk_size:
chunks.append(seg)
else:
chunks.extend(_pack_paragraphs(seg, chunk_size, overlap))
return chunks
# Generic fallback for arbitrary uploaded documents.
return _pack_paragraphs(text, chunk_size, overlap)
def _pack_paragraphs(text: str, chunk_size: int, overlap: int) -> List[str]:
paragraphs = [p.strip() for p in text.split("\n") if p.strip()]
blocks: List[str] = []
current = ""
for para in paragraphs:
if len(para) > chunk_size:
if current:
blocks.append(current)
current = ""
step = max(1, chunk_size - overlap)
for start in range(0, len(para), step):
blocks.append(para[start:start + chunk_size])
elif len(current) + len(para) + 1 <= chunk_size:
current = f"{current}\n{para}".strip()
else:
blocks.append(current)
current = para
if current:
blocks.append(current)
if overlap <= 0 or len(blocks) <= 1:
return blocks
out = [blocks[0]]
for i in range(1, len(blocks)):
tail = blocks[i - 1][-overlap:]
out.append(f"{tail}\n{blocks[i]}".strip())
return out