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.

@@ -24,7 +24,8 @@ class SynthesizeNode(BaseNode[ResearchState, ResearchDeps, ResearchReport]):
24
24
  deps = ctx.deps
25
25
 
26
26
  log(
27
- deps.console,
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, context=state.context, console=deps.console
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.console, "[bold green]✅ Research complete![/bold green]")
54
+ log(deps, state, "[bold green]✅ Research complete![/bold green]")
51
55
  return End(result.output)
@@ -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
- EVALUATION_AGENT_PROMPT = """You are an analysis and evaluation specialist for
48
- the research workflow.
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 produced by search
53
- - Raw search results and source metadata
54
- - Previously identified insights
55
-
56
- ANALYSIS:
57
- 1. Extract the most important, non‑obvious insights from the collected evidence.
58
- 2. Identify patterns, agreements, and disagreements across sources.
59
- 3. Note material uncertainties and assumptions.
60
-
61
- EVALUATION:
62
- 1. Decide if we have sufficient information to answer the original question.
63
- 2. Provide a confidence_score in [0,1] considering:
64
- - Coverage of the main question’s aspects
65
- - Quality, consistency, and diversity of sources
66
- - Depth and specificity of evidence
67
- 3. List concrete gaps that still need investigation.
68
- 4. Propose up to 3 new sub_questions that would close the highest‑value gaps.
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 all major aspects are addressed with
72
- consistent, reliable evidence and no critical gaps remain.
73
-
74
- New sub_questions must:
75
- - Be genuinely new (not answered or duplicative; check qa_responses).
76
- - Be standalone and specific (entities, scope, timeframe/region if relevant).
77
- - Be actionable and scoped to the knowledge base (narrow if necessary).
78
- - Be ordered by expected impact (most valuable first)."""
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.
@@ -1,25 +1,32 @@
1
- from dataclasses import dataclass, field
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
@@ -1,4 +1,4 @@
1
1
  from .engine import Store
2
2
  from .models import Chunk, Document
3
3
 
4
- __all__ = ["Store", "Chunk", "Document"]
4
+ __all__ = ["Store", "Chunk", "Document"]
@@ -1,4 +1,4 @@
1
1
  from .chunk import Chunk
2
2
  from .document import Document
3
3
 
4
- __all__ = ["Chunk", "Document"]
4
+ __all__ = ["Chunk", "Document"]
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.10.1
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-src document.pdf
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
- PlanNode,
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
- question=(
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=3,
140
+ max_concurrency=2,
139
141
  )
140
142
  deps = ResearchDeps(client=client)
141
- start = PlanNode(provider=None, model=None)
142
- result = await graph.run(start, state=state, deps=deps)
143
- report = result.output
144
- print(report.title)
145
- print(report.executive_summary)
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=06nsdjrljPNqZew4gsLFIA0BSwv-CxPovJaAYSFzm-w,13265
2
+ haiku/rag/app.py,sha256=TRFwMP9mzLaM7EPc7dhsPODKZxCDkSSgPCnGAdj65VU,17929
3
3
  haiku/rag/chunker.py,sha256=PVe6ysv8UlacUd4Zb3_8RFWIaWDXnzBAy2VDJ4TaUsE,1555
4
- haiku/rag/cli.py,sha256=4aYUXPr54q-7U2-cySNpSnW1ntvD0Jck6oj8vvA6IoI,10830
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=M--KnSF3lxgKjxmokb4vuzGH-pV8eg0C_8e7jvPqW8Y,11058
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=hKH8bBBbAVYlLFBOAcErvX-4cuWIaPTbrAFeeLN1HdM,5062
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=t4JAmIXcKaWqvpFGX5yaehsNrfblskEMn-4mDmdKn9c,502
28
- haiku/rag/research/common.py,sha256=EUnsA6VZ3-WMweXESuUYezH1ALit8N38064bsZFqtBE,1688
29
- haiku/rag/research/dependencies.py,sha256=ZiSQdV6jHti4DuUp4WCaJL73TqYDr5vC8ppB34M2cNg,1639
30
- haiku/rag/research/graph.py,sha256=m3vDP1nPXWzfS7VeTQzmTOk-lFpoaTvKHvRIF2mbxvs,798
31
- haiku/rag/research/models.py,sha256=Q92oxBNq3qp3DyUzTim9YGDOBtGzXH25K_mmfLAA7Y8,2329
32
- haiku/rag/research/prompts.py,sha256=0_EMA5CS7O37QhKJM7OCDdrdgMcoF2DgehBHR4L7xmk,5103
33
- haiku/rag/research/state.py,sha256=vFwO8c2JmwwfkELE5Mwjt9Oat-bHn5tayf31MIG2SRs,623
34
- haiku/rag/research/nodes/evaluate.py,sha256=Cp2J-jXYZothiQV3zRZFaCsBLaUU0Tm_-ri-hlgQQII,2897
35
- haiku/rag/research/nodes/plan.py,sha256=9AkTls01Q3zTLKGgIgSCX9X4VYC8IWjEWii8A_f77YQ,2439
36
- haiku/rag/research/nodes/search.py,sha256=2ioc5Ba3ciq2zpFxgzoGkZOvVsJ1TBX9zseURLDJpBg,3591
37
- haiku/rag/research/nodes/synthesize.py,sha256=4acKduqWnE11ML7elUksKLozxzWJTkBLSJ2li_YMxgY,1736
38
- haiku/rag/store/__init__.py,sha256=hq0W0DAC7ysqhWSP2M2uHX8cbG6kbr-sWHxhq6qQcY0,103
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=s0E72zneGlowvZrFWaNxHYjOAUjgWdLxzdYsnvNRVlY,88
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.10.1.dist-info/METADATA,sha256=Bu1Nmz3AoD_EquvCvsbcJjGXmFsGDEwqnfaYIBgOLqQ,5879
51
- haiku_rag-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
- haiku_rag-0.10.1.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
53
- haiku_rag-0.10.1.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
54
- haiku_rag-0.10.1.dist-info/RECORD,,
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)