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.

Files changed (94) hide show
  1. haiku/rag/app.py +430 -72
  2. haiku/rag/chunkers/__init__.py +31 -0
  3. haiku/rag/chunkers/base.py +31 -0
  4. haiku/rag/chunkers/docling_local.py +164 -0
  5. haiku/rag/chunkers/docling_serve.py +179 -0
  6. haiku/rag/cli.py +207 -24
  7. haiku/rag/cli_chat.py +489 -0
  8. haiku/rag/client.py +1251 -266
  9. haiku/rag/config/__init__.py +16 -10
  10. haiku/rag/config/loader.py +5 -44
  11. haiku/rag/config/models.py +126 -17
  12. haiku/rag/converters/__init__.py +31 -0
  13. haiku/rag/converters/base.py +63 -0
  14. haiku/rag/converters/docling_local.py +193 -0
  15. haiku/rag/converters/docling_serve.py +229 -0
  16. haiku/rag/converters/text_utils.py +237 -0
  17. haiku/rag/embeddings/__init__.py +123 -24
  18. haiku/rag/embeddings/voyageai.py +175 -20
  19. haiku/rag/graph/__init__.py +0 -11
  20. haiku/rag/graph/agui/__init__.py +8 -2
  21. haiku/rag/graph/agui/cli_renderer.py +1 -1
  22. haiku/rag/graph/agui/emitter.py +219 -31
  23. haiku/rag/graph/agui/server.py +20 -62
  24. haiku/rag/graph/agui/stream.py +1 -2
  25. haiku/rag/graph/research/__init__.py +5 -2
  26. haiku/rag/graph/research/dependencies.py +12 -126
  27. haiku/rag/graph/research/graph.py +390 -135
  28. haiku/rag/graph/research/models.py +91 -112
  29. haiku/rag/graph/research/prompts.py +99 -91
  30. haiku/rag/graph/research/state.py +35 -27
  31. haiku/rag/inspector/__init__.py +8 -0
  32. haiku/rag/inspector/app.py +259 -0
  33. haiku/rag/inspector/widgets/__init__.py +6 -0
  34. haiku/rag/inspector/widgets/chunk_list.py +100 -0
  35. haiku/rag/inspector/widgets/context_modal.py +89 -0
  36. haiku/rag/inspector/widgets/detail_view.py +130 -0
  37. haiku/rag/inspector/widgets/document_list.py +75 -0
  38. haiku/rag/inspector/widgets/info_modal.py +209 -0
  39. haiku/rag/inspector/widgets/search_modal.py +183 -0
  40. haiku/rag/inspector/widgets/visual_modal.py +126 -0
  41. haiku/rag/mcp.py +106 -102
  42. haiku/rag/monitor.py +33 -9
  43. haiku/rag/providers/__init__.py +5 -0
  44. haiku/rag/providers/docling_serve.py +108 -0
  45. haiku/rag/qa/__init__.py +12 -10
  46. haiku/rag/qa/agent.py +43 -61
  47. haiku/rag/qa/prompts.py +35 -57
  48. haiku/rag/reranking/__init__.py +9 -6
  49. haiku/rag/reranking/base.py +1 -1
  50. haiku/rag/reranking/cohere.py +5 -4
  51. haiku/rag/reranking/mxbai.py +5 -2
  52. haiku/rag/reranking/vllm.py +3 -4
  53. haiku/rag/reranking/zeroentropy.py +6 -5
  54. haiku/rag/store/__init__.py +2 -1
  55. haiku/rag/store/engine.py +242 -42
  56. haiku/rag/store/exceptions.py +4 -0
  57. haiku/rag/store/models/__init__.py +8 -2
  58. haiku/rag/store/models/chunk.py +190 -0
  59. haiku/rag/store/models/document.py +46 -0
  60. haiku/rag/store/repositories/chunk.py +141 -121
  61. haiku/rag/store/repositories/document.py +25 -84
  62. haiku/rag/store/repositories/settings.py +11 -14
  63. haiku/rag/store/upgrades/__init__.py +19 -3
  64. haiku/rag/store/upgrades/v0_10_1.py +1 -1
  65. haiku/rag/store/upgrades/v0_19_6.py +65 -0
  66. haiku/rag/store/upgrades/v0_20_0.py +68 -0
  67. haiku/rag/store/upgrades/v0_23_1.py +100 -0
  68. haiku/rag/store/upgrades/v0_9_3.py +3 -3
  69. haiku/rag/utils.py +371 -146
  70. {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/METADATA +15 -12
  71. haiku_rag_slim-0.24.0.dist-info/RECORD +78 -0
  72. {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/WHEEL +1 -1
  73. haiku/rag/chunker.py +0 -65
  74. haiku/rag/embeddings/base.py +0 -25
  75. haiku/rag/embeddings/ollama.py +0 -28
  76. haiku/rag/embeddings/openai.py +0 -26
  77. haiku/rag/embeddings/vllm.py +0 -29
  78. haiku/rag/graph/agui/events.py +0 -254
  79. haiku/rag/graph/common/__init__.py +0 -5
  80. haiku/rag/graph/common/models.py +0 -42
  81. haiku/rag/graph/common/nodes.py +0 -265
  82. haiku/rag/graph/common/prompts.py +0 -46
  83. haiku/rag/graph/common/utils.py +0 -44
  84. haiku/rag/graph/deep_qa/__init__.py +0 -1
  85. haiku/rag/graph/deep_qa/dependencies.py +0 -27
  86. haiku/rag/graph/deep_qa/graph.py +0 -243
  87. haiku/rag/graph/deep_qa/models.py +0 -20
  88. haiku/rag/graph/deep_qa/prompts.py +0 -59
  89. haiku/rag/graph/deep_qa/state.py +0 -56
  90. haiku/rag/graph/research/common.py +0 -87
  91. haiku/rag/reader.py +0 -135
  92. haiku_rag_slim-0.16.0.dist-info/RECORD +0 -71
  93. {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/entry_points.txt +0 -0
  94. {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,149 +1,128 @@
1
- import uuid
2
- from enum import Enum
1
+ from typing import TYPE_CHECKING
3
2
 
4
3
  from pydantic import BaseModel, Field, field_validator
5
4
 
5
+ if TYPE_CHECKING:
6
+ from haiku.rag.store.models import SearchResult
6
7
 
7
- def _deduplicate_list(items: list[str]) -> list[str]:
8
- """Remove duplicates while preserving order."""
9
- return list(dict.fromkeys(items))
10
8
 
9
+ class ResearchPlan(BaseModel):
10
+ """A structured research plan with sub-questions to explore."""
11
11
 
12
- class InsightStatus(str, Enum):
13
- OPEN = "open"
14
- VALIDATED = "validated"
15
- TENTATIVE = "tentative"
16
-
17
-
18
- class GapSeverity(str, Enum):
19
- LOW = "low"
20
- MEDIUM = "medium"
21
- HIGH = "high"
22
-
23
-
24
- class TrackedRecord(BaseModel):
25
- """Base model for tracked entities with sources and metadata."""
26
-
27
- model_config = {"validate_assignment": True}
28
-
29
- id: str = Field(
30
- default_factory=lambda: str(uuid.uuid4())[:8],
31
- description="Unique identifier for the record",
32
- )
33
- supporting_sources: list[str] = Field(
34
- default_factory=list,
35
- description="Source identifiers backing this record",
36
- )
37
- notes: str | None = Field(
38
- default=None,
39
- description="Optional elaboration or caveats",
12
+ sub_questions: list[str] = Field(
13
+ ...,
14
+ description="Specific questions to research, phrased as complete questions",
40
15
  )
41
16
 
42
- @field_validator("supporting_sources", mode="before")
17
+ @field_validator("sub_questions")
43
18
  @classmethod
44
- def deduplicate_sources(cls, v: list[str]) -> list[str]:
45
- """Ensure supporting_sources has no duplicates."""
46
- return _deduplicate_list(v) if v else []
19
+ def validate_sub_questions(cls, v: list[str]) -> list[str]:
20
+ if len(v) < 1:
21
+ raise ValueError("Must have at least 1 sub-question")
22
+ if len(v) > 12:
23
+ raise ValueError("Cannot have more than 12 sub-questions")
24
+ return v
47
25
 
48
26
 
49
- class InsightRecord(TrackedRecord):
50
- """Structured insight with provenance and lifecycle metadata."""
27
+ class Citation(BaseModel):
28
+ """Resolved citation with full metadata for display/visual grounding."""
51
29
 
52
- summary: str = Field(description="Concise description of the insight")
53
- status: InsightStatus = Field(
54
- default=InsightStatus.OPEN,
55
- description="Lifecycle status for the insight",
56
- )
57
- originating_questions: list[str] = Field(
58
- default_factory=list,
59
- description="Research sub-questions that produced this insight",
60
- )
30
+ document_id: str
31
+ chunk_id: str
32
+ document_uri: str
33
+ document_title: str | None = None
34
+ page_numbers: list[int] = Field(default_factory=list)
35
+ headings: list[str] | None = None
36
+ content: str
61
37
 
62
- @field_validator("originating_questions", mode="before")
63
- @classmethod
64
- def deduplicate_questions(cls, v: list[str]) -> list[str]:
65
- """Ensure originating_questions has no duplicates."""
66
- return _deduplicate_list(v) if v else []
67
38
 
39
+ class RawSearchAnswer(BaseModel):
40
+ """Answer to a search query with chunk references."""
68
41
 
69
- class GapRecord(TrackedRecord):
70
- """Structured representation of an identified research gap."""
71
-
72
- description: str = Field(description="Concrete statement of what is missing")
73
- severity: GapSeverity = Field(
74
- default=GapSeverity.MEDIUM,
75
- description="Severity of the gap for answering the main question",
76
- )
77
- blocking: bool = Field(
78
- default=True,
79
- description="Whether this gap blocks a confident answer",
80
- )
81
- resolved: bool = Field(
82
- default=False,
83
- description="Flag indicating if the gap has been resolved",
84
- )
85
- resolved_by: list[str] = Field(
42
+ query: str = Field(..., description="The question that was answered")
43
+ answer: str = Field(..., description="The answer to the question")
44
+ cited_chunks: list[str] = Field(
86
45
  default_factory=list,
87
- description="Insight IDs or notes explaining how the gap was closed",
46
+ description="IDs of chunks used to form the answer",
47
+ )
48
+ confidence: float = Field(
49
+ default=1.0,
50
+ description="Confidence score for this answer (0-1)",
51
+ ge=0.0,
52
+ le=1.0,
88
53
  )
89
-
90
- @field_validator("resolved_by", mode="before")
91
- @classmethod
92
- def deduplicate_resolved_by(cls, v: list[str]) -> list[str]:
93
- """Ensure resolved_by has no duplicates."""
94
- return _deduplicate_list(v) if v else []
95
54
 
96
55
 
97
- class InsightAnalysis(BaseModel):
98
- """Output of the insight aggregation agent."""
56
+ class SearchAnswer(RawSearchAnswer):
57
+ """Answer to a search query with resolved citations."""
99
58
 
100
- highlights: list[InsightRecord] = Field(
101
- default_factory=list,
102
- description="New or updated insights discovered this iteration",
103
- )
104
- gap_assessments: list[GapRecord] = Field(
105
- default_factory=list,
106
- description="New or updated gap records based on current evidence",
107
- )
108
- resolved_gaps: list[str] = Field(
59
+ citations: list[Citation] = Field(
109
60
  default_factory=list,
110
- description="Gap identifiers or descriptions considered resolved",
111
- )
112
- new_questions: list[str] = Field(
113
- default_factory=list,
114
- max_length=3,
115
- description="Up to three follow-up sub-questions to pursue next",
116
- )
117
- commentary: str = Field(
118
- description="Short narrative summary of the incremental findings",
61
+ description="Resolved citations with full metadata",
119
62
  )
120
63
 
64
+ @classmethod
65
+ def from_raw(
66
+ cls,
67
+ raw: RawSearchAnswer,
68
+ search_results: "list[SearchResult]",
69
+ ) -> "SearchAnswer":
70
+ """Create SearchAnswer from RawSearchAnswer with resolved citations."""
71
+ citations = resolve_citations(raw.cited_chunks, search_results)
72
+ return cls(
73
+ query=raw.query,
74
+ answer=raw.answer,
75
+ cited_chunks=raw.cited_chunks,
76
+ confidence=raw.confidence,
77
+ citations=citations,
78
+ )
79
+
80
+
81
+ def resolve_citations(
82
+ cited_chunk_ids: list[str],
83
+ search_results: "list[SearchResult]",
84
+ ) -> list[Citation]:
85
+ """Resolve chunk IDs to full Citation objects with metadata."""
86
+ by_id = {r.chunk_id: r for r in search_results if r.chunk_id}
87
+
88
+ citations = []
89
+ for chunk_id in cited_chunk_ids:
90
+ r = by_id.get(chunk_id)
91
+ if not r:
92
+ continue
93
+ citations.append(
94
+ Citation(
95
+ document_id=r.document_id or "",
96
+ chunk_id=chunk_id,
97
+ document_uri=r.document_uri or "",
98
+ document_title=r.document_title,
99
+ page_numbers=r.page_numbers,
100
+ headings=r.headings,
101
+ content=r.content,
102
+ )
103
+ )
104
+ return citations
105
+
121
106
 
122
107
  class EvaluationResult(BaseModel):
123
- """Result of analysis and evaluation."""
108
+ """Result of research sufficiency evaluation."""
124
109
 
125
- key_insights: list[str] = Field(
126
- description="Main insights extracted from the research so far"
127
- )
128
- new_questions: list[str] = Field(
129
- description="New sub-questions to add to the research (max 3)",
130
- max_length=3,
131
- default=[],
132
- )
133
- gaps: list[str] = Field(
134
- description="Concrete information gaps that remain", default_factory=list
110
+ is_sufficient: bool = Field(
111
+ description="Whether the research is sufficient to answer the original question"
135
112
  )
136
113
  confidence_score: float = Field(
137
- description="Confidence level in the completeness of research (0-1)",
138
114
  ge=0.0,
139
115
  le=1.0,
140
- )
141
- is_sufficient: bool = Field(
142
- description="Whether the research is sufficient to answer the original question"
116
+ description="Confidence level in the completeness of research (0-1)",
143
117
  )
144
118
  reasoning: str = Field(
145
119
  description="Explanation of why the research is or isn't complete"
146
120
  )
121
+ new_questions: list[str] = Field(
122
+ default_factory=list,
123
+ max_length=3,
124
+ description="New sub-questions to add to the research (max 3)",
125
+ )
147
126
 
148
127
 
149
128
  class ResearchReport(BaseModel):
@@ -1,107 +1,115 @@
1
- INSIGHT_AGENT_PROMPT = """You are the insight aggregation specialist for the
2
- research workflow.
1
+ PLAN_PROMPT = """You are the research orchestrator for a focused, iterative workflow.
2
+
3
+ Responsibilities:
4
+ 1. Understand and decompose the main question
5
+ 2. Propose a minimal, high-leverage plan
6
+ 3. Coordinate specialized agents to gather evidence
7
+ 4. Iterate based on gaps and new findings
8
+
9
+ Plan requirements:
10
+ - Produce at most 3 sub_questions that together cover the main question.
11
+ - sub_questions must be a list of plain strings, where each string is a complete
12
+ question. Do NOT use objects with nested fields like {question, details}.
13
+ - Each sub_question must be a standalone, self-contained query that can run
14
+ without extra context. Include concrete entities, scope, timeframe, and any
15
+ qualifiers. Avoid ambiguous pronouns (it/they/this/that).
16
+ - Prioritize the highest-value aspects first; avoid redundancy and overlap.
17
+ - Prefer questions that are likely answerable from the current knowledge base;
18
+ if coverage is uncertain, make scopes narrower and specific.
19
+ - Order sub_questions by execution priority (most valuable first).
20
+
21
+ Use the gather_context tool once on the main question before planning."""
22
+
23
+ SEARCH_PROMPT = """You are a search and question-answering specialist.
24
+
25
+ Process:
26
+ 1. Call search_and_answer with relevant keywords from the question.
27
+ 2. Review the results and their relevance scores.
28
+ 3. If needed, perform follow-up searches with different keywords (max 3 total).
29
+ 4. Provide a concise answer based strictly on the retrieved content.
30
+
31
+ The search tool returns results like:
32
+ [9bde5847-44c9-400a-8997-0e6b65babf92] (score: 0.85)
33
+ Source: "Document Title" > Section > Subsection
34
+ Type: paragraph
35
+ Content:
36
+ The actual text content here...
37
+
38
+ [d5a63c82-cb40-439f-9b2e-de7d177829b7] (score: 0.72)
39
+ Source: "Another Document"
40
+ Type: table
41
+ Content:
42
+ | Column 1 | Column 2 |
43
+ ...
44
+
45
+ Each result includes:
46
+ - chunk_id in brackets and relevance score
47
+ - Source: document title and section hierarchy (when available)
48
+ - Type: content type like paragraph, table, code, list_item (when available)
49
+ - Content: the actual text
50
+
51
+ Output format:
52
+ - query: Echo the question you are answering
53
+ - answer: Your concise answer based on the retrieved content
54
+ - cited_chunks: List of plain strings containing only the chunk UUIDs (not objects)
55
+ - confidence: A score from 0.0 to 1.0 indicating answer confidence
56
+
57
+ IMPORTANT: Use the EXACT, COMPLETE chunk ID (full UUID). Do NOT truncate IDs.
58
+
59
+ Guidelines:
60
+ - Base answers strictly on retrieved content - do not use external knowledge.
61
+ - Use the Source and Type metadata to understand context.
62
+ - If multiple results are relevant, synthesize them coherently.
63
+ - If information is insufficient, say so clearly.
64
+ - Be concise and direct; avoid meta commentary about the process.
65
+ - Higher scores indicate more relevant results."""
66
+
67
+ DECISION_PROMPT = """You are the research evaluator responsible for assessing
68
+ whether gathered evidence sufficiently answers the research question.
3
69
 
4
70
  Inputs available:
5
- - Original research question and sub-questions
6
- - Questionanswer pairs with supporting snippets and sources
7
- - Existing insights and gaps (with status metadata)
71
+ - Original research question
72
+ - Question-answer pairs with supporting sources
73
+ - Previous evaluation (if any)
8
74
 
9
75
  Tasks:
10
- 1. Extract new or refined insights that advance understanding of the question.
11
- 2. Update gap status, creating new gap entries when necessary and marking
12
- resolved ones explicitly.
13
- 3. Suggest up to 3 high-impact follow-up sub_questions that would close the
14
- most important remaining gaps.
15
-
16
- Output format (map directly to fields):
17
- - highlights: list of insights with fields {summary, status, supporting_sources,
18
- originating_questions, notes}. Use status one of {validated, open, tentative}.
19
- - gap_assessments: list of gaps with fields {description, severity, blocking,
20
- resolved, resolved_by, supporting_sources, notes}. Severity must be one of
21
- {low, medium, high}. resolved_by may reference related insight summaries if no
22
- stable identifier yet.
23
- - resolved_gaps: list of identifiers or descriptions for gaps now closed.
24
- - new_questions: up to 3 standalone, specific sub-questions (no duplicates with
25
- existing ones).
26
- - commentary: 1–3 sentences summarizing what changed this round.
27
-
28
- Guidance:
29
- - Be concise and avoid repeating previously recorded information unless it
30
- changed materially.
31
- - Tie supporting_sources to the evidence used; omit if unavailable.
32
- - Only propose new sub_questions that directly address remaining gaps.
33
- - When marking a gap as resolved, ensure the rationale is clear via
34
- resolved_by or notes."""
35
-
36
- DECISION_AGENT_PROMPT = """You are the research governor responsible for making
37
- stop/go decisions.
76
+ 1. Assess whether the collected evidence answers the original question.
77
+ 2. Provide a confidence_score in [0,1] reflecting coverage and evidence quality.
78
+ 3. Optionally propose up to 3 new sub-questions if important gaps remain.
38
79
 
39
- Inputs available:
40
- - Original research question and current plan
41
- - Full insight ledger with status metadata
42
- - Up-to-date gap tracker, including resolved indicators
43
- - Latest insight analysis summary (highlights, gap changes, new questions)
44
- - Previous evaluation decision (if any)
80
+ Output fields:
81
+ - is_sufficient: true when the question is adequately answered
82
+ - confidence_score: numeric in [0,1]
83
+ - reasoning: brief explanation of the assessment
84
+ - new_questions: list of follow-up questions (max 3), only if needed
45
85
 
46
- Tasks:
47
- 1. Determine whether the collected evidence now answers the original question.
48
- 2. Provide a confidence_score in [0,1] that reflects coverage, evidence quality,
49
- and agreement across sources.
50
- 3. List the highest-priority gaps that still block a confident answer. Reference
51
- existing gap descriptions rather than inventing new ones.
52
- 4. Optionally propose up to 3 new sub_questions only if they are not already in
53
- the current backlog.
54
-
55
- Strictness:
56
- - Only mark research as sufficient when every critical aspect of the main
57
- question is addressed with reliable, corroborated evidence.
58
- - Treat unresolved high-severity or blocking gaps as a hard stop.
59
-
60
- Output fields must line up with EvaluationResult:
61
- - key_insights: concise bullet-ready statements of the most decision-relevant
62
- insights (cite status if helpful).
63
- - new_questions: follow-up sub-questions (max 3) meeting the specificity rules.
64
- - gaps: list remaining blockers; reuse wording from the tracked gaps when
65
- possible to aid downstream reconciliation.
66
- - confidence_score: numeric in [0,1].
67
- - is_sufficient: true only when no blocking gaps remain.
68
- - reasoning: short narrative tying the decision to evidence coverage.
69
-
70
- Remember: prefer maintaining continuity with the structured context over
71
- introducing new terminology."""
72
-
73
- SYNTHESIS_AGENT_PROMPT = """You are a synthesis specialist producing the final
74
- research report.
86
+ Be strict: only mark sufficient when key aspects are addressed with reliable evidence."""
87
+
88
+ SYNTHESIS_PROMPT = """You are a synthesis specialist producing the final
89
+ research report that directly answers the original question.
75
90
 
76
91
  Goals:
77
- 1. Synthesize all gathered information into a coherent narrative.
92
+ 1. Directly answer the research question using gathered evidence.
78
93
  2. Present findings clearly and concisely.
79
- 3. Draw evidencebased conclusions and recommendations.
94
+ 3. Draw evidence-based conclusions and recommendations.
80
95
  4. State limitations and uncertainties transparently.
81
96
 
82
97
  Report guidelines (map to output fields):
83
- - title: concise (512 words), informative.
84
- - executive_summary: 35 sentences summarizing the overall answer.
85
- - main_findings: 4–8 one‑sentence bullets; each reflects evidence from the
86
- research (do not include inline citations or snippet text).
87
- - conclusions: 2–4 bullets that follow logically from findings.
88
- - recommendations: 2–5 actionable bullets tied to findings.
89
- - limitations: 1–3 bullets describing key constraints or uncertainties.
90
- - sources_summary: 2–4 sentences summarizing sources used and their reliability.
98
+ - title: concise (5-12 words), informative.
99
+ - executive_summary: 3-5 sentences that DIRECTLY ANSWER the original question.
100
+ Write the actual answer, not a description of what the report contains.
101
+ BAD: "This report examines the topic and presents findings..."
102
+ GOOD: "The system requires configuration X and supports features Y and Z..."
103
+ - main_findings: list of plain strings, 4-8 one-sentence bullets reflecting evidence.
104
+ - conclusions: list of plain strings, 2-4 bullets following logically from findings.
105
+ - recommendations: list of plain strings, 2-5 actionable bullets tied to findings.
106
+ - limitations: list of plain strings, 1-3 bullets describing constraints or uncertainties.
107
+ - sources_summary: single string listing sources with document paths and page numbers.
108
+
109
+ All list fields must contain plain strings only, not objects.
91
110
 
92
111
  Style:
93
112
  - Base all content solely on the collected evidence.
94
113
  - Be professional, objective, and specific.
95
- - Avoid meta commentary and refrain from speculation beyond the evidence."""
96
-
97
- PRESEARCH_AGENT_PROMPT = """You are a rapid research surveyor.
98
-
99
- Task:
100
- - Call gather_context once on the main question to obtain relevant text from
101
- the knowledge base (KB).
102
- - Read that context and produce a short natural‑language summary of what the
103
- KB appears to contain relative to the question.
104
-
105
- Rules:
106
- - Base the summary strictly on the provided text; do not invent.
107
- - Output only the summary as plain text (one short paragraph)."""
114
+ - NEVER use meta-commentary like "This report covers..." or "The findings show...".
115
+ Instead, state the actual information directly."""
@@ -1,22 +1,29 @@
1
1
  import asyncio
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Literal
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
7
7
  from haiku.rag.client import HaikuRAG
8
8
  from haiku.rag.graph.research.dependencies import ResearchContext
9
- from haiku.rag.graph.research.models import (
10
- EvaluationResult,
11
- InsightAnalysis,
12
- ResearchReport,
13
- )
9
+ from haiku.rag.graph.research.models import EvaluationResult, ResearchReport
14
10
 
15
11
  if TYPE_CHECKING:
16
12
  from haiku.rag.config.models import AppConfig
17
13
  from haiku.rag.graph.agui.emitter import AGUIEmitter
18
14
 
19
15
 
16
+ class HumanDecision(BaseModel):
17
+ """Human decision input for interactive research."""
18
+
19
+ action: Literal[
20
+ "search", "synthesize", "modify_questions", "add_questions", "chat", "research"
21
+ ]
22
+ questions: list[str] | None = None
23
+ message: str | None = None
24
+ research_question: str | None = None
25
+
26
+
20
27
  @dataclass
21
28
  class ResearchDeps:
22
29
  """Dependencies for research graph execution."""
@@ -24,14 +31,11 @@ class ResearchDeps:
24
31
  client: HaikuRAG
25
32
  agui_emitter: "AGUIEmitter[ResearchState, ResearchReport] | None" = None
26
33
  semaphore: asyncio.Semaphore | None = None
34
+ human_input_queue: asyncio.Queue[HumanDecision] | None = None
35
+ interactive: bool = False
27
36
 
28
37
  def emit_log(self, message: str, state: "ResearchState | None" = None) -> None:
29
- """Emit a log message through AG-UI events.
30
-
31
- Args:
32
- message: The message to log
33
- state: Optional state to include in state update
34
- """
38
+ """Emit a log message through AG-UI events."""
35
39
  if self.agui_emitter:
36
40
  self.agui_emitter.log(message)
37
41
  if state:
@@ -39,15 +43,12 @@ class ResearchDeps:
39
43
 
40
44
 
41
45
  class ResearchState(BaseModel):
42
- """Research graph state model.
43
-
44
- Fully JSON-serializable Pydantic model suitable for AG-UI state synchronization.
45
- """
46
+ """Research graph state model."""
46
47
 
47
48
  model_config = {"arbitrary_types_allowed": True}
48
49
 
49
50
  context: ResearchContext = Field(
50
- description="Shared research context with questions, insights, and gaps"
51
+ description="Shared research context with questions and QA responses"
51
52
  )
52
53
  iterations: int = Field(default=0, description="Current iteration number")
53
54
  max_iterations: int = Field(default=3, description="Maximum allowed iterations")
@@ -60,26 +61,33 @@ class ResearchState(BaseModel):
60
61
  last_eval: EvaluationResult | None = Field(
61
62
  default=None, description="Last evaluation result"
62
63
  )
63
- last_analysis: InsightAnalysis | None = Field(
64
- default=None, description="Last insight analysis"
64
+ search_filter: str | None = Field(
65
+ default=None, description="SQL WHERE clause to filter search results"
65
66
  )
66
67
 
67
68
  @classmethod
68
69
  def from_config(
69
- cls, context: ResearchContext, config: "AppConfig"
70
+ cls,
71
+ context: ResearchContext,
72
+ config: "AppConfig",
73
+ max_iterations: int | None = None,
74
+ confidence_threshold: float | None = None,
70
75
  ) -> "ResearchState":
71
76
  """Create a ResearchState from an AppConfig.
72
77
 
73
78
  Args:
74
- context: The ResearchContext containing the question and settings
75
- config: The AppConfig object (uses config.research for state parameters)
76
-
77
- Returns:
78
- A configured ResearchState instance
79
+ context: The ResearchContext containing the question
80
+ config: The AppConfig object
81
+ max_iterations: Override max iterations (None uses config default)
82
+ confidence_threshold: Override threshold (None uses config, 0.0 disables check)
79
83
  """
80
84
  return cls(
81
85
  context=context,
82
- max_iterations=config.research.max_iterations,
83
- confidence_threshold=config.research.confidence_threshold,
86
+ max_iterations=max_iterations
87
+ if max_iterations is not None
88
+ else config.research.max_iterations,
89
+ confidence_threshold=confidence_threshold
90
+ if confidence_threshold is not None
91
+ else config.research.confidence_threshold,
84
92
  max_concurrency=config.research.max_concurrency,
85
93
  )
@@ -0,0 +1,8 @@
1
+ try:
2
+ from haiku.rag.inspector.app import run_inspector
3
+ except ImportError as e:
4
+ raise ImportError(
5
+ "textual is not installed. Please install it with `pip install 'haiku.rag-slim[inspector]'` or use the full haiku.rag package."
6
+ ) from e
7
+
8
+ __all__ = ["run_inspector"]