Coverage for src / lexigram / contracts / ai / document.py: 93%
56 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
1"""Document and Text Splitter contracts for Lexigram.
3Defines Document and text splitting classes analogous to LangChain's.
4"""
6from __future__ import annotations
8from dataclasses import dataclass, field
9from typing import Any
12@dataclass(frozen=True)
13class Document:
14 """A document (like LangChain's Document).
16 Attributes:
17 page_content: The text content.
18 metadata: Additional metadata.
19 """
21 page_content: str
22 metadata: dict[str, Any] = field(default_factory=dict)
25class TextSplitter:
26 """Base text splitter (like LangChain's TextSplitter).
28 Splits text into chunks.
29 """
31 def __init__(
32 self,
33 chunk_size: int = 1000,
34 chunk_overlap: int = 200,
35 ) -> None:
36 self.chunk_size = chunk_size
37 self.chunk_overlap = chunk_overlap
39 def split_text(self, text: str) -> list[str]:
40 """Split text into chunks.
42 Args:
43 text: Text to split.
45 Returns:
46 List of text chunks.
47 """
48 if not text:
49 return []
51 chunks = []
52 start = 0
53 while start < len(text):
54 end = start + self.chunk_size
55 chunk = text[start:end]
56 chunks.append(chunk)
57 start += self.chunk_size - self.chunk_overlap
59 return chunks
62class RecursiveCharacterTextSplitter(TextSplitter):
63 """Split by characters recursively (like LangChain's RecursiveCharacterTextSplitter)."""
65 def __init__(
66 self,
67 separators: list[str] | None = None,
68 chunk_size: int = 1000,
69 chunk_overlap: int = 200,
70 ) -> None:
71 super().__init__(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
72 self.separators = separators or ["\n\n", "\n", " ", ""]
74 def split_text(self, text: str) -> list[str]:
75 """Split text recursively by separators."""
76 if not text:
77 return []
79 final_chunks: list[str] = []
80 for separator in self.separators:
81 chunks = []
82 if separator == "":
83 chunks = list(text)
84 else:
85 parts = text.split(separator)
86 for part in parts:
87 if part:
88 chunks.append(part)
90 if len(chunks) > 1:
91 break
93 if not chunks:
94 return [text]
96 return self._merge_chunks(chunks)
98 def _merge_chunks(self, chunks: list[str]) -> list[str]:
99 """Merge chunks respecting size limits."""
100 merged = []
101 current = ""
103 for chunk in chunks:
104 if len(current) + len(chunk) <= self.chunk_size:
105 current += chunk
106 else:
107 if current:
108 merged.append(current)
109 current = chunk
111 if current:
112 merged.append(current)
114 return merged
117__all__ = [
118 "Document",
119 "RecursiveCharacterTextSplitter",
120 "TextSplitter",
121]