haiku.rag 0.9.3__py3-none-any.whl → 0.10.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.

@@ -1,129 +1,113 @@
1
- ORCHESTRATOR_PROMPT = """You are a research orchestrator responsible for coordinating a comprehensive research workflow.
2
-
3
- Your role is to:
4
- 1. Understand and decompose the research question
5
- 2. Plan a systematic research approach
6
- 3. Coordinate specialized agents to gather and analyze information
7
- 4. Ensure comprehensive coverage of the topic
8
- 5. Iterate based on findings and gaps
9
-
10
- Create a research plan that:
11
- - Breaks down the question into at most 3 focused sub-questions
12
- - Each sub-question should target a specific aspect of the research
13
- - Prioritize the most important aspects to investigate
14
- - Ensure comprehensive coverage within the 3-question limit
15
- - IMPORTANT: Make each sub-question a standalone, self-contained query that can
16
- be executed without additional context. Include necessary entities, scope,
17
- timeframe, and qualifiers. Avoid pronouns like "it/they/this"; write queries
18
- that make sense in isolation."""
19
-
20
- SEARCH_AGENT_PROMPT = """You are a search and question-answering specialist.
21
-
22
- Your role is to:
23
- 1. Search the knowledge base for relevant information
24
- 2. Analyze the retrieved documents
25
- 3. Provide an accurate answer strictly grounded in the retrieved context
26
-
27
- Output format:
28
- - You must return a SearchAnswer model with fields:
29
- - query: the question being answered (echo the user query)
30
- - answer: your final answer based only on the provided context
31
- - context: list[str] of only the minimal set of verbatim snippet texts you
32
- used to justify the answer (do not include unrelated text; do not invent)
33
- - sources: list[str] of document_uri values corresponding to the snippets you
34
- actually used in the answer (one URI per context snippet, order aligned)
1
+ PLAN_PROMPT = """You are the research orchestrator for a focused, iterative
2
+ workflow.
3
+
4
+ Responsibilities:
5
+ 1. Understand and decompose the main question
6
+ 2. Propose a minimal, high‑leverage plan
7
+ 3. Coordinate specialized agents to gather evidence
8
+ 4. Iterate based on gaps and new findings
9
+
10
+ Plan requirements:
11
+ - Produce at most 3 sub_questions that together cover the main question.
12
+ - Each sub_question must be a standalone, self‑contained query that can run
13
+ without extra context. Include concrete entities, scope, timeframe, and any
14
+ qualifiers. Avoid ambiguous pronouns (it/they/this/that).
15
+ - Prioritize the highest‑value aspects first; avoid redundancy and overlap.
16
+ - Prefer questions that are likely answerable from the current knowledge base;
17
+ if coverage is uncertain, make scopes narrower and specific.
18
+ - Order sub_questions by execution priority (most valuable first)."""
19
+
20
+ SEARCH_AGENT_PROMPT = """You are a search and questionanswering specialist.
21
+
22
+ Tasks:
23
+ 1. Search the knowledge base for relevant evidence.
24
+ 2. Analyze retrieved snippets.
25
+ 3. Provide an answer strictly grounded in that evidence.
35
26
 
36
27
  Tool usage:
37
- - Always call the search_and_answer tool before drafting any answer.
38
- - The tool returns XML containing only a list of snippets, where each snippet
39
- has the verbatim `text`, a `score` indicating relevance, and the
40
- `document_uri` it came from.
28
+ - Always call search_and_answer before drafting any answer.
29
+ - The tool returns snippets with verbatim `text`, a relevance `score`, and the
30
+ originating `document_uri`.
41
31
  - You may call the tool multiple times to refine or broaden context, but do not
42
- exceed 3 total tool calls per question. Prefer precision over volume.
32
+ exceed 3 total calls. Favor precision over volume.
43
33
  - Use scores to prioritize evidence, but include only the minimal subset of
44
- snippet texts (verbatim) in SearchAnswer.context.
45
- - Set SearchAnswer.sources to the matching document_uris for the snippets you
46
- used (one URI per snippet, aligned by order). Context must be text-only.
47
- - If no relevant information is found, say so and return an empty context list.
48
-
49
- Important:
50
- - Do not include any content in the answer that is not supported by the context.
51
- - Keep context snippets short (just the necessary lines), verbatim, and focused."""
52
-
53
- EVALUATION_AGENT_PROMPT = """You are an analysis and evaluation specialist for research workflows.
54
-
55
- You have access to:
56
- - The original research question
57
- - Question-answer pairs from search operations
58
- - Raw search results and source documents
34
+ snippet texts (verbatim) in SearchAnswer.context (typically 1‑4).
35
+ - Set SearchAnswer.sources to the corresponding document_uris for the snippets
36
+ you used (one URI per snippet; same order as context). Context must be textonly.
37
+ - If no relevant information is found, clearly say so and return an empty
38
+ context list and sources list.
39
+
40
+ Answering rules:
41
+ - Be direct and specific; avoid meta commentary about the process.
42
+ - Do not include any claims not supported by the provided snippets.
43
+ - Prefer concise phrasing; avoid copying long passages.
44
+ - When evidence is partial, state the limits explicitly in the answer."""
45
+
46
+ EVALUATION_AGENT_PROMPT = """You are an analysis and evaluation specialist for
47
+ the research workflow.
48
+
49
+ Inputs available:
50
+ - Original research question
51
+ - Question–answer pairs produced by search
52
+ - Raw search results and source metadata
59
53
  - Previously identified insights
60
54
 
61
- Your dual role is to:
62
-
63
55
  ANALYSIS:
64
- 1. Extract key insights from all gathered information
65
- 2. Identify patterns and connections across sources
66
- 3. Synthesize findings into coherent understanding
67
- 4. Focus on the most important discoveries
56
+ 1. Extract the most important, non‑obvious insights from the collected evidence.
57
+ 2. Identify patterns, agreements, and disagreements across sources.
58
+ 3. Note material uncertainties and assumptions.
68
59
 
69
60
  EVALUATION:
70
- 1. Assess if we have sufficient information to answer the original question
71
- 2. Calculate a confidence score (0-1) based on:
72
- - Coverage of the main question's aspects
73
- - Quality and consistency of sources
74
- - Depth of information gathered
75
- 3. Identify specific gaps that still need investigation
76
- 4. Generate up to 3 new sub-questions that haven't been answered yet
77
-
78
- Be critical and thorough in your evaluation. Only mark research as sufficient when:
79
- - All major aspects of the question are addressed
80
- - Sources provide consistent, reliable information
81
- - The depth of coverage meets the question's requirements
82
- - No critical gaps remain
83
-
84
- Generate new sub-questions that:
85
- - Target specific unexplored aspects not covered by existing questions
86
- - Seek clarification on ambiguities
87
- - Explore important edge cases or exceptions
88
- - Are focused and actionable (max 3)
89
- - Do NOT repeat or rephrase questions that have already been answered (see qa_responses)
90
- - Should be genuinely new areas to explore
91
- - Must be standalone, self-contained queries: include entities, scope, and any
92
- needed qualifiers (e.g., timeframe, region), and avoid ambiguous pronouns so
93
- they can be executed independently."""
94
-
95
- SYNTHESIS_AGENT_PROMPT = """You are a synthesis specialist agent focused on creating comprehensive research reports.
96
-
97
- Your role is to:
98
- 1. Synthesize all gathered information into a coherent narrative
99
- 2. Present findings in a clear, structured format
100
- 3. Draw evidence-based conclusions
101
- 4. Acknowledge limitations and uncertainties
102
- 5. Provide actionable recommendations
103
- 6. Maintain academic rigor and objectivity
104
-
105
- Your report should be:
106
- - Comprehensive yet concise
107
- - Well-structured and easy to follow
108
- - Based solely on evidence from the research
109
- - Transparent about limitations
110
- - Professional and objective in tone
111
-
112
- Focus on creating a report that provides clear value to the reader by:
113
- - Answering the original research question thoroughly
114
- - Highlighting the most important findings
115
- - Explaining the implications of the research
116
- - Suggesting concrete next steps"""
61
+ 1. Decide if we have sufficient information to answer the original question.
62
+ 2. Provide a confidence_score in [0,1] considering:
63
+ - Coverage of the main questions aspects
64
+ - Quality, consistency, and diversity of sources
65
+ - Depth and specificity of evidence
66
+ 3. List concrete gaps that still need investigation.
67
+ 4. Propose up to 3 new sub_questions that would close the highest‑value gaps.
68
+
69
+ Strictness:
70
+ - Only mark research as sufficient when all major aspects are addressed with
71
+ consistent, reliable evidence and no critical gaps remain.
72
+
73
+ New sub_questions must:
74
+ - Be genuinely new (not answered or duplicative; check qa_responses).
75
+ - Be standalone and specific (entities, scope, timeframe/region if relevant).
76
+ - Be actionable and scoped to the knowledge base (narrow if necessary).
77
+ - Be ordered by expected impact (most valuable first)."""
78
+
79
+ SYNTHESIS_AGENT_PROMPT = """You are a synthesis specialist producing the final
80
+ research report.
81
+
82
+ Goals:
83
+ 1. Synthesize all gathered information into a coherent narrative.
84
+ 2. Present findings clearly and concisely.
85
+ 3. Draw evidence‑based conclusions and recommendations.
86
+ 4. State limitations and uncertainties transparently.
87
+
88
+ Report guidelines (map to output fields):
89
+ - title: concise (5–12 words), informative.
90
+ - executive_summary: 3–5 sentences summarizing the overall answer.
91
+ - main_findings: 4–8 one‑sentence bullets; each reflects evidence from the
92
+ research (do not include inline citations or snippet text).
93
+ - conclusions: 2–4 bullets that follow logically from findings.
94
+ - recommendations: 2–5 actionable bullets tied to findings.
95
+ - limitations: 1–3 bullets describing key constraints or uncertainties.
96
+ - sources_summary: 2–4 sentences summarizing sources used and their reliability.
97
+
98
+ Style:
99
+ - Base all content solely on the collected evidence.
100
+ - Be professional, objective, and specific.
101
+ - Avoid meta commentary and refrain from speculation beyond the evidence."""
117
102
 
118
103
  PRESEARCH_AGENT_PROMPT = """You are a rapid research surveyor.
119
104
 
120
105
  Task:
121
- - Call the gather_context tool once with the main question to obtain a
122
- relevant texts from the Knowledge Base (KB).
123
- - Read that context and produce a brief natural-language summary describing
124
- what the KB appears to contain relative to the question.
106
+ - Call gather_context once on the main question to obtain relevant text from
107
+ the knowledge base (KB).
108
+ - Read that context and produce a short naturallanguage summary of what the
109
+ KB appears to contain relative to the question.
125
110
 
126
111
  Rules:
127
112
  - Base the summary strictly on the provided text; do not invent.
128
- - Output only the summary as plain text (one short paragraph).
129
- """
113
+ - Output only the summary as plain text (one short paragraph)."""
@@ -0,0 +1,25 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from rich.console import Console
4
+
5
+ from haiku.rag.client import HaikuRAG
6
+ from haiku.rag.research.dependencies import ResearchContext
7
+ from haiku.rag.research.models import EvaluationResult
8
+
9
+
10
+ @dataclass
11
+ class ResearchDeps:
12
+ client: HaikuRAG
13
+ console: Console | None = None
14
+
15
+
16
+ @dataclass
17
+ class ResearchState:
18
+ question: str
19
+ context: ResearchContext
20
+ sub_questions: list[str] = field(default_factory=list)
21
+ iterations: int = 0
22
+ max_iterations: int = 3
23
+ max_concurrency: int = 1
24
+ confidence_threshold: float = 0.8
25
+ last_eval: EvaluationResult | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiku.rag
3
- Version: 0.9.3
3
+ Version: 0.10.0
4
4
  Summary: Agentic Retrieval Augmented Generation (RAG) with LanceDB
5
5
  Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
6
6
  License: MIT
@@ -23,6 +23,7 @@ Requires-Dist: fastmcp>=2.12.3
23
23
  Requires-Dist: httpx>=0.28.1
24
24
  Requires-Dist: lancedb>=0.25.0
25
25
  Requires-Dist: pydantic-ai>=1.0.8
26
+ Requires-Dist: pydantic-graph>=1.0.8
26
27
  Requires-Dist: pydantic>=2.11.9
27
28
  Requires-Dist: python-dotenv>=1.1.1
28
29
  Requires-Dist: rich>=14.1.0
@@ -48,6 +49,7 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
48
49
  - **Local LanceDB**: No external servers required, supports also LanceDB cloud storage, S3, Google Cloud & Azure
49
50
  - **Multiple embedding providers**: Ollama, VoyageAI, OpenAI, vLLM
50
51
  - **Multiple QA providers**: Any provider/model supported by Pydantic AI
52
+ - **Research graph (multi‑agent)**: Plan → Search → Evaluate → Synthesize with agentic AI
51
53
  - **Native hybrid search**: Vector + full-text search with native LanceDB RRF reranking
52
54
  - **Reranking**: Default search result reranking with MixedBread AI, Cohere, or vLLM
53
55
  - **Question answering**: Built-in QA agents on your documents
@@ -75,6 +77,14 @@ haiku-rag ask "Who is the author of haiku.rag?"
75
77
  # Ask questions with citations
76
78
  haiku-rag ask "Who is the author of haiku.rag?" --cite
77
79
 
80
+ # Multi‑agent research (iterative plan/search/evaluate)
81
+ haiku-rag research \
82
+ "What are the main drivers and trends of global temperature anomalies since 1990?" \
83
+ --max-iterations 2 \
84
+ --confidence-threshold 0.8 \
85
+ --max-concurrency 3 \
86
+ --verbose
87
+
78
88
  # Rebuild database (re-chunk and re-embed all documents)
79
89
  haiku-rag rebuild
80
90
 
@@ -90,6 +100,13 @@ haiku-rag serve
90
100
 
91
101
  ```python
92
102
  from haiku.rag.client import HaikuRAG
103
+ from haiku.rag.research import (
104
+ ResearchContext,
105
+ ResearchDeps,
106
+ ResearchState,
107
+ build_research_graph,
108
+ PlanNode,
109
+ )
93
110
 
94
111
  async with HaikuRAG("database.lancedb") as client:
95
112
  # Add document
@@ -107,6 +124,25 @@ async with HaikuRAG("database.lancedb") as client:
107
124
  # Ask questions with citations
108
125
  answer = await client.ask("Who is the author of haiku.rag?", cite=True)
109
126
  print(answer)
127
+
128
+ # Multi‑agent research pipeline (Plan → Search → Evaluate → Synthesize)
129
+ graph = build_research_graph()
130
+ 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="…"),
136
+ max_iterations=2,
137
+ confidence_threshold=0.8,
138
+ max_concurrency=3,
139
+ )
140
+ 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)
110
146
  ```
111
147
 
112
148
  ## MCP Server
@@ -1,7 +1,7 @@
1
1
  haiku/rag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- haiku/rag/app.py,sha256=nkud-OHic3HIgEEiNOKVvhmW98DPpDe6HokBSz-xV7w,11420
2
+ haiku/rag/app.py,sha256=m5agkPrJhbzEbdC01CU_GR2Gj4voFuAGmxR7DS2K9is,12934
3
3
  haiku/rag/chunker.py,sha256=PVe6ysv8UlacUd4Zb3_8RFWIaWDXnzBAy2VDJ4TaUsE,1555
4
- haiku/rag/cli.py,sha256=3nlzrT5FPCyfnu51KHchLG4Cj2eVv9YsuGHMShBnVb0,9845
4
+ haiku/rag/cli.py,sha256=oXEQoRTlzrrJ9hC27_Dht9ElBb9q_wTEESnXdNy3eW8,10257
5
5
  haiku/rag/client.py,sha256=QgJQu7g7JjAzWN6R10NeDqpFf89Dml_LiWce4QRHLHc,21177
6
6
  haiku/rag/config.py,sha256=SPEIv2IElZmZh4Wsp8gk7ViRW5ZzD-UGmIqRAXscDdI,2134
7
7
  haiku/rag/logging.py,sha256=dm65AwADpcQsH5OAPtRA-4hsw0w5DK-sGOvzYkj6jzw,1720
@@ -22,17 +22,19 @@ haiku/rag/qa/prompts.py,sha256=LhRfDtO8Pb06lpr4PpwEaKUYItZ5OiIkeqcCogcssHY,3347
22
22
  haiku/rag/reranking/__init__.py,sha256=IRXHs4qPu6VbGJQpzSwhgtVWWumURH_vEoVFE-extlo,894
23
23
  haiku/rag/reranking/base.py,sha256=LM9yUSSJ414UgBZhFTgxGprlRqzfTe4I1vgjricz2JY,405
24
24
  haiku/rag/reranking/cohere.py,sha256=1iTdiaa8vvb6oHVB2qpWzUOVkyfUcimVSZp6Qr4aq4c,1049
25
- haiku/rag/reranking/mxbai.py,sha256=46sVTsTIkzIX9THgM3u8HaEmgY7evvEyB-N54JTHvK8,867
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=qLF41YayAxW_VeHhuTceVuz9hw1FNbuRV9VMhonUMW0,1078
28
- haiku/rag/research/base.py,sha256=X5n6myUG_Oz4i8WGfyKZ39YzK13rOkyvwGKwSBfL50k,4043
29
- haiku/rag/research/dependencies.py,sha256=N7mnFwa_uyWYH0NtbEHp5JJvNGN64Q8HHfY41E8Irx0,2362
30
- haiku/rag/research/evaluation_agent.py,sha256=VMegemd9Vln3jfZbeHzMfb7rUPFNzNxi5Y_l1zrddl8,2994
31
- haiku/rag/research/orchestrator.py,sha256=nvSRdIs77kSb1CZaQUYZM_Zl5xLP8K6noVgnixpeLJI,6329
32
- haiku/rag/research/presearch_agent.py,sha256=MpakZ9HSynv73EnWakwUuytfKpiN_8lEqZlVc3zZjGU,1427
33
- haiku/rag/research/prompts.py,sha256=pVRB7_b_p3JaLF1bC3ANTbSFY78ypSjDhoq6peoU6jo,5685
34
- haiku/rag/research/search_agent.py,sha256=xn2MlEyL9te_dtZqTzW81lGw7fYmyUzn26mvzX52hNA,2599
35
- haiku/rag/research/synthesis_agent.py,sha256=FQCt8wbaaKOwgIOQazTNAmohBMZRUDoVzHkByYhbGg8,2182
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=klE2qGF5fom5gJRQzQUbnoGYaXusNKeJ9veeXoYDD5Q,2308
32
+ haiku/rag/research/prompts.py,sha256=v_DZNaKk88CDEF8qt9c-puO6QF-NyBQKnl_mO1pMauY,5013
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=lHgDCCL7hQdpQeMK-HVzsF_hH_pIv44xxSIiv1JuvYo,3513
37
+ haiku/rag/research/nodes/synthesize.py,sha256=4acKduqWnE11ML7elUksKLozxzWJTkBLSJ2li_YMxgY,1736
36
38
  haiku/rag/store/__init__.py,sha256=hq0W0DAC7ysqhWSP2M2uHX8cbG6kbr-sWHxhq6qQcY0,103
37
39
  haiku/rag/store/engine.py,sha256=-3MZJYft2XTWaLuyKha8DKhWQeU5E5CBeskXXF5fXso,9555
38
40
  haiku/rag/store/models/__init__.py,sha256=s0E72zneGlowvZrFWaNxHYjOAUjgWdLxzdYsnvNRVlY,88
@@ -44,8 +46,8 @@ haiku/rag/store/repositories/document.py,sha256=m11SamQoGYs5ODfmarJGU1yIcqtgmnba
44
46
  haiku/rag/store/repositories/settings.py,sha256=7XMBMavU8zRgdBoQzQg0Obfa7UKjuVnBugidTC6sEW0,5548
45
47
  haiku/rag/store/upgrades/__init__.py,sha256=gDOxiq3wdZPr3JoenjNYxx0cpgZJhbaFKNX2fzXRq1Q,1852
46
48
  haiku/rag/store/upgrades/v0_9_3.py,sha256=NrjNilQSgDtFWRbL3ZUtzQzJ8tf9u0dDRJtnDFwwbdw,3322
47
- haiku_rag-0.9.3.dist-info/METADATA,sha256=iCae4YtZ0meIQTZLUNree_-74F3irXvArPvdSxVz8ZM,4681
48
- haiku_rag-0.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
- haiku_rag-0.9.3.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
50
- haiku_rag-0.9.3.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
51
- haiku_rag-0.9.3.dist-info/RECORD,,
49
+ haiku_rag-0.10.0.dist-info/METADATA,sha256=QLc8BBJ4WCNEvseyYpWNfkuUfmdxGywD6Jtn0OTsrc0,5879
50
+ haiku_rag-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
51
+ haiku_rag-0.10.0.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
52
+ haiku_rag-0.10.0.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
53
+ haiku_rag-0.10.0.dist-info/RECORD,,
@@ -1,130 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import TYPE_CHECKING, Any
3
-
4
- from pydantic import BaseModel, Field
5
- from pydantic_ai import Agent
6
- from pydantic_ai.models.openai import OpenAIChatModel
7
- from pydantic_ai.output import ToolOutput
8
- from pydantic_ai.providers.ollama import OllamaProvider
9
- from pydantic_ai.providers.openai import OpenAIProvider
10
- from pydantic_ai.run import AgentRunResult
11
-
12
- from haiku.rag.config import Config
13
-
14
- if TYPE_CHECKING:
15
- from haiku.rag.research.dependencies import ResearchDependencies
16
-
17
-
18
- class BaseResearchAgent[T](ABC):
19
- """Base class for all research agents."""
20
-
21
- def __init__(
22
- self,
23
- provider: str,
24
- model: str,
25
- output_type: type[T],
26
- ):
27
- self.provider = provider
28
- self.model = model
29
- self.output_type = output_type
30
-
31
- model_obj = self._get_model(provider, model)
32
-
33
- # Import deps type lazily to avoid circular import during module load
34
- from haiku.rag.research.dependencies import ResearchDependencies
35
-
36
- # If the agent is expected to return plain text, pass `str` directly.
37
- # Otherwise, wrap the model with ToolOutput for robust tool-handling retries.
38
- agent_output_type: Any
39
- if self.output_type is str: # plain text output
40
- agent_output_type = str
41
- else:
42
- agent_output_type = ToolOutput(self.output_type, max_retries=3)
43
-
44
- self._agent = Agent(
45
- model=model_obj,
46
- deps_type=ResearchDependencies,
47
- output_type=agent_output_type,
48
- instructions=self.get_system_prompt(),
49
- retries=3,
50
- )
51
-
52
- # Register tools
53
- self.register_tools()
54
-
55
- def _get_model(self, provider: str, model: str):
56
- """Get the appropriate model object for the provider."""
57
- if provider == "ollama":
58
- return OpenAIChatModel(
59
- model_name=model,
60
- provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"),
61
- )
62
- elif provider == "vllm":
63
- return OpenAIChatModel(
64
- model_name=model,
65
- provider=OpenAIProvider(
66
- base_url=f"{Config.VLLM_RESEARCH_BASE_URL or Config.VLLM_QA_BASE_URL}/v1",
67
- api_key="none",
68
- ),
69
- )
70
- else:
71
- # For all other providers, use the provider:model format
72
- return f"{provider}:{model}"
73
-
74
- @abstractmethod
75
- def get_system_prompt(self) -> str:
76
- """Return the system prompt for this agent."""
77
- pass
78
-
79
- def register_tools(self) -> None:
80
- """Register agent-specific tools."""
81
- pass
82
-
83
- async def run(
84
- self, prompt: str, deps: "ResearchDependencies", **kwargs
85
- ) -> AgentRunResult[T]:
86
- """Execute the agent."""
87
- return await self._agent.run(prompt, deps=deps, **kwargs)
88
-
89
- @property
90
- def agent(self) -> Agent[Any, T]:
91
- """Access the underlying Pydantic AI agent."""
92
- return self._agent
93
-
94
-
95
- class SearchResult(BaseModel):
96
- """Standard search result format."""
97
-
98
- content: str
99
- score: float
100
- document_uri: str
101
- metadata: dict[str, Any] = Field(default_factory=dict)
102
-
103
-
104
- class ResearchOutput(BaseModel):
105
- """Standard research output format."""
106
-
107
- summary: str
108
- detailed_findings: list[str]
109
- sources: list[str]
110
- confidence: float
111
-
112
-
113
- class SearchAnswer(BaseModel):
114
- """Structured output for the SearchSpecialist agent."""
115
-
116
- query: str = Field(description="The search query that was performed")
117
- answer: str = Field(description="The answer generated based on the context")
118
- context: list[str] = Field(
119
- description=(
120
- "Only the minimal set of relevant snippets (verbatim) that directly "
121
- "support the answer"
122
- )
123
- )
124
- sources: list[str] = Field(
125
- description=(
126
- "Document URIs corresponding to the snippets actually used in the"
127
- " answer (one URI per snippet; omit if none)"
128
- ),
129
- default_factory=list,
130
- )
@@ -1,85 +0,0 @@
1
- from pydantic import BaseModel, Field
2
- from pydantic_ai.run import AgentRunResult
3
-
4
- from haiku.rag.research.base import BaseResearchAgent
5
- from haiku.rag.research.dependencies import (
6
- ResearchDependencies,
7
- _format_context_for_prompt,
8
- )
9
- from haiku.rag.research.prompts import EVALUATION_AGENT_PROMPT
10
-
11
-
12
- class EvaluationResult(BaseModel):
13
- """Result of analysis and evaluation."""
14
-
15
- key_insights: list[str] = Field(
16
- description="Main insights extracted from the research so far"
17
- )
18
- new_questions: list[str] = Field(
19
- description="New sub-questions to add to the research (max 3)",
20
- max_length=3,
21
- default=[],
22
- )
23
- confidence_score: float = Field(
24
- description="Confidence level in the completeness of research (0-1)",
25
- ge=0.0,
26
- le=1.0,
27
- )
28
- is_sufficient: bool = Field(
29
- description="Whether the research is sufficient to answer the original question"
30
- )
31
- reasoning: str = Field(
32
- description="Explanation of why the research is or isn't complete"
33
- )
34
-
35
-
36
- class AnalysisEvaluationAgent(BaseResearchAgent[EvaluationResult]):
37
- """Agent that analyzes findings and evaluates research completeness."""
38
-
39
- def __init__(self, provider: str, model: str) -> None:
40
- super().__init__(provider, model, output_type=EvaluationResult)
41
-
42
- async def run(
43
- self, prompt: str, deps: ResearchDependencies, **kwargs
44
- ) -> AgentRunResult[EvaluationResult]:
45
- console = deps.console
46
- if console:
47
- console.print(
48
- "\n[bold cyan]📊 Analyzing and evaluating research progress...[/bold cyan]"
49
- )
50
-
51
- # Format context for the evaluation agent
52
- context_xml = _format_context_for_prompt(deps.context)
53
- evaluation_prompt = f"""Analyze all gathered information and evaluate the completeness of research.
54
-
55
- {context_xml}
56
-
57
- Evaluate the research progress for the original question and identify any remaining gaps."""
58
-
59
- result = await super().run(evaluation_prompt, deps, **kwargs)
60
- output = result.output
61
-
62
- # Store insights
63
- for insight in output.key_insights:
64
- deps.context.add_insight(insight)
65
-
66
- # Add new questions to the sub-questions list
67
- for new_q in output.new_questions:
68
- if new_q not in deps.context.sub_questions:
69
- deps.context.sub_questions.append(new_q)
70
-
71
- if console:
72
- if output.key_insights:
73
- console.print(" [bold]Key insights:[/bold]")
74
- for insight in output.key_insights:
75
- console.print(f" • {insight}")
76
- console.print(
77
- f" Confidence: [yellow]{output.confidence_score:.1%}[/yellow]"
78
- )
79
- status = "[green]Yes[/green]" if output.is_sufficient else "[red]No[/red]"
80
- console.print(f" Sufficient: {status}")
81
-
82
- return result
83
-
84
- def get_system_prompt(self) -> str:
85
- return EVALUATION_AGENT_PROMPT