haiku.rag-slim 0.16.0__py3-none-any.whl → 0.24.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.
Potentially problematic release.
This version of haiku.rag-slim might be problematic. Click here for more details.
- haiku/rag/app.py +430 -72
- haiku/rag/chunkers/__init__.py +31 -0
- haiku/rag/chunkers/base.py +31 -0
- haiku/rag/chunkers/docling_local.py +164 -0
- haiku/rag/chunkers/docling_serve.py +179 -0
- haiku/rag/cli.py +207 -24
- haiku/rag/cli_chat.py +489 -0
- haiku/rag/client.py +1251 -266
- haiku/rag/config/__init__.py +16 -10
- haiku/rag/config/loader.py +5 -44
- haiku/rag/config/models.py +126 -17
- haiku/rag/converters/__init__.py +31 -0
- haiku/rag/converters/base.py +63 -0
- haiku/rag/converters/docling_local.py +193 -0
- haiku/rag/converters/docling_serve.py +229 -0
- haiku/rag/converters/text_utils.py +237 -0
- haiku/rag/embeddings/__init__.py +123 -24
- haiku/rag/embeddings/voyageai.py +175 -20
- haiku/rag/graph/__init__.py +0 -11
- haiku/rag/graph/agui/__init__.py +8 -2
- haiku/rag/graph/agui/cli_renderer.py +1 -1
- haiku/rag/graph/agui/emitter.py +219 -31
- haiku/rag/graph/agui/server.py +20 -62
- haiku/rag/graph/agui/stream.py +1 -2
- haiku/rag/graph/research/__init__.py +5 -2
- haiku/rag/graph/research/dependencies.py +12 -126
- haiku/rag/graph/research/graph.py +390 -135
- haiku/rag/graph/research/models.py +91 -112
- haiku/rag/graph/research/prompts.py +99 -91
- haiku/rag/graph/research/state.py +35 -27
- haiku/rag/inspector/__init__.py +8 -0
- haiku/rag/inspector/app.py +259 -0
- haiku/rag/inspector/widgets/__init__.py +6 -0
- haiku/rag/inspector/widgets/chunk_list.py +100 -0
- haiku/rag/inspector/widgets/context_modal.py +89 -0
- haiku/rag/inspector/widgets/detail_view.py +130 -0
- haiku/rag/inspector/widgets/document_list.py +75 -0
- haiku/rag/inspector/widgets/info_modal.py +209 -0
- haiku/rag/inspector/widgets/search_modal.py +183 -0
- haiku/rag/inspector/widgets/visual_modal.py +126 -0
- haiku/rag/mcp.py +106 -102
- haiku/rag/monitor.py +33 -9
- haiku/rag/providers/__init__.py +5 -0
- haiku/rag/providers/docling_serve.py +108 -0
- haiku/rag/qa/__init__.py +12 -10
- haiku/rag/qa/agent.py +43 -61
- haiku/rag/qa/prompts.py +35 -57
- haiku/rag/reranking/__init__.py +9 -6
- haiku/rag/reranking/base.py +1 -1
- haiku/rag/reranking/cohere.py +5 -4
- haiku/rag/reranking/mxbai.py +5 -2
- haiku/rag/reranking/vllm.py +3 -4
- haiku/rag/reranking/zeroentropy.py +6 -5
- haiku/rag/store/__init__.py +2 -1
- haiku/rag/store/engine.py +242 -42
- haiku/rag/store/exceptions.py +4 -0
- haiku/rag/store/models/__init__.py +8 -2
- haiku/rag/store/models/chunk.py +190 -0
- haiku/rag/store/models/document.py +46 -0
- haiku/rag/store/repositories/chunk.py +141 -121
- haiku/rag/store/repositories/document.py +25 -84
- haiku/rag/store/repositories/settings.py +11 -14
- haiku/rag/store/upgrades/__init__.py +19 -3
- haiku/rag/store/upgrades/v0_10_1.py +1 -1
- haiku/rag/store/upgrades/v0_19_6.py +65 -0
- haiku/rag/store/upgrades/v0_20_0.py +68 -0
- haiku/rag/store/upgrades/v0_23_1.py +100 -0
- haiku/rag/store/upgrades/v0_9_3.py +3 -3
- haiku/rag/utils.py +371 -146
- {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/METADATA +15 -12
- haiku_rag_slim-0.24.0.dist-info/RECORD +78 -0
- {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/WHEEL +1 -1
- haiku/rag/chunker.py +0 -65
- haiku/rag/embeddings/base.py +0 -25
- haiku/rag/embeddings/ollama.py +0 -28
- haiku/rag/embeddings/openai.py +0 -26
- haiku/rag/embeddings/vllm.py +0 -29
- haiku/rag/graph/agui/events.py +0 -254
- haiku/rag/graph/common/__init__.py +0 -5
- haiku/rag/graph/common/models.py +0 -42
- haiku/rag/graph/common/nodes.py +0 -265
- haiku/rag/graph/common/prompts.py +0 -46
- haiku/rag/graph/common/utils.py +0 -44
- haiku/rag/graph/deep_qa/__init__.py +0 -1
- haiku/rag/graph/deep_qa/dependencies.py +0 -27
- haiku/rag/graph/deep_qa/graph.py +0 -243
- haiku/rag/graph/deep_qa/models.py +0 -20
- haiku/rag/graph/deep_qa/prompts.py +0 -59
- haiku/rag/graph/deep_qa/state.py +0 -56
- haiku/rag/graph/research/common.py +0 -87
- haiku/rag/reader.py +0 -135
- haiku_rag_slim-0.16.0.dist-info/RECORD +0 -71
- {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/entry_points.txt +0 -0
- {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/licenses/LICENSE +0 -0
haiku/rag/graph/agui/server.py
CHANGED
|
@@ -18,8 +18,7 @@ from starlette.responses import JSONResponse, StreamingResponse
|
|
|
18
18
|
from starlette.routing import Route
|
|
19
19
|
|
|
20
20
|
from haiku.rag.config.models import AGUIConfig
|
|
21
|
-
from haiku.rag.graph.agui.emitter import AGUIEmitter
|
|
22
|
-
from haiku.rag.graph.agui.events import AGUIEvent
|
|
21
|
+
from haiku.rag.graph.agui.emitter import AGUIEmitter, AGUIEvent
|
|
23
22
|
from haiku.rag.graph.agui.stream import stream_graph
|
|
24
23
|
|
|
25
24
|
|
|
@@ -151,23 +150,25 @@ def format_sse_event(event: AGUIEvent) -> str:
|
|
|
151
150
|
return f"data: {event_json}\n\n"
|
|
152
151
|
|
|
153
152
|
|
|
154
|
-
def create_agui_server(
|
|
155
|
-
""
|
|
153
|
+
def create_agui_server( # pragma: no cover
|
|
154
|
+
config: "AppConfig", db_path: Path | None = None
|
|
155
|
+
) -> Starlette:
|
|
156
|
+
"""Create AG-UI server with research endpoint.
|
|
156
157
|
|
|
157
158
|
Args:
|
|
158
|
-
config: Application config with research
|
|
159
|
+
config: Application config with research settings
|
|
159
160
|
db_path: Optional database path override
|
|
160
161
|
|
|
161
162
|
Returns:
|
|
162
|
-
Starlette app with research
|
|
163
|
+
Starlette app with research endpoint
|
|
163
164
|
"""
|
|
164
165
|
from haiku.rag.client import HaikuRAG
|
|
165
|
-
from haiku.rag.graph.deep_qa.dependencies import DeepQAContext
|
|
166
|
-
from haiku.rag.graph.deep_qa.graph import build_deep_qa_graph
|
|
167
|
-
from haiku.rag.graph.deep_qa.state import DeepQADeps, DeepQAState
|
|
168
166
|
from haiku.rag.graph.research.dependencies import ResearchContext
|
|
169
167
|
from haiku.rag.graph.research.graph import build_research_graph
|
|
170
|
-
from haiku.rag.graph.research.state import
|
|
168
|
+
from haiku.rag.graph.research.state import (
|
|
169
|
+
ResearchDeps,
|
|
170
|
+
ResearchState,
|
|
171
|
+
)
|
|
171
172
|
|
|
172
173
|
# Store client reference for proper lifecycle management
|
|
173
174
|
_client_cache: dict[str, HaikuRAG] = {}
|
|
@@ -190,7 +191,14 @@ def create_agui_server(config: "AppConfig", db_path: Path | None = None) -> Star
|
|
|
190
191
|
if messages:
|
|
191
192
|
question = messages[0].get("content", "")
|
|
192
193
|
context = ResearchContext(original_question=question)
|
|
193
|
-
|
|
194
|
+
max_iterations = input_state.get("max_iterations")
|
|
195
|
+
confidence_threshold = input_state.get("confidence_threshold")
|
|
196
|
+
return ResearchState.from_config(
|
|
197
|
+
context=context,
|
|
198
|
+
config=config,
|
|
199
|
+
max_iterations=max_iterations,
|
|
200
|
+
confidence_threshold=confidence_threshold,
|
|
201
|
+
)
|
|
194
202
|
|
|
195
203
|
def research_deps_factory(input_config: dict[str, Any]) -> ResearchDeps:
|
|
196
204
|
effective_db_path = (
|
|
@@ -200,29 +208,7 @@ def create_agui_server(config: "AppConfig", db_path: Path | None = None) -> Star
|
|
|
200
208
|
)
|
|
201
209
|
return ResearchDeps(client=get_client(effective_db_path))
|
|
202
210
|
|
|
203
|
-
#
|
|
204
|
-
def deep_ask_graph_factory() -> Graph:
|
|
205
|
-
return build_deep_qa_graph(config)
|
|
206
|
-
|
|
207
|
-
def deep_ask_state_factory(input_state: dict[str, Any]) -> DeepQAState:
|
|
208
|
-
question = input_state.get("question", "")
|
|
209
|
-
if not question:
|
|
210
|
-
messages = input_state.get("messages", [])
|
|
211
|
-
if messages:
|
|
212
|
-
question = messages[0].get("content", "")
|
|
213
|
-
use_citations = input_state.get("use_citations", False)
|
|
214
|
-
context = DeepQAContext(original_question=question, use_citations=use_citations)
|
|
215
|
-
return DeepQAState.from_config(context=context, config=config)
|
|
216
|
-
|
|
217
|
-
def deep_ask_deps_factory(input_config: dict[str, Any]) -> DeepQADeps:
|
|
218
|
-
effective_db_path = (
|
|
219
|
-
db_path
|
|
220
|
-
or input_config.get("db_path")
|
|
221
|
-
or config.storage.data_dir / "haiku.rag.lancedb"
|
|
222
|
-
)
|
|
223
|
-
return DeepQADeps(client=get_client(effective_db_path))
|
|
224
|
-
|
|
225
|
-
# Create event stream functions for each graph type
|
|
211
|
+
# Create event stream function
|
|
226
212
|
async def research_event_stream(
|
|
227
213
|
input_data: RunAgentInput,
|
|
228
214
|
) -> AsyncIterator[str]:
|
|
@@ -235,18 +221,6 @@ def create_agui_server(config: "AppConfig", db_path: Path | None = None) -> Star
|
|
|
235
221
|
event_data = format_sse_event(event)
|
|
236
222
|
yield event_data
|
|
237
223
|
|
|
238
|
-
async def deep_ask_event_stream(
|
|
239
|
-
input_data: RunAgentInput,
|
|
240
|
-
) -> AsyncIterator[str]:
|
|
241
|
-
"""Generate SSE event stream from deep ask graph execution."""
|
|
242
|
-
graph = deep_ask_graph_factory()
|
|
243
|
-
initial_state = deep_ask_state_factory(input_data.state)
|
|
244
|
-
deps = deep_ask_deps_factory(input_data.config)
|
|
245
|
-
|
|
246
|
-
async for event in stream_graph(graph, initial_state, deps):
|
|
247
|
-
event_data = format_sse_event(event)
|
|
248
|
-
yield event_data
|
|
249
|
-
|
|
250
224
|
# Endpoint handlers
|
|
251
225
|
async def stream_research(request: Request) -> StreamingResponse:
|
|
252
226
|
"""Research graph streaming endpoint."""
|
|
@@ -263,21 +237,6 @@ def create_agui_server(config: "AppConfig", db_path: Path | None = None) -> Star
|
|
|
263
237
|
},
|
|
264
238
|
)
|
|
265
239
|
|
|
266
|
-
async def stream_deep_ask(request: Request) -> StreamingResponse:
|
|
267
|
-
"""Deep ask graph streaming endpoint."""
|
|
268
|
-
body = await request.json()
|
|
269
|
-
input_data = RunAgentInput(**body)
|
|
270
|
-
|
|
271
|
-
return StreamingResponse(
|
|
272
|
-
deep_ask_event_stream(input_data),
|
|
273
|
-
media_type="text/event-stream",
|
|
274
|
-
headers={
|
|
275
|
-
"Cache-Control": "no-cache",
|
|
276
|
-
"Connection": "keep-alive",
|
|
277
|
-
"X-Accel-Buffering": "no",
|
|
278
|
-
},
|
|
279
|
-
)
|
|
280
|
-
|
|
281
240
|
async def health_check(_: Request) -> JSONResponse:
|
|
282
241
|
"""Health check endpoint."""
|
|
283
242
|
return JSONResponse({"status": "healthy"})
|
|
@@ -285,7 +244,6 @@ def create_agui_server(config: "AppConfig", db_path: Path | None = None) -> Star
|
|
|
285
244
|
# Define routes
|
|
286
245
|
routes = [
|
|
287
246
|
Route("/v1/research/stream", stream_research, methods=["POST"]),
|
|
288
|
-
Route("/v1/deep-ask/stream", stream_deep_ask, methods=["POST"]),
|
|
289
247
|
Route("/health", health_check, methods=["GET"]),
|
|
290
248
|
]
|
|
291
249
|
|
haiku/rag/graph/agui/stream.py
CHANGED
|
@@ -8,8 +8,7 @@ from typing import Protocol, TypeVar
|
|
|
8
8
|
from pydantic import BaseModel
|
|
9
9
|
from pydantic_graph.beta import Graph
|
|
10
10
|
|
|
11
|
-
from haiku.rag.graph.agui.emitter import AGUIEmitter
|
|
12
|
-
from haiku.rag.graph.agui.events import AGUIEvent
|
|
11
|
+
from haiku.rag.graph.agui.emitter import AGUIEmitter, AGUIEvent
|
|
13
12
|
|
|
14
13
|
StateT = TypeVar("StateT", bound=BaseModel)
|
|
15
14
|
ResultT = TypeVar("ResultT")
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
from haiku.rag.graph.common.models import SearchAnswer
|
|
2
1
|
from haiku.rag.graph.research.dependencies import ResearchContext, ResearchDependencies
|
|
3
|
-
from haiku.rag.graph.research.models import
|
|
2
|
+
from haiku.rag.graph.research.models import (
|
|
3
|
+
EvaluationResult,
|
|
4
|
+
ResearchReport,
|
|
5
|
+
SearchAnswer,
|
|
6
|
+
)
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
5
|
from haiku.rag.client import HaikuRAG
|
|
6
|
-
from haiku.rag.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
InsightRecord,
|
|
11
|
-
)
|
|
6
|
+
from haiku.rag.store.models import SearchResult
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from haiku.rag.graph.research.models import SearchAnswer
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class ResearchContext(BaseModel):
|
|
@@ -18,124 +16,14 @@ class ResearchContext(BaseModel):
|
|
|
18
16
|
sub_questions: list[str] = Field(
|
|
19
17
|
default_factory=list, description="Decomposed sub-questions"
|
|
20
18
|
)
|
|
21
|
-
qa_responses: list[
|
|
19
|
+
qa_responses: list[Any] = Field(
|
|
22
20
|
default_factory=list, description="Structured QA pairs used during research"
|
|
23
21
|
)
|
|
24
|
-
insights: list[InsightRecord] = Field(
|
|
25
|
-
default_factory=list, description="Key insights discovered"
|
|
26
|
-
)
|
|
27
|
-
gaps: list[GapRecord] = Field(
|
|
28
|
-
default_factory=list, description="Identified information gaps"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
# Private dict indexes for O(1) lookups
|
|
32
|
-
_insights_by_id: dict[str, InsightRecord] = PrivateAttr(default_factory=dict)
|
|
33
|
-
_gaps_by_id: dict[str, GapRecord] = PrivateAttr(default_factory=dict)
|
|
34
|
-
|
|
35
|
-
def model_post_init(self, __context: object) -> None:
|
|
36
|
-
"""Build indexes after initialization."""
|
|
37
|
-
self._insights_by_id = {ins.id: ins for ins in self.insights}
|
|
38
|
-
self._gaps_by_id = {gap.id: gap for gap in self.gaps}
|
|
39
22
|
|
|
40
|
-
def add_qa_response(self, qa: SearchAnswer) -> None:
|
|
41
|
-
"""Add a structured QA response
|
|
23
|
+
def add_qa_response(self, qa: "SearchAnswer") -> None:
|
|
24
|
+
"""Add a structured QA response."""
|
|
42
25
|
self.qa_responses.append(qa)
|
|
43
26
|
|
|
44
|
-
def upsert_insights(self, records: Iterable[InsightRecord]) -> list[InsightRecord]:
|
|
45
|
-
"""Merge one or more insights into the shared context with deduplication."""
|
|
46
|
-
merged: list[InsightRecord] = []
|
|
47
|
-
|
|
48
|
-
for record in records:
|
|
49
|
-
candidate = InsightRecord.model_validate(record)
|
|
50
|
-
existing = self._insights_by_id.get(candidate.id)
|
|
51
|
-
|
|
52
|
-
if existing:
|
|
53
|
-
# Update existing insight
|
|
54
|
-
existing.summary = candidate.summary
|
|
55
|
-
existing.status = candidate.status
|
|
56
|
-
if candidate.notes:
|
|
57
|
-
existing.notes = candidate.notes
|
|
58
|
-
existing.supporting_sources = _merge_unique(
|
|
59
|
-
existing.supporting_sources, candidate.supporting_sources
|
|
60
|
-
)
|
|
61
|
-
existing.originating_questions = _merge_unique(
|
|
62
|
-
existing.originating_questions, candidate.originating_questions
|
|
63
|
-
)
|
|
64
|
-
merged.append(existing)
|
|
65
|
-
else:
|
|
66
|
-
# Add new insight
|
|
67
|
-
new_insight = candidate.model_copy(deep=True)
|
|
68
|
-
self.insights.append(new_insight)
|
|
69
|
-
self._insights_by_id[new_insight.id] = new_insight
|
|
70
|
-
merged.append(new_insight)
|
|
71
|
-
|
|
72
|
-
return merged
|
|
73
|
-
|
|
74
|
-
def upsert_gaps(self, records: Iterable[GapRecord]) -> list[GapRecord]:
|
|
75
|
-
"""Merge one or more gap records into the shared context with deduplication."""
|
|
76
|
-
merged: list[GapRecord] = []
|
|
77
|
-
|
|
78
|
-
for record in records:
|
|
79
|
-
candidate = GapRecord.model_validate(record)
|
|
80
|
-
existing = self._gaps_by_id.get(candidate.id)
|
|
81
|
-
|
|
82
|
-
if existing:
|
|
83
|
-
# Update existing gap
|
|
84
|
-
existing.description = candidate.description
|
|
85
|
-
existing.severity = candidate.severity
|
|
86
|
-
existing.blocking = candidate.blocking
|
|
87
|
-
existing.resolved = candidate.resolved
|
|
88
|
-
if candidate.notes:
|
|
89
|
-
existing.notes = candidate.notes
|
|
90
|
-
existing.supporting_sources = _merge_unique(
|
|
91
|
-
existing.supporting_sources, candidate.supporting_sources
|
|
92
|
-
)
|
|
93
|
-
existing.resolved_by = _merge_unique(
|
|
94
|
-
existing.resolved_by, candidate.resolved_by
|
|
95
|
-
)
|
|
96
|
-
merged.append(existing)
|
|
97
|
-
else:
|
|
98
|
-
# Add new gap
|
|
99
|
-
new_gap = candidate.model_copy(deep=True)
|
|
100
|
-
self.gaps.append(new_gap)
|
|
101
|
-
self._gaps_by_id[new_gap.id] = new_gap
|
|
102
|
-
merged.append(new_gap)
|
|
103
|
-
|
|
104
|
-
return merged
|
|
105
|
-
|
|
106
|
-
def mark_gap_resolved(
|
|
107
|
-
self, identifier: str, resolved_by: Iterable[str] | None = None
|
|
108
|
-
) -> GapRecord | None:
|
|
109
|
-
"""Mark a gap as resolved by identifier."""
|
|
110
|
-
gap = self._gaps_by_id.get(identifier)
|
|
111
|
-
if gap is None:
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
gap.resolved = True
|
|
115
|
-
gap.blocking = False
|
|
116
|
-
if resolved_by:
|
|
117
|
-
gap.resolved_by = _merge_unique(gap.resolved_by, list(resolved_by))
|
|
118
|
-
return gap
|
|
119
|
-
|
|
120
|
-
def integrate_analysis(self, analysis: InsightAnalysis) -> None:
|
|
121
|
-
"""Apply an analysis result to the shared context."""
|
|
122
|
-
merged_insights: list[InsightRecord] = []
|
|
123
|
-
if analysis.highlights:
|
|
124
|
-
merged_insights = self.upsert_insights(analysis.highlights)
|
|
125
|
-
analysis.highlights = merged_insights
|
|
126
|
-
if analysis.gap_assessments:
|
|
127
|
-
merged_gaps = self.upsert_gaps(analysis.gap_assessments)
|
|
128
|
-
analysis.gap_assessments = merged_gaps
|
|
129
|
-
if analysis.resolved_gaps:
|
|
130
|
-
resolved_by_list = (
|
|
131
|
-
[ins.id for ins in merged_insights] if merged_insights else None
|
|
132
|
-
)
|
|
133
|
-
for resolved in analysis.resolved_gaps:
|
|
134
|
-
self.mark_gap_resolved(resolved, resolved_by=resolved_by_list)
|
|
135
|
-
for question in analysis.new_questions:
|
|
136
|
-
if question not in self.sub_questions:
|
|
137
|
-
self.sub_questions.append(question)
|
|
138
|
-
|
|
139
27
|
|
|
140
28
|
class ResearchDependencies(BaseModel):
|
|
141
29
|
"""Dependencies for research agents with multi-agent context."""
|
|
@@ -144,8 +32,6 @@ class ResearchDependencies(BaseModel):
|
|
|
144
32
|
|
|
145
33
|
client: HaikuRAG = Field(description="RAG client for document operations")
|
|
146
34
|
context: ResearchContext = Field(description="Shared research context")
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"""Merge two iterables preserving order while removing duplicates."""
|
|
151
|
-
return [k for k in dict.fromkeys([*existing, *incoming]) if k]
|
|
35
|
+
search_results: list[SearchResult] = Field(
|
|
36
|
+
default_factory=list, description="Search results for citation resolution"
|
|
37
|
+
)
|