haiku.rag 0.10.1__py3-none-any.whl → 0.11.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 might be problematic. Click here for more details.
- haiku/rag/app.py +152 -28
- haiku/rag/cli.py +72 -2
- haiku/rag/migration.py +2 -2
- haiku/rag/research/__init__.py +8 -0
- haiku/rag/research/common.py +71 -6
- haiku/rag/research/dependencies.py +179 -11
- haiku/rag/research/graph.py +5 -3
- haiku/rag/research/models.py +134 -1
- haiku/rag/research/nodes/analysis.py +181 -0
- haiku/rag/research/nodes/plan.py +16 -9
- haiku/rag/research/nodes/search.py +14 -11
- haiku/rag/research/nodes/synthesize.py +7 -3
- haiku/rag/research/prompts.py +67 -28
- haiku/rag/research/state.py +11 -4
- haiku/rag/research/stream.py +177 -0
- haiku/rag/store/__init__.py +1 -1
- haiku/rag/store/models/__init__.py +1 -1
- haiku/rag/utils.py +34 -0
- {haiku_rag-0.10.1.dist-info → haiku_rag-0.11.0.dist-info}/METADATA +34 -14
- {haiku_rag-0.10.1.dist-info → haiku_rag-0.11.0.dist-info}/RECORD +23 -22
- haiku/rag/research/nodes/evaluate.py +0 -80
- {haiku_rag-0.10.1.dist-info → haiku_rag-0.11.0.dist-info}/WHEEL +0 -0
- {haiku_rag-0.10.1.dist-info → haiku_rag-0.11.0.dist-info}/entry_points.txt +0 -0
- {haiku_rag-0.10.1.dist-info → haiku_rag-0.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -24,7 +24,8 @@ class SynthesizeNode(BaseNode[ResearchState, ResearchDeps, ResearchReport]):
|
|
|
24
24
|
deps = ctx.deps
|
|
25
25
|
|
|
26
26
|
log(
|
|
27
|
-
deps
|
|
27
|
+
deps,
|
|
28
|
+
state,
|
|
28
29
|
"\n[bold cyan]📝 Generating final research report...[/bold cyan]",
|
|
29
30
|
)
|
|
30
31
|
|
|
@@ -43,9 +44,12 @@ class SynthesizeNode(BaseNode[ResearchState, ResearchDeps, ResearchReport]):
|
|
|
43
44
|
"Create a detailed report that synthesizes all findings into a coherent response."
|
|
44
45
|
)
|
|
45
46
|
agent_deps = ResearchDependencies(
|
|
46
|
-
client=deps.client,
|
|
47
|
+
client=deps.client,
|
|
48
|
+
context=state.context,
|
|
49
|
+
console=deps.console,
|
|
50
|
+
stream=deps.stream,
|
|
47
51
|
)
|
|
48
52
|
result = await agent.run(prompt, deps=agent_deps)
|
|
49
53
|
|
|
50
|
-
log(deps
|
|
54
|
+
log(deps, state, "[bold green]✅ Research complete![/bold green]")
|
|
51
55
|
return End(result.output)
|
haiku/rag/research/prompts.py
CHANGED
|
@@ -44,38 +44,77 @@ Answering rules:
|
|
|
44
44
|
- Prefer concise phrasing; avoid copying long passages.
|
|
45
45
|
- When evidence is partial, state the limits explicitly in the answer."""
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
INSIGHT_AGENT_PROMPT = """You are the insight aggregation specialist for the
|
|
48
|
+
research workflow.
|
|
49
49
|
|
|
50
50
|
Inputs available:
|
|
51
|
-
- Original research question
|
|
52
|
-
- Question–answer pairs
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
3.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
51
|
+
- Original research question and sub-questions
|
|
52
|
+
- Question–answer pairs with supporting snippets and sources
|
|
53
|
+
- Existing insights and gaps (with status metadata)
|
|
54
|
+
|
|
55
|
+
Tasks:
|
|
56
|
+
1. Extract new or refined insights that advance understanding of the question.
|
|
57
|
+
2. Update gap status, creating new gap entries when necessary and marking
|
|
58
|
+
resolved ones explicitly.
|
|
59
|
+
3. Suggest up to 3 high-impact follow-up sub_questions that would close the
|
|
60
|
+
most important remaining gaps.
|
|
61
|
+
|
|
62
|
+
Output format (map directly to fields):
|
|
63
|
+
- highlights: list of insights with fields {summary, status, supporting_sources,
|
|
64
|
+
originating_questions, notes}. Use status one of {validated, open, tentative}.
|
|
65
|
+
- gap_assessments: list of gaps with fields {description, severity, blocking,
|
|
66
|
+
resolved, resolved_by, supporting_sources, notes}. Severity must be one of
|
|
67
|
+
{low, medium, high}. resolved_by may reference related insight summaries if no
|
|
68
|
+
stable identifier yet.
|
|
69
|
+
- resolved_gaps: list of identifiers or descriptions for gaps now closed.
|
|
70
|
+
- new_questions: up to 3 standalone, specific sub-questions (no duplicates with
|
|
71
|
+
existing ones).
|
|
72
|
+
- commentary: 1–3 sentences summarizing what changed this round.
|
|
73
|
+
|
|
74
|
+
Guidance:
|
|
75
|
+
- Be concise and avoid repeating previously recorded information unless it
|
|
76
|
+
changed materially.
|
|
77
|
+
- Tie supporting_sources to the evidence used; omit if unavailable.
|
|
78
|
+
- Only propose new sub_questions that directly address remaining gaps.
|
|
79
|
+
- When marking a gap as resolved, ensure the rationale is clear via
|
|
80
|
+
resolved_by or notes."""
|
|
81
|
+
|
|
82
|
+
DECISION_AGENT_PROMPT = """You are the research governor responsible for making
|
|
83
|
+
stop/go decisions.
|
|
84
|
+
|
|
85
|
+
Inputs available:
|
|
86
|
+
- Original research question and current plan
|
|
87
|
+
- Full insight ledger with status metadata
|
|
88
|
+
- Up-to-date gap tracker, including resolved indicators
|
|
89
|
+
- Latest insight analysis summary (highlights, gap changes, new questions)
|
|
90
|
+
- Previous evaluation decision (if any)
|
|
91
|
+
|
|
92
|
+
Tasks:
|
|
93
|
+
1. Determine whether the collected evidence now answers the original question.
|
|
94
|
+
2. Provide a confidence_score in [0,1] that reflects coverage, evidence quality,
|
|
95
|
+
and agreement across sources.
|
|
96
|
+
3. List the highest-priority gaps that still block a confident answer. Reference
|
|
97
|
+
existing gap descriptions rather than inventing new ones.
|
|
98
|
+
4. Optionally propose up to 3 new sub_questions only if they are not already in
|
|
99
|
+
the current backlog.
|
|
69
100
|
|
|
70
101
|
Strictness:
|
|
71
|
-
- Only mark research as sufficient when
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
-
|
|
102
|
+
- Only mark research as sufficient when every critical aspect of the main
|
|
103
|
+
question is addressed with reliable, corroborated evidence.
|
|
104
|
+
- Treat unresolved high-severity or blocking gaps as a hard stop.
|
|
105
|
+
|
|
106
|
+
Output fields must line up with EvaluationResult:
|
|
107
|
+
- key_insights: concise bullet-ready statements of the most decision-relevant
|
|
108
|
+
insights (cite status if helpful).
|
|
109
|
+
- new_questions: follow-up sub-questions (max 3) meeting the specificity rules.
|
|
110
|
+
- gaps: list remaining blockers; reuse wording from the tracked gaps when
|
|
111
|
+
possible to aid downstream reconciliation.
|
|
112
|
+
- confidence_score: numeric in [0,1].
|
|
113
|
+
- is_sufficient: true only when no blocking gaps remain.
|
|
114
|
+
- reasoning: short narrative tying the decision to evidence coverage.
|
|
115
|
+
|
|
116
|
+
Remember: prefer maintaining continuity with the structured context over
|
|
117
|
+
introducing new terminology."""
|
|
79
118
|
|
|
80
119
|
SYNTHESIS_AGENT_PROMPT = """You are a synthesis specialist producing the final
|
|
81
120
|
research report.
|
haiku/rag/research/state.py
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass
|
|
2
2
|
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
|
|
5
5
|
from haiku.rag.client import HaikuRAG
|
|
6
6
|
from haiku.rag.research.dependencies import ResearchContext
|
|
7
|
-
from haiku.rag.research.models import EvaluationResult
|
|
7
|
+
from haiku.rag.research.models import EvaluationResult, InsightAnalysis
|
|
8
|
+
from haiku.rag.research.stream import ResearchStream
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
@dataclass
|
|
11
12
|
class ResearchDeps:
|
|
12
13
|
client: HaikuRAG
|
|
13
14
|
console: Console | None = None
|
|
15
|
+
stream: ResearchStream | None = None
|
|
16
|
+
|
|
17
|
+
def emit_log(self, message: str, state: "ResearchState | None" = None) -> None:
|
|
18
|
+
if self.console:
|
|
19
|
+
self.console.print(message)
|
|
20
|
+
if self.stream:
|
|
21
|
+
self.stream.log(message, state)
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
@dataclass
|
|
17
25
|
class ResearchState:
|
|
18
|
-
question: str
|
|
19
26
|
context: ResearchContext
|
|
20
|
-
sub_questions: list[str] = field(default_factory=list)
|
|
21
27
|
iterations: int = 0
|
|
22
28
|
max_iterations: int = 3
|
|
23
29
|
max_concurrency: int = 1
|
|
24
30
|
confidence_threshold: float = 0.8
|
|
25
31
|
last_eval: EvaluationResult | None = None
|
|
32
|
+
last_analysis: InsightAnalysis | None = None
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import AsyncIterator
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Literal
|
|
5
|
+
|
|
6
|
+
from haiku.rag.research.models import ResearchReport
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
9
|
+
from haiku.rag.research.state import ResearchState
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(slots=True)
|
|
13
|
+
class ResearchStateSnapshot:
|
|
14
|
+
question: str
|
|
15
|
+
sub_questions: list[str]
|
|
16
|
+
iterations: int
|
|
17
|
+
max_iterations: int
|
|
18
|
+
max_concurrency: int
|
|
19
|
+
confidence_threshold: float
|
|
20
|
+
pending_sub_questions: int
|
|
21
|
+
answered_questions: int
|
|
22
|
+
insights: list[str]
|
|
23
|
+
gaps: list[str]
|
|
24
|
+
last_confidence: float | None
|
|
25
|
+
last_sufficient: bool | None
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_state(cls, state: "ResearchState") -> "ResearchStateSnapshot":
|
|
29
|
+
context = state.context
|
|
30
|
+
last_confidence: float | None = None
|
|
31
|
+
last_sufficient: bool | None = None
|
|
32
|
+
if state.last_eval:
|
|
33
|
+
last_confidence = state.last_eval.confidence_score
|
|
34
|
+
last_sufficient = state.last_eval.is_sufficient
|
|
35
|
+
|
|
36
|
+
return cls(
|
|
37
|
+
question=context.original_question,
|
|
38
|
+
sub_questions=list(context.sub_questions),
|
|
39
|
+
iterations=state.iterations,
|
|
40
|
+
max_iterations=state.max_iterations,
|
|
41
|
+
max_concurrency=state.max_concurrency,
|
|
42
|
+
confidence_threshold=state.confidence_threshold,
|
|
43
|
+
pending_sub_questions=len(context.sub_questions),
|
|
44
|
+
answered_questions=len(context.qa_responses),
|
|
45
|
+
insights=[
|
|
46
|
+
f"{insight.status.value}:{insight.summary}"
|
|
47
|
+
for insight in context.insights
|
|
48
|
+
],
|
|
49
|
+
gaps=[
|
|
50
|
+
f"{gap.severity.value}/{'resolved' if gap.resolved else 'open'}:{gap.description}"
|
|
51
|
+
for gap in context.gaps
|
|
52
|
+
],
|
|
53
|
+
last_confidence=last_confidence,
|
|
54
|
+
last_sufficient=last_sufficient,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(slots=True)
|
|
59
|
+
class ResearchStreamEvent:
|
|
60
|
+
type: Literal["log", "report", "error"]
|
|
61
|
+
message: str | None = None
|
|
62
|
+
state: ResearchStateSnapshot | None = None
|
|
63
|
+
report: ResearchReport | None = None
|
|
64
|
+
error: str | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ResearchStream:
|
|
68
|
+
"""Queue-backed stream for research graph events."""
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
self._queue: asyncio.Queue[ResearchStreamEvent | None] = asyncio.Queue()
|
|
72
|
+
self._closed = False
|
|
73
|
+
|
|
74
|
+
def _snapshot(self, state: "ResearchState | None") -> ResearchStateSnapshot | None:
|
|
75
|
+
if state is None:
|
|
76
|
+
return None
|
|
77
|
+
return ResearchStateSnapshot.from_state(state)
|
|
78
|
+
|
|
79
|
+
def log(self, message: str, state: "ResearchState | None" = None) -> None:
|
|
80
|
+
if self._closed:
|
|
81
|
+
return
|
|
82
|
+
event = ResearchStreamEvent(
|
|
83
|
+
type="log", message=message, state=self._snapshot(state)
|
|
84
|
+
)
|
|
85
|
+
self._queue.put_nowait(event)
|
|
86
|
+
|
|
87
|
+
def report(self, report: ResearchReport, state: "ResearchState") -> None:
|
|
88
|
+
if self._closed:
|
|
89
|
+
return
|
|
90
|
+
event = ResearchStreamEvent(
|
|
91
|
+
type="report",
|
|
92
|
+
report=report,
|
|
93
|
+
state=self._snapshot(state),
|
|
94
|
+
)
|
|
95
|
+
self._queue.put_nowait(event)
|
|
96
|
+
|
|
97
|
+
def error(self, error: Exception, state: "ResearchState | None" = None) -> None:
|
|
98
|
+
if self._closed:
|
|
99
|
+
return
|
|
100
|
+
event = ResearchStreamEvent(
|
|
101
|
+
type="error",
|
|
102
|
+
message=str(error),
|
|
103
|
+
error=str(error),
|
|
104
|
+
state=self._snapshot(state),
|
|
105
|
+
)
|
|
106
|
+
self._queue.put_nowait(event)
|
|
107
|
+
|
|
108
|
+
async def close(self) -> None:
|
|
109
|
+
if self._closed:
|
|
110
|
+
return
|
|
111
|
+
self._closed = True
|
|
112
|
+
await self._queue.put(None)
|
|
113
|
+
|
|
114
|
+
def __aiter__(self) -> AsyncIterator[ResearchStreamEvent]:
|
|
115
|
+
return self._iter_events()
|
|
116
|
+
|
|
117
|
+
async def _iter_events(self) -> AsyncIterator[ResearchStreamEvent]:
|
|
118
|
+
while True:
|
|
119
|
+
event = await self._queue.get()
|
|
120
|
+
if event is None:
|
|
121
|
+
break
|
|
122
|
+
yield event
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def stream_research_graph(
|
|
126
|
+
graph,
|
|
127
|
+
start,
|
|
128
|
+
state: "ResearchState",
|
|
129
|
+
deps,
|
|
130
|
+
) -> AsyncIterator[ResearchStreamEvent]:
|
|
131
|
+
"""Run the research graph and yield streaming events as they occur."""
|
|
132
|
+
|
|
133
|
+
from contextlib import suppress
|
|
134
|
+
|
|
135
|
+
from haiku.rag.research.state import ResearchDeps # Local import to avoid cycle
|
|
136
|
+
|
|
137
|
+
if not isinstance(deps, ResearchDeps):
|
|
138
|
+
raise TypeError("deps must be an instance of ResearchDeps")
|
|
139
|
+
|
|
140
|
+
stream = ResearchStream()
|
|
141
|
+
deps.stream = stream
|
|
142
|
+
|
|
143
|
+
async def _execute() -> None:
|
|
144
|
+
try:
|
|
145
|
+
report = None
|
|
146
|
+
try:
|
|
147
|
+
result = await graph.run(start, state=state, deps=deps)
|
|
148
|
+
report = result.output
|
|
149
|
+
except Exception:
|
|
150
|
+
from pydantic_graph import End
|
|
151
|
+
|
|
152
|
+
async with graph.iter(start, state=state, deps=deps) as run:
|
|
153
|
+
node = run.next_node
|
|
154
|
+
while not isinstance(node, End):
|
|
155
|
+
node = await run.next(node)
|
|
156
|
+
if run.result:
|
|
157
|
+
report = run.result.output
|
|
158
|
+
|
|
159
|
+
if report is None:
|
|
160
|
+
raise RuntimeError("Graph did not produce a report")
|
|
161
|
+
|
|
162
|
+
stream.report(report, state)
|
|
163
|
+
except Exception as exc: # pragma: no cover - defensive path
|
|
164
|
+
stream.error(exc, state)
|
|
165
|
+
finally:
|
|
166
|
+
await stream.close()
|
|
167
|
+
|
|
168
|
+
runner = asyncio.create_task(_execute())
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
async for event in stream:
|
|
172
|
+
yield event
|
|
173
|
+
finally:
|
|
174
|
+
if not runner.done():
|
|
175
|
+
runner.cancel()
|
|
176
|
+
with suppress(asyncio.CancelledError):
|
|
177
|
+
await runner
|
haiku/rag/store/__init__.py
CHANGED
haiku/rag/utils.py
CHANGED
|
@@ -163,3 +163,37 @@ def load_callable(path: str):
|
|
|
163
163
|
f"Attribute '{func_name}' in module '{module_part}' is not callable"
|
|
164
164
|
)
|
|
165
165
|
return func
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def prefetch_models():
|
|
169
|
+
"""Prefetch runtime models (Docling + Ollama as configured)."""
|
|
170
|
+
import httpx
|
|
171
|
+
from docling.utils.model_downloader import download_models
|
|
172
|
+
|
|
173
|
+
from haiku.rag.config import Config
|
|
174
|
+
|
|
175
|
+
download_models()
|
|
176
|
+
|
|
177
|
+
# Collect Ollama models from config
|
|
178
|
+
required_models: set[str] = set()
|
|
179
|
+
if Config.EMBEDDINGS_PROVIDER == "ollama":
|
|
180
|
+
required_models.add(Config.EMBEDDINGS_MODEL)
|
|
181
|
+
if Config.QA_PROVIDER == "ollama":
|
|
182
|
+
required_models.add(Config.QA_MODEL)
|
|
183
|
+
if Config.RESEARCH_PROVIDER == "ollama":
|
|
184
|
+
required_models.add(Config.RESEARCH_MODEL)
|
|
185
|
+
if Config.RERANK_PROVIDER == "ollama":
|
|
186
|
+
required_models.add(Config.RERANK_MODEL)
|
|
187
|
+
|
|
188
|
+
if not required_models:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
base_url = Config.OLLAMA_BASE_URL
|
|
192
|
+
|
|
193
|
+
with httpx.Client(timeout=None) as client:
|
|
194
|
+
for model in sorted(required_models):
|
|
195
|
+
with client.stream(
|
|
196
|
+
"POST", f"{base_url}/api/pull", json={"model": model}
|
|
197
|
+
) as r:
|
|
198
|
+
for _ in r.iter_lines():
|
|
199
|
+
pass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haiku.rag
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: Agentic Retrieval Augmented Generation (RAG) with LanceDB
|
|
5
5
|
Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -66,7 +66,8 @@ uv pip install haiku.rag
|
|
|
66
66
|
|
|
67
67
|
# Add documents
|
|
68
68
|
haiku-rag add "Your content here"
|
|
69
|
-
haiku-rag add
|
|
69
|
+
haiku-rag add "Your content here" --meta author=alice --meta topic=notes
|
|
70
|
+
haiku-rag add-src document.pdf --meta source=manual
|
|
70
71
|
|
|
71
72
|
# Search
|
|
72
73
|
haiku-rag search "query"
|
|
@@ -101,11 +102,12 @@ haiku-rag serve
|
|
|
101
102
|
```python
|
|
102
103
|
from haiku.rag.client import HaikuRAG
|
|
103
104
|
from haiku.rag.research import (
|
|
105
|
+
PlanNode,
|
|
104
106
|
ResearchContext,
|
|
105
107
|
ResearchDeps,
|
|
106
108
|
ResearchState,
|
|
107
109
|
build_research_graph,
|
|
108
|
-
|
|
110
|
+
stream_research_graph,
|
|
109
111
|
)
|
|
110
112
|
|
|
111
113
|
async with HaikuRAG("database.lancedb") as client:
|
|
@@ -127,22 +129,40 @@ async with HaikuRAG("database.lancedb") as client:
|
|
|
127
129
|
|
|
128
130
|
# Multi‑agent research pipeline (Plan → Search → Evaluate → Synthesize)
|
|
129
131
|
graph = build_research_graph()
|
|
132
|
+
question = (
|
|
133
|
+
"What are the main drivers and trends of global temperature "
|
|
134
|
+
"anomalies since 1990?"
|
|
135
|
+
)
|
|
130
136
|
state = ResearchState(
|
|
131
|
-
|
|
132
|
-
"What are the main drivers and trends of global temperature "
|
|
133
|
-
"anomalies since 1990?"
|
|
134
|
-
),
|
|
135
|
-
context=ResearchContext(original_question="…"),
|
|
137
|
+
context=ResearchContext(original_question=question),
|
|
136
138
|
max_iterations=2,
|
|
137
139
|
confidence_threshold=0.8,
|
|
138
|
-
max_concurrency=
|
|
140
|
+
max_concurrency=2,
|
|
139
141
|
)
|
|
140
142
|
deps = ResearchDeps(client=client)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
|
|
144
|
+
# Blocking run (final result only)
|
|
145
|
+
result = await graph.run(
|
|
146
|
+
PlanNode(provider="openai", model="gpt-4o-mini"),
|
|
147
|
+
state=state,
|
|
148
|
+
deps=deps,
|
|
149
|
+
)
|
|
150
|
+
print(result.output.title)
|
|
151
|
+
|
|
152
|
+
# Streaming progress (log/report/error events)
|
|
153
|
+
async for event in stream_research_graph(
|
|
154
|
+
graph,
|
|
155
|
+
PlanNode(provider="openai", model="gpt-4o-mini"),
|
|
156
|
+
state,
|
|
157
|
+
deps,
|
|
158
|
+
):
|
|
159
|
+
if event.type == "log":
|
|
160
|
+
iteration = event.state.iterations if event.state else state.iterations
|
|
161
|
+
print(f"[{iteration}] {event.message}")
|
|
162
|
+
elif event.type == "report":
|
|
163
|
+
print("\nResearch complete!\n")
|
|
164
|
+
print(event.report.title)
|
|
165
|
+
print(event.report.executive_summary)
|
|
146
166
|
```
|
|
147
167
|
|
|
148
168
|
## MCP Server
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
haiku/rag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
haiku/rag/app.py,sha256=
|
|
2
|
+
haiku/rag/app.py,sha256=TRFwMP9mzLaM7EPc7dhsPODKZxCDkSSgPCnGAdj65VU,17929
|
|
3
3
|
haiku/rag/chunker.py,sha256=PVe6ysv8UlacUd4Zb3_8RFWIaWDXnzBAy2VDJ4TaUsE,1555
|
|
4
|
-
haiku/rag/cli.py,sha256=
|
|
4
|
+
haiku/rag/cli.py,sha256=wreAxyXSRnn7f09t9SGe4uAXQjlieUQIpNpOapJT7y8,12910
|
|
5
5
|
haiku/rag/client.py,sha256=iUaa6YUac3CXFniIm8DsaaNsiyHsi4cp8-fPhF5XuVU,22925
|
|
6
6
|
haiku/rag/config.py,sha256=SEV2OzaKavYwHZ0LmRzBj-0dbI6YFIRuNiTw9el7SO0,2307
|
|
7
7
|
haiku/rag/logging.py,sha256=dm65AwADpcQsH5OAPtRA-4hsw0w5DK-sGOvzYkj6jzw,1720
|
|
8
8
|
haiku/rag/mcp.py,sha256=H7XibtSNUviFeaJVsXzHiRqUm0nJCpA7A1QHuBv6SKQ,5057
|
|
9
|
-
haiku/rag/migration.py,sha256=
|
|
9
|
+
haiku/rag/migration.py,sha256=zm0-60PiS1hIQnZz65B7qfsgM7GwZVXFqMFowjpVBs8,11058
|
|
10
10
|
haiku/rag/monitor.py,sha256=r386nkhdlsU8UECwIuVwnrSlgMk3vNIuUZGNIzkZuec,2770
|
|
11
11
|
haiku/rag/reader.py,sha256=qkPTMJuQ_o4sK-8zpDl9WFYe_MJ7aL_gUw6rczIpW-g,3274
|
|
12
|
-
haiku/rag/utils.py,sha256=
|
|
12
|
+
haiku/rag/utils.py,sha256=dBzhKaOHI9KRiJqHErcXUnqtnXY2AgOK8PCLA3rhO0A,6115
|
|
13
13
|
haiku/rag/embeddings/__init__.py,sha256=44IfDITGIFTflGT6UEmiYOwpWFVbYv5smLY59D0YeCs,1419
|
|
14
14
|
haiku/rag/embeddings/base.py,sha256=BnSviKrlzjv3L0sZJs_T-pxfawd-bcTak-rsX-D2f3A,497
|
|
15
15
|
haiku/rag/embeddings/ollama.py,sha256=LuLlHH6RGoO9_gFCIlbmesuXOj017gTw6z-p8Ez0CfE,595
|
|
@@ -24,20 +24,21 @@ haiku/rag/reranking/base.py,sha256=LM9yUSSJ414UgBZhFTgxGprlRqzfTe4I1vgjricz2JY,4
|
|
|
24
24
|
haiku/rag/reranking/cohere.py,sha256=1iTdiaa8vvb6oHVB2qpWzUOVkyfUcimVSZp6Qr4aq4c,1049
|
|
25
25
|
haiku/rag/reranking/mxbai.py,sha256=uveGFIdmNmepd2EQsvYr64wv0ra2_wB845hdSZXy5Cw,908
|
|
26
26
|
haiku/rag/reranking/vllm.py,sha256=xVGH9ss-ISWdJ5SKUUHUbTqBo7PIEmA_SQv0ScdJ6XA,1479
|
|
27
|
-
haiku/rag/research/__init__.py,sha256=
|
|
28
|
-
haiku/rag/research/common.py,sha256=
|
|
29
|
-
haiku/rag/research/dependencies.py,sha256=
|
|
30
|
-
haiku/rag/research/graph.py,sha256=
|
|
31
|
-
haiku/rag/research/models.py,sha256=
|
|
32
|
-
haiku/rag/research/prompts.py,sha256=
|
|
33
|
-
haiku/rag/research/state.py,sha256=
|
|
34
|
-
haiku/rag/research/
|
|
35
|
-
haiku/rag/research/nodes/
|
|
36
|
-
haiku/rag/research/nodes/
|
|
37
|
-
haiku/rag/research/nodes/
|
|
38
|
-
haiku/rag/
|
|
27
|
+
haiku/rag/research/__init__.py,sha256=Ujci3u7yM11g10J3EzOYs6y4nG1W3CeG70pMRvPoSL4,708
|
|
28
|
+
haiku/rag/research/common.py,sha256=E-7SN1XBbMoTp5sWGGcItBfnmOvFgbeybB0FnqSCp9I,3995
|
|
29
|
+
haiku/rag/research/dependencies.py,sha256=GYtD2jkxBkxeHm44JAtrCGx0IMJbDCiVXnbQd_n6T0M,8118
|
|
30
|
+
haiku/rag/research/graph.py,sha256=Zaqdjj3wmSTPdMoMl5CmhM2z1otwDq9kS-e5vYi-Y7k,879
|
|
31
|
+
haiku/rag/research/models.py,sha256=LInrRtcL-VzCL_PA8LRBqpH5wVckEFuf8zkVnZg7wEg,6666
|
|
32
|
+
haiku/rag/research/prompts.py,sha256=5lDXT874npJn-oXucLk6Z5jqvXEf0cGrnEeNE46iur8,7056
|
|
33
|
+
haiku/rag/research/state.py,sha256=P8RXJMi3wA3l1j6yo8dsAyso6S27FgqS7fvZUUY447A,917
|
|
34
|
+
haiku/rag/research/stream.py,sha256=amyGDimkNp_FHYUXCqtpbeDOx7sC1jQ-7DwoxuNOL1g,5576
|
|
35
|
+
haiku/rag/research/nodes/analysis.py,sha256=-ydF7dpzyiOSwk6jwhWHqUtTXk_Bzr-dMWodIdBr9KA,6475
|
|
36
|
+
haiku/rag/research/nodes/plan.py,sha256=8lsrDt70SjLcK-awnII7PyW6gaGZ-YLmP7nCqCxqkBo,2588
|
|
37
|
+
haiku/rag/research/nodes/search.py,sha256=-5JgsaWVivFmt7jCdBradE0ZXf_i87o2fj9eLgRC1uE,3674
|
|
38
|
+
haiku/rag/research/nodes/synthesize.py,sha256=SEQ6aDgSwvSVHyJxVK0248JnpfrfDoUbvlEUTAQll58,1803
|
|
39
|
+
haiku/rag/store/__init__.py,sha256=R2IRcxtkFDxqa2sgMirqLq3l2-FPdWr6ydYStaqm5OQ,104
|
|
39
40
|
haiku/rag/store/engine.py,sha256=BceAeTpDgV92B1A3GVcjsTwlD-c0cZPPvGiXW2Gola0,10215
|
|
40
|
-
haiku/rag/store/models/__init__.py,sha256=
|
|
41
|
+
haiku/rag/store/models/__init__.py,sha256=kc7Ctf53Jr483tk4QTIrcgqBbXDz4ZoeYSkFXfPnpks,89
|
|
41
42
|
haiku/rag/store/models/chunk.py,sha256=3EuZav4QekJIeHBCub48EM8SjNX8HEJ6wVDXGot4PEQ,421
|
|
42
43
|
haiku/rag/store/models/document.py,sha256=cZXy_jEti-hnhq7FKhuhCfd99ccY9fIHMLovB_Thbb8,425
|
|
43
44
|
haiku/rag/store/repositories/__init__.py,sha256=Olv5dLfBQINRV3HrsfUpjzkZ7Qm7goEYyMNykgo_DaY,291
|
|
@@ -47,8 +48,8 @@ haiku/rag/store/repositories/settings.py,sha256=7XMBMavU8zRgdBoQzQg0Obfa7UKjuVnB
|
|
|
47
48
|
haiku/rag/store/upgrades/__init__.py,sha256=RQ8A6rEXBASLb5PD9vdDnEas_m_GgRzzdVu4B88Snqc,1975
|
|
48
49
|
haiku/rag/store/upgrades/v0_10_1.py,sha256=qNGnxj6hoHaHJ1rKTiALfw0c9NQOi0KAK-VZCD_073A,1959
|
|
49
50
|
haiku/rag/store/upgrades/v0_9_3.py,sha256=NrjNilQSgDtFWRbL3ZUtzQzJ8tf9u0dDRJtnDFwwbdw,3322
|
|
50
|
-
haiku_rag-0.
|
|
51
|
-
haiku_rag-0.
|
|
52
|
-
haiku_rag-0.
|
|
53
|
-
haiku_rag-0.
|
|
54
|
-
haiku_rag-0.
|
|
51
|
+
haiku_rag-0.11.0.dist-info/METADATA,sha256=wT7Q8ZsLKwHpDoRTdT9Aa5gPTMaSRH-9bbK_tBCPl-8,6542
|
|
52
|
+
haiku_rag-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
53
|
+
haiku_rag-0.11.0.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
|
|
54
|
+
haiku_rag-0.11.0.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
|
|
55
|
+
haiku_rag-0.11.0.dist-info/RECORD,,
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
|
|
3
|
-
from pydantic_ai import Agent
|
|
4
|
-
from pydantic_graph import BaseNode, GraphRunContext
|
|
5
|
-
|
|
6
|
-
from haiku.rag.research.common import format_context_for_prompt, get_model, log
|
|
7
|
-
from haiku.rag.research.dependencies import (
|
|
8
|
-
ResearchDependencies,
|
|
9
|
-
)
|
|
10
|
-
from haiku.rag.research.models import EvaluationResult, ResearchReport
|
|
11
|
-
from haiku.rag.research.nodes.synthesize import SynthesizeNode
|
|
12
|
-
from haiku.rag.research.prompts import EVALUATION_AGENT_PROMPT
|
|
13
|
-
from haiku.rag.research.state import ResearchDeps, ResearchState
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class EvaluateNode(BaseNode[ResearchState, ResearchDeps, ResearchReport]):
|
|
18
|
-
provider: str
|
|
19
|
-
model: str
|
|
20
|
-
|
|
21
|
-
async def run(
|
|
22
|
-
self, ctx: GraphRunContext[ResearchState, ResearchDeps]
|
|
23
|
-
) -> BaseNode[ResearchState, ResearchDeps, ResearchReport]:
|
|
24
|
-
state = ctx.state
|
|
25
|
-
deps = ctx.deps
|
|
26
|
-
|
|
27
|
-
log(
|
|
28
|
-
deps.console,
|
|
29
|
-
"\n[bold cyan]📊 Analyzing and evaluating research progress...[/bold cyan]",
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
agent = Agent(
|
|
33
|
-
model=get_model(self.provider, self.model),
|
|
34
|
-
output_type=EvaluationResult,
|
|
35
|
-
instructions=EVALUATION_AGENT_PROMPT,
|
|
36
|
-
retries=3,
|
|
37
|
-
deps_type=ResearchDependencies,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
context_xml = format_context_for_prompt(state.context)
|
|
41
|
-
prompt = (
|
|
42
|
-
"Analyze gathered information and evaluate completeness for the original question.\n\n"
|
|
43
|
-
f"{context_xml}"
|
|
44
|
-
)
|
|
45
|
-
agent_deps = ResearchDependencies(
|
|
46
|
-
client=deps.client, context=state.context, console=deps.console
|
|
47
|
-
)
|
|
48
|
-
eval_result = await agent.run(prompt, deps=agent_deps)
|
|
49
|
-
output = eval_result.output
|
|
50
|
-
|
|
51
|
-
for insight in output.key_insights:
|
|
52
|
-
state.context.add_insight(insight)
|
|
53
|
-
for new_q in output.new_questions:
|
|
54
|
-
if new_q not in state.sub_questions:
|
|
55
|
-
state.sub_questions.append(new_q)
|
|
56
|
-
|
|
57
|
-
state.last_eval = output
|
|
58
|
-
state.iterations += 1
|
|
59
|
-
|
|
60
|
-
if output.key_insights:
|
|
61
|
-
log(deps.console, " [bold]Key insights:[/bold]")
|
|
62
|
-
for ins in output.key_insights:
|
|
63
|
-
log(deps.console, f" • {ins}")
|
|
64
|
-
log(
|
|
65
|
-
deps.console,
|
|
66
|
-
f" Confidence: [yellow]{output.confidence_score:.1%}[/yellow]",
|
|
67
|
-
)
|
|
68
|
-
status = "[green]Yes[/green]" if output.is_sufficient else "[red]No[/red]"
|
|
69
|
-
log(deps.console, f" Sufficient: {status}")
|
|
70
|
-
|
|
71
|
-
from haiku.rag.research.nodes.search import SearchDispatchNode
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
output.is_sufficient
|
|
75
|
-
and output.confidence_score >= state.confidence_threshold
|
|
76
|
-
) or state.iterations >= state.max_iterations:
|
|
77
|
-
log(deps.console, "\n[bold green]✅ Stopping research.[/bold green]")
|
|
78
|
-
return SynthesizeNode(self.provider, self.model)
|
|
79
|
-
|
|
80
|
-
return SearchDispatchNode(self.provider, self.model)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|