haiku.rag 0.10.2__py3-none-any.whl → 0.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- README.md +205 -0
- {haiku_rag-0.10.2.dist-info → haiku_rag-0.14.0.dist-info}/METADATA +100 -41
- haiku_rag-0.14.0.dist-info/RECORD +6 -0
- haiku/rag/__init__.py +0 -0
- haiku/rag/app.py +0 -437
- haiku/rag/chunker.py +0 -51
- haiku/rag/cli.py +0 -466
- haiku/rag/client.py +0 -605
- haiku/rag/config.py +0 -81
- haiku/rag/embeddings/__init__.py +0 -35
- haiku/rag/embeddings/base.py +0 -15
- haiku/rag/embeddings/ollama.py +0 -17
- haiku/rag/embeddings/openai.py +0 -16
- haiku/rag/embeddings/vllm.py +0 -19
- haiku/rag/embeddings/voyageai.py +0 -17
- haiku/rag/logging.py +0 -56
- haiku/rag/mcp.py +0 -156
- haiku/rag/migration.py +0 -316
- haiku/rag/monitor.py +0 -73
- haiku/rag/qa/__init__.py +0 -15
- haiku/rag/qa/agent.py +0 -91
- haiku/rag/qa/prompts.py +0 -60
- haiku/rag/reader.py +0 -115
- haiku/rag/reranking/__init__.py +0 -34
- haiku/rag/reranking/base.py +0 -13
- haiku/rag/reranking/cohere.py +0 -34
- haiku/rag/reranking/mxbai.py +0 -28
- haiku/rag/reranking/vllm.py +0 -44
- haiku/rag/research/__init__.py +0 -20
- haiku/rag/research/common.py +0 -53
- haiku/rag/research/dependencies.py +0 -47
- haiku/rag/research/graph.py +0 -29
- haiku/rag/research/models.py +0 -70
- haiku/rag/research/nodes/evaluate.py +0 -80
- haiku/rag/research/nodes/plan.py +0 -63
- haiku/rag/research/nodes/search.py +0 -93
- haiku/rag/research/nodes/synthesize.py +0 -51
- haiku/rag/research/prompts.py +0 -114
- haiku/rag/research/state.py +0 -25
- haiku/rag/store/__init__.py +0 -4
- haiku/rag/store/engine.py +0 -269
- haiku/rag/store/models/__init__.py +0 -4
- haiku/rag/store/models/chunk.py +0 -17
- haiku/rag/store/models/document.py +0 -17
- haiku/rag/store/repositories/__init__.py +0 -9
- haiku/rag/store/repositories/chunk.py +0 -424
- haiku/rag/store/repositories/document.py +0 -237
- haiku/rag/store/repositories/settings.py +0 -155
- haiku/rag/store/upgrades/__init__.py +0 -62
- haiku/rag/store/upgrades/v0_10_1.py +0 -64
- haiku/rag/store/upgrades/v0_9_3.py +0 -112
- haiku/rag/utils.py +0 -199
- haiku_rag-0.10.2.dist-info/RECORD +0 -54
- {haiku_rag-0.10.2.dist-info → haiku_rag-0.14.0.dist-info}/WHEEL +0 -0
- {haiku_rag-0.10.2.dist-info → haiku_rag-0.14.0.dist-info}/entry_points.txt +0 -0
- {haiku_rag-0.10.2.dist-info → haiku_rag-0.14.0.dist-info}/licenses/LICENSE +0 -0
haiku/rag/app.py
DELETED
|
@@ -1,437 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import json
|
|
3
|
-
from importlib.metadata import version as pkg_version
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from rich.console import Console
|
|
7
|
-
from rich.markdown import Markdown
|
|
8
|
-
from rich.progress import Progress
|
|
9
|
-
|
|
10
|
-
from haiku.rag.client import HaikuRAG
|
|
11
|
-
from haiku.rag.config import Config
|
|
12
|
-
from haiku.rag.mcp import create_mcp_server
|
|
13
|
-
from haiku.rag.monitor import FileWatcher
|
|
14
|
-
from haiku.rag.research.dependencies import ResearchContext
|
|
15
|
-
from haiku.rag.research.graph import (
|
|
16
|
-
PlanNode,
|
|
17
|
-
ResearchDeps,
|
|
18
|
-
ResearchState,
|
|
19
|
-
build_research_graph,
|
|
20
|
-
)
|
|
21
|
-
from haiku.rag.store.models.chunk import Chunk
|
|
22
|
-
from haiku.rag.store.models.document import Document
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class HaikuRAGApp:
|
|
26
|
-
def __init__(self, db_path: Path):
|
|
27
|
-
self.db_path = db_path
|
|
28
|
-
self.console = Console()
|
|
29
|
-
|
|
30
|
-
async def info(self):
|
|
31
|
-
"""Display read-only information about the database without modifying it."""
|
|
32
|
-
|
|
33
|
-
import lancedb
|
|
34
|
-
|
|
35
|
-
# Basic: show path
|
|
36
|
-
self.console.print("[bold]haiku.rag database info[/bold]")
|
|
37
|
-
self.console.print(
|
|
38
|
-
f" [repr.attrib_name]path[/repr.attrib_name]: {self.db_path}"
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
if not self.db_path.exists():
|
|
42
|
-
self.console.print("[red]Database path does not exist.[/red]")
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
# Connect without going through Store to avoid upgrades/validation writes
|
|
46
|
-
try:
|
|
47
|
-
db = lancedb.connect(self.db_path)
|
|
48
|
-
table_names = set(db.table_names())
|
|
49
|
-
except Exception as e:
|
|
50
|
-
self.console.print(f"[red]Failed to open database: {e}[/red]")
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
try:
|
|
54
|
-
ldb_version = pkg_version("lancedb")
|
|
55
|
-
except Exception:
|
|
56
|
-
ldb_version = "unknown"
|
|
57
|
-
try:
|
|
58
|
-
hr_version = pkg_version("haiku.rag")
|
|
59
|
-
except Exception:
|
|
60
|
-
hr_version = "unknown"
|
|
61
|
-
try:
|
|
62
|
-
docling_version = pkg_version("docling")
|
|
63
|
-
except Exception:
|
|
64
|
-
docling_version = "unknown"
|
|
65
|
-
|
|
66
|
-
# Read settings (if present) to find stored haiku.rag version and embedding config
|
|
67
|
-
stored_version = "unknown"
|
|
68
|
-
embed_provider: str | None = None
|
|
69
|
-
embed_model: str | None = None
|
|
70
|
-
vector_dim: int | None = None
|
|
71
|
-
|
|
72
|
-
if "settings" in table_names:
|
|
73
|
-
settings_tbl = db.open_table("settings")
|
|
74
|
-
arrow = settings_tbl.search().where("id = 'settings'").limit(1).to_arrow()
|
|
75
|
-
rows = arrow.to_pylist() if arrow is not None else []
|
|
76
|
-
if rows:
|
|
77
|
-
raw = rows[0].get("settings") or "{}"
|
|
78
|
-
data = json.loads(raw) if isinstance(raw, str) else (raw or {})
|
|
79
|
-
stored_version = str(data.get("version", stored_version))
|
|
80
|
-
embed_provider = data.get("EMBEDDINGS_PROVIDER")
|
|
81
|
-
embed_model = data.get("EMBEDDINGS_MODEL")
|
|
82
|
-
vector_dim = (
|
|
83
|
-
int(data.get("EMBEDDINGS_VECTOR_DIM")) # pyright: ignore[reportArgumentType]
|
|
84
|
-
if data.get("EMBEDDINGS_VECTOR_DIM") is not None
|
|
85
|
-
else None
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
num_docs = 0
|
|
89
|
-
if "documents" in table_names:
|
|
90
|
-
docs_tbl = db.open_table("documents")
|
|
91
|
-
num_docs = int(docs_tbl.count_rows()) # type: ignore[attr-defined]
|
|
92
|
-
|
|
93
|
-
# Table versions per table (direct API)
|
|
94
|
-
doc_versions = (
|
|
95
|
-
len(list(db.open_table("documents").list_versions()))
|
|
96
|
-
if "documents" in table_names
|
|
97
|
-
else 0
|
|
98
|
-
)
|
|
99
|
-
chunk_versions = (
|
|
100
|
-
len(list(db.open_table("chunks").list_versions()))
|
|
101
|
-
if "chunks" in table_names
|
|
102
|
-
else 0
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
self.console.print(
|
|
106
|
-
f" [repr.attrib_name]haiku.rag version (db)[/repr.attrib_name]: {stored_version}"
|
|
107
|
-
)
|
|
108
|
-
if embed_provider or embed_model or vector_dim:
|
|
109
|
-
provider_part = embed_provider or "unknown"
|
|
110
|
-
model_part = embed_model or "unknown"
|
|
111
|
-
dim_part = f"{vector_dim}" if vector_dim is not None else "unknown"
|
|
112
|
-
self.console.print(
|
|
113
|
-
" [repr.attrib_name]embeddings[/repr.attrib_name]: "
|
|
114
|
-
f"{provider_part}/{model_part} (dim: {dim_part})"
|
|
115
|
-
)
|
|
116
|
-
else:
|
|
117
|
-
self.console.print(
|
|
118
|
-
" [repr.attrib_name]embeddings[/repr.attrib_name]: unknown"
|
|
119
|
-
)
|
|
120
|
-
self.console.print(
|
|
121
|
-
f" [repr.attrib_name]documents[/repr.attrib_name]: {num_docs}"
|
|
122
|
-
)
|
|
123
|
-
self.console.print(
|
|
124
|
-
f" [repr.attrib_name]versions (documents)[/repr.attrib_name]: {doc_versions}"
|
|
125
|
-
)
|
|
126
|
-
self.console.print(
|
|
127
|
-
f" [repr.attrib_name]versions (chunks)[/repr.attrib_name]: {chunk_versions}"
|
|
128
|
-
)
|
|
129
|
-
self.console.rule()
|
|
130
|
-
self.console.print("[bold]Versions[/bold]")
|
|
131
|
-
self.console.print(
|
|
132
|
-
f" [repr.attrib_name]haiku.rag[/repr.attrib_name]: {hr_version}"
|
|
133
|
-
)
|
|
134
|
-
self.console.print(
|
|
135
|
-
f" [repr.attrib_name]lancedb[/repr.attrib_name]: {ldb_version}"
|
|
136
|
-
)
|
|
137
|
-
self.console.print(
|
|
138
|
-
f" [repr.attrib_name]docling[/repr.attrib_name]: {docling_version}"
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
async def list_documents(self):
|
|
142
|
-
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
143
|
-
documents = await self.client.list_documents()
|
|
144
|
-
for doc in documents:
|
|
145
|
-
self._rich_print_document(doc, truncate=True)
|
|
146
|
-
|
|
147
|
-
async def add_document_from_text(self, text: str, metadata: dict | None = None):
|
|
148
|
-
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
149
|
-
doc = await self.client.create_document(text, metadata=metadata)
|
|
150
|
-
self._rich_print_document(doc, truncate=True)
|
|
151
|
-
self.console.print(
|
|
152
|
-
f"[bold green]Document {doc.id} added successfully.[/bold green]"
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
async def add_document_from_source(
|
|
156
|
-
self, source: str, title: str | None = None, metadata: dict | None = None
|
|
157
|
-
):
|
|
158
|
-
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
159
|
-
doc = await self.client.create_document_from_source(
|
|
160
|
-
source, title=title, metadata=metadata
|
|
161
|
-
)
|
|
162
|
-
self._rich_print_document(doc, truncate=True)
|
|
163
|
-
self.console.print(
|
|
164
|
-
f"[bold green]Document {doc.id} added successfully.[/bold green]"
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
async def get_document(self, doc_id: str):
|
|
168
|
-
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
169
|
-
doc = await self.client.get_document_by_id(doc_id)
|
|
170
|
-
if doc is None:
|
|
171
|
-
self.console.print(f"[red]Document with id {doc_id} not found.[/red]")
|
|
172
|
-
return
|
|
173
|
-
self._rich_print_document(doc, truncate=False)
|
|
174
|
-
|
|
175
|
-
async def delete_document(self, doc_id: str):
|
|
176
|
-
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
177
|
-
deleted = await self.client.delete_document(doc_id)
|
|
178
|
-
if deleted:
|
|
179
|
-
self.console.print(
|
|
180
|
-
f"[bold green]Document {doc_id} deleted successfully.[/bold green]"
|
|
181
|
-
)
|
|
182
|
-
else:
|
|
183
|
-
self.console.print(
|
|
184
|
-
f"[yellow]Document with id {doc_id} not found.[/yellow]"
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
async def search(self, query: str, limit: int = 5):
|
|
188
|
-
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
189
|
-
results = await self.client.search(query, limit=limit)
|
|
190
|
-
if not results:
|
|
191
|
-
self.console.print("[yellow]No results found.[/yellow]")
|
|
192
|
-
return
|
|
193
|
-
for chunk, score in results:
|
|
194
|
-
self._rich_print_search_result(chunk, score)
|
|
195
|
-
|
|
196
|
-
async def ask(self, question: str, cite: bool = False):
|
|
197
|
-
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
198
|
-
try:
|
|
199
|
-
answer = await self.client.ask(question, cite=cite)
|
|
200
|
-
self.console.print(f"[bold blue]Question:[/bold blue] {question}")
|
|
201
|
-
self.console.print()
|
|
202
|
-
self.console.print("[bold green]Answer:[/bold green]")
|
|
203
|
-
self.console.print(Markdown(answer))
|
|
204
|
-
except Exception as e:
|
|
205
|
-
self.console.print(f"[red]Error: {e}[/red]")
|
|
206
|
-
|
|
207
|
-
async def research(
|
|
208
|
-
self,
|
|
209
|
-
question: str,
|
|
210
|
-
max_iterations: int = 3,
|
|
211
|
-
confidence_threshold: float = 0.8,
|
|
212
|
-
max_concurrency: int = 1,
|
|
213
|
-
verbose: bool = False,
|
|
214
|
-
):
|
|
215
|
-
"""Run research via the pydantic-graph pipeline (default)."""
|
|
216
|
-
async with HaikuRAG(db_path=self.db_path) as client:
|
|
217
|
-
try:
|
|
218
|
-
if verbose:
|
|
219
|
-
self.console.print("[bold cyan]Starting research[/bold cyan]")
|
|
220
|
-
self.console.print(f"[bold blue]Question:[/bold blue] {question}")
|
|
221
|
-
self.console.print()
|
|
222
|
-
|
|
223
|
-
graph = build_research_graph()
|
|
224
|
-
state = ResearchState(
|
|
225
|
-
question=question,
|
|
226
|
-
context=ResearchContext(original_question=question),
|
|
227
|
-
max_iterations=max_iterations,
|
|
228
|
-
confidence_threshold=confidence_threshold,
|
|
229
|
-
max_concurrency=max_concurrency,
|
|
230
|
-
)
|
|
231
|
-
deps = ResearchDeps(
|
|
232
|
-
client=client, console=self.console if verbose else None
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
start = PlanNode(
|
|
236
|
-
provider=Config.RESEARCH_PROVIDER or Config.QA_PROVIDER,
|
|
237
|
-
model=Config.RESEARCH_MODEL or Config.QA_MODEL,
|
|
238
|
-
)
|
|
239
|
-
# Prefer graph.run; fall back to iter if unavailable
|
|
240
|
-
report = None
|
|
241
|
-
try:
|
|
242
|
-
result = await graph.run(start, state=state, deps=deps)
|
|
243
|
-
report = result.output
|
|
244
|
-
except Exception:
|
|
245
|
-
from pydantic_graph import End
|
|
246
|
-
|
|
247
|
-
async with graph.iter(start, state=state, deps=deps) as run:
|
|
248
|
-
node = run.next_node
|
|
249
|
-
while not isinstance(node, End):
|
|
250
|
-
node = await run.next(node)
|
|
251
|
-
if run.result:
|
|
252
|
-
report = run.result.output
|
|
253
|
-
if report is None:
|
|
254
|
-
raise RuntimeError("Graph did not produce a report")
|
|
255
|
-
|
|
256
|
-
# Display the report
|
|
257
|
-
self.console.print("[bold green]Research Report[/bold green]")
|
|
258
|
-
self.console.rule()
|
|
259
|
-
|
|
260
|
-
# Title and Executive Summary
|
|
261
|
-
self.console.print(f"[bold]{report.title}[/bold]")
|
|
262
|
-
self.console.print()
|
|
263
|
-
self.console.print("[bold cyan]Executive Summary:[/bold cyan]")
|
|
264
|
-
self.console.print(report.executive_summary)
|
|
265
|
-
self.console.print()
|
|
266
|
-
|
|
267
|
-
# Confidence (from last evaluation)
|
|
268
|
-
if state.last_eval:
|
|
269
|
-
conf = state.last_eval.confidence_score # type: ignore[attr-defined]
|
|
270
|
-
self.console.print(f"[bold cyan]Confidence:[/bold cyan] {conf:.1%}")
|
|
271
|
-
self.console.print()
|
|
272
|
-
|
|
273
|
-
# Main Findings
|
|
274
|
-
if report.main_findings:
|
|
275
|
-
self.console.print("[bold cyan]Main Findings:[/bold cyan]")
|
|
276
|
-
for finding in report.main_findings:
|
|
277
|
-
self.console.print(f"• {finding}")
|
|
278
|
-
self.console.print()
|
|
279
|
-
|
|
280
|
-
# (Themes section removed)
|
|
281
|
-
|
|
282
|
-
# Conclusions
|
|
283
|
-
if report.conclusions:
|
|
284
|
-
self.console.print("[bold cyan]Conclusions:[/bold cyan]")
|
|
285
|
-
for conclusion in report.conclusions:
|
|
286
|
-
self.console.print(f"• {conclusion}")
|
|
287
|
-
self.console.print()
|
|
288
|
-
|
|
289
|
-
# Recommendations
|
|
290
|
-
if report.recommendations:
|
|
291
|
-
self.console.print("[bold cyan]Recommendations:[/bold cyan]")
|
|
292
|
-
for rec in report.recommendations:
|
|
293
|
-
self.console.print(f"• {rec}")
|
|
294
|
-
self.console.print()
|
|
295
|
-
|
|
296
|
-
# Limitations
|
|
297
|
-
if report.limitations:
|
|
298
|
-
self.console.print("[bold yellow]Limitations:[/bold yellow]")
|
|
299
|
-
for limitation in report.limitations:
|
|
300
|
-
self.console.print(f"• {limitation}")
|
|
301
|
-
self.console.print()
|
|
302
|
-
|
|
303
|
-
# Sources Summary
|
|
304
|
-
if report.sources_summary:
|
|
305
|
-
self.console.print("[bold cyan]Sources:[/bold cyan]")
|
|
306
|
-
self.console.print(report.sources_summary)
|
|
307
|
-
|
|
308
|
-
except Exception as e:
|
|
309
|
-
self.console.print(f"[red]Error during research: {e}[/red]")
|
|
310
|
-
|
|
311
|
-
async def rebuild(self):
|
|
312
|
-
async with HaikuRAG(db_path=self.db_path, skip_validation=True) as client:
|
|
313
|
-
try:
|
|
314
|
-
documents = await client.list_documents()
|
|
315
|
-
total_docs = len(documents)
|
|
316
|
-
|
|
317
|
-
if total_docs == 0:
|
|
318
|
-
self.console.print(
|
|
319
|
-
"[yellow]No documents found in database.[/yellow]"
|
|
320
|
-
)
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
self.console.print(
|
|
324
|
-
f"[bold cyan]Rebuilding database with {total_docs} documents...[/bold cyan]"
|
|
325
|
-
)
|
|
326
|
-
with Progress() as progress:
|
|
327
|
-
task = progress.add_task("Rebuilding...", total=total_docs)
|
|
328
|
-
async for _ in client.rebuild_database():
|
|
329
|
-
progress.update(task, advance=1)
|
|
330
|
-
|
|
331
|
-
self.console.print(
|
|
332
|
-
"[bold green]Database rebuild completed successfully.[/bold green]"
|
|
333
|
-
)
|
|
334
|
-
except Exception as e:
|
|
335
|
-
self.console.print(f"[red]Error rebuilding database: {e}[/red]")
|
|
336
|
-
|
|
337
|
-
async def vacuum(self):
|
|
338
|
-
"""Run database maintenance: optimize and cleanup table history."""
|
|
339
|
-
try:
|
|
340
|
-
async with HaikuRAG(db_path=self.db_path, skip_validation=True) as client:
|
|
341
|
-
await client.vacuum()
|
|
342
|
-
self.console.print(
|
|
343
|
-
"[bold green]Vacuum completed successfully.[/bold green]"
|
|
344
|
-
)
|
|
345
|
-
except Exception as e:
|
|
346
|
-
self.console.print(f"[red]Error during vacuum: {e}[/red]")
|
|
347
|
-
|
|
348
|
-
def show_settings(self):
|
|
349
|
-
"""Display current configuration settings."""
|
|
350
|
-
self.console.print("[bold]haiku.rag configuration[/bold]")
|
|
351
|
-
self.console.print()
|
|
352
|
-
|
|
353
|
-
# Get all config fields dynamically
|
|
354
|
-
for field_name, field_value in Config.model_dump().items():
|
|
355
|
-
# Format the display value
|
|
356
|
-
if isinstance(field_value, str) and (
|
|
357
|
-
"key" in field_name.lower()
|
|
358
|
-
or "password" in field_name.lower()
|
|
359
|
-
or "token" in field_name.lower()
|
|
360
|
-
):
|
|
361
|
-
# Hide sensitive values but show if they're set
|
|
362
|
-
display_value = "✓ Set" if field_value else "✗ Not set"
|
|
363
|
-
else:
|
|
364
|
-
display_value = field_value
|
|
365
|
-
|
|
366
|
-
self.console.print(
|
|
367
|
-
f" [repr.attrib_name]{field_name}[/repr.attrib_name]: {display_value}"
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
def _rich_print_document(self, doc: Document, truncate: bool = False):
|
|
371
|
-
"""Format a document for display."""
|
|
372
|
-
if truncate:
|
|
373
|
-
content = doc.content.splitlines()
|
|
374
|
-
if len(content) > 3:
|
|
375
|
-
content = content[:3] + ["\n…"]
|
|
376
|
-
content = "\n".join(content)
|
|
377
|
-
content = Markdown(content)
|
|
378
|
-
else:
|
|
379
|
-
content = Markdown(doc.content)
|
|
380
|
-
title_part = (
|
|
381
|
-
f" [repr.attrib_name]title[/repr.attrib_name]: {doc.title}"
|
|
382
|
-
if doc.title
|
|
383
|
-
else ""
|
|
384
|
-
)
|
|
385
|
-
self.console.print(
|
|
386
|
-
f"[repr.attrib_name]id[/repr.attrib_name]: {doc.id} "
|
|
387
|
-
f"[repr.attrib_name]uri[/repr.attrib_name]: {doc.uri}"
|
|
388
|
-
+ title_part
|
|
389
|
-
+ f" [repr.attrib_name]meta[/repr.attrib_name]: {doc.metadata}"
|
|
390
|
-
)
|
|
391
|
-
self.console.print(
|
|
392
|
-
f"[repr.attrib_name]created at[/repr.attrib_name]: {doc.created_at} [repr.attrib_name]updated at[/repr.attrib_name]: {doc.updated_at}"
|
|
393
|
-
)
|
|
394
|
-
self.console.print("[repr.attrib_name]content[/repr.attrib_name]:")
|
|
395
|
-
self.console.print(content)
|
|
396
|
-
self.console.rule()
|
|
397
|
-
|
|
398
|
-
def _rich_print_search_result(self, chunk: Chunk, score: float):
|
|
399
|
-
"""Format a search result chunk for display."""
|
|
400
|
-
content = Markdown(chunk.content)
|
|
401
|
-
self.console.print(
|
|
402
|
-
f"[repr.attrib_name]document_id[/repr.attrib_name]: {chunk.document_id} "
|
|
403
|
-
f"[repr.attrib_name]score[/repr.attrib_name]: {score:.4f}"
|
|
404
|
-
)
|
|
405
|
-
if chunk.document_uri:
|
|
406
|
-
self.console.print("[repr.attrib_name]document uri[/repr.attrib_name]:")
|
|
407
|
-
self.console.print(chunk.document_uri)
|
|
408
|
-
if chunk.document_title:
|
|
409
|
-
self.console.print("[repr.attrib_name]document title[/repr.attrib_name]:")
|
|
410
|
-
self.console.print(chunk.document_title)
|
|
411
|
-
if chunk.document_meta:
|
|
412
|
-
self.console.print("[repr.attrib_name]document meta[/repr.attrib_name]:")
|
|
413
|
-
self.console.print(chunk.document_meta)
|
|
414
|
-
self.console.print("[repr.attrib_name]content[/repr.attrib_name]:")
|
|
415
|
-
self.console.print(content)
|
|
416
|
-
self.console.rule()
|
|
417
|
-
|
|
418
|
-
async def serve(self, transport: str | None = None):
|
|
419
|
-
"""Start the MCP server."""
|
|
420
|
-
async with HaikuRAG(self.db_path) as client:
|
|
421
|
-
monitor = FileWatcher(paths=Config.MONITOR_DIRECTORIES, client=client)
|
|
422
|
-
monitor_task = asyncio.create_task(monitor.observe())
|
|
423
|
-
server = create_mcp_server(self.db_path)
|
|
424
|
-
|
|
425
|
-
try:
|
|
426
|
-
if transport == "stdio":
|
|
427
|
-
await server.run_stdio_async()
|
|
428
|
-
else:
|
|
429
|
-
await server.run_http_async(transport="streamable-http")
|
|
430
|
-
except KeyboardInterrupt:
|
|
431
|
-
pass
|
|
432
|
-
finally:
|
|
433
|
-
monitor_task.cancel()
|
|
434
|
-
try:
|
|
435
|
-
await monitor_task
|
|
436
|
-
except asyncio.CancelledError:
|
|
437
|
-
pass
|
haiku/rag/chunker.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from typing import ClassVar
|
|
2
|
-
|
|
3
|
-
import tiktoken
|
|
4
|
-
from docling.chunking import HybridChunker # type: ignore
|
|
5
|
-
from docling_core.transforms.chunker.tokenizer.openai import OpenAITokenizer
|
|
6
|
-
from docling_core.types.doc.document import DoclingDocument
|
|
7
|
-
|
|
8
|
-
from haiku.rag.config import Config
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Chunker:
|
|
12
|
-
"""A class that chunks text into smaller pieces for embedding and retrieval.
|
|
13
|
-
|
|
14
|
-
Uses docling's structure-aware chunking to create semantically meaningful chunks
|
|
15
|
-
that respect document boundaries.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
chunk_size: The maximum size of a chunk in tokens.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
encoder: ClassVar[tiktoken.Encoding] = tiktoken.encoding_for_model("gpt-4o")
|
|
22
|
-
|
|
23
|
-
def __init__(
|
|
24
|
-
self,
|
|
25
|
-
chunk_size: int = Config.CHUNK_SIZE,
|
|
26
|
-
):
|
|
27
|
-
self.chunk_size = chunk_size
|
|
28
|
-
tokenizer = OpenAITokenizer(
|
|
29
|
-
tokenizer=tiktoken.encoding_for_model("gpt-4o"), max_tokens=chunk_size
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
self.chunker = HybridChunker(tokenizer=tokenizer) # type: ignore
|
|
33
|
-
|
|
34
|
-
async def chunk(self, document: DoclingDocument) -> list[str]:
|
|
35
|
-
"""Split the document into chunks using docling's structure-aware chunking.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
document: The DoclingDocument to be split into chunks.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
A list of text chunks with semantic boundaries.
|
|
42
|
-
"""
|
|
43
|
-
if document is None:
|
|
44
|
-
return []
|
|
45
|
-
|
|
46
|
-
# Chunk using docling's hybrid chunker
|
|
47
|
-
chunks = list(self.chunker.chunk(document))
|
|
48
|
-
return [self.chunker.contextualize(chunk) for chunk in chunks]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
chunker = Chunker()
|