haiku.rag-slim 0.16.0__py3-none-any.whl → 0.24.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of haiku.rag-slim might be problematic. Click here for more details.
- haiku/rag/app.py +430 -72
- haiku/rag/chunkers/__init__.py +31 -0
- haiku/rag/chunkers/base.py +31 -0
- haiku/rag/chunkers/docling_local.py +164 -0
- haiku/rag/chunkers/docling_serve.py +179 -0
- haiku/rag/cli.py +207 -24
- haiku/rag/cli_chat.py +489 -0
- haiku/rag/client.py +1251 -266
- haiku/rag/config/__init__.py +16 -10
- haiku/rag/config/loader.py +5 -44
- haiku/rag/config/models.py +126 -17
- haiku/rag/converters/__init__.py +31 -0
- haiku/rag/converters/base.py +63 -0
- haiku/rag/converters/docling_local.py +193 -0
- haiku/rag/converters/docling_serve.py +229 -0
- haiku/rag/converters/text_utils.py +237 -0
- haiku/rag/embeddings/__init__.py +123 -24
- haiku/rag/embeddings/voyageai.py +175 -20
- haiku/rag/graph/__init__.py +0 -11
- haiku/rag/graph/agui/__init__.py +8 -2
- haiku/rag/graph/agui/cli_renderer.py +1 -1
- haiku/rag/graph/agui/emitter.py +219 -31
- haiku/rag/graph/agui/server.py +20 -62
- haiku/rag/graph/agui/stream.py +1 -2
- haiku/rag/graph/research/__init__.py +5 -2
- haiku/rag/graph/research/dependencies.py +12 -126
- haiku/rag/graph/research/graph.py +390 -135
- haiku/rag/graph/research/models.py +91 -112
- haiku/rag/graph/research/prompts.py +99 -91
- haiku/rag/graph/research/state.py +35 -27
- haiku/rag/inspector/__init__.py +8 -0
- haiku/rag/inspector/app.py +259 -0
- haiku/rag/inspector/widgets/__init__.py +6 -0
- haiku/rag/inspector/widgets/chunk_list.py +100 -0
- haiku/rag/inspector/widgets/context_modal.py +89 -0
- haiku/rag/inspector/widgets/detail_view.py +130 -0
- haiku/rag/inspector/widgets/document_list.py +75 -0
- haiku/rag/inspector/widgets/info_modal.py +209 -0
- haiku/rag/inspector/widgets/search_modal.py +183 -0
- haiku/rag/inspector/widgets/visual_modal.py +126 -0
- haiku/rag/mcp.py +106 -102
- haiku/rag/monitor.py +33 -9
- haiku/rag/providers/__init__.py +5 -0
- haiku/rag/providers/docling_serve.py +108 -0
- haiku/rag/qa/__init__.py +12 -10
- haiku/rag/qa/agent.py +43 -61
- haiku/rag/qa/prompts.py +35 -57
- haiku/rag/reranking/__init__.py +9 -6
- haiku/rag/reranking/base.py +1 -1
- haiku/rag/reranking/cohere.py +5 -4
- haiku/rag/reranking/mxbai.py +5 -2
- haiku/rag/reranking/vllm.py +3 -4
- haiku/rag/reranking/zeroentropy.py +6 -5
- haiku/rag/store/__init__.py +2 -1
- haiku/rag/store/engine.py +242 -42
- haiku/rag/store/exceptions.py +4 -0
- haiku/rag/store/models/__init__.py +8 -2
- haiku/rag/store/models/chunk.py +190 -0
- haiku/rag/store/models/document.py +46 -0
- haiku/rag/store/repositories/chunk.py +141 -121
- haiku/rag/store/repositories/document.py +25 -84
- haiku/rag/store/repositories/settings.py +11 -14
- haiku/rag/store/upgrades/__init__.py +19 -3
- haiku/rag/store/upgrades/v0_10_1.py +1 -1
- haiku/rag/store/upgrades/v0_19_6.py +65 -0
- haiku/rag/store/upgrades/v0_20_0.py +68 -0
- haiku/rag/store/upgrades/v0_23_1.py +100 -0
- haiku/rag/store/upgrades/v0_9_3.py +3 -3
- haiku/rag/utils.py +371 -146
- {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/METADATA +15 -12
- haiku_rag_slim-0.24.0.dist-info/RECORD +78 -0
- {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/WHEEL +1 -1
- haiku/rag/chunker.py +0 -65
- haiku/rag/embeddings/base.py +0 -25
- haiku/rag/embeddings/ollama.py +0 -28
- haiku/rag/embeddings/openai.py +0 -26
- haiku/rag/embeddings/vllm.py +0 -29
- haiku/rag/graph/agui/events.py +0 -254
- haiku/rag/graph/common/__init__.py +0 -5
- haiku/rag/graph/common/models.py +0 -42
- haiku/rag/graph/common/nodes.py +0 -265
- haiku/rag/graph/common/prompts.py +0 -46
- haiku/rag/graph/common/utils.py +0 -44
- haiku/rag/graph/deep_qa/__init__.py +0 -1
- haiku/rag/graph/deep_qa/dependencies.py +0 -27
- haiku/rag/graph/deep_qa/graph.py +0 -243
- haiku/rag/graph/deep_qa/models.py +0 -20
- haiku/rag/graph/deep_qa/prompts.py +0 -59
- haiku/rag/graph/deep_qa/state.py +0 -56
- haiku/rag/graph/research/common.py +0 -87
- haiku/rag/reader.py +0 -135
- haiku_rag_slim-0.16.0.dist-info/RECORD +0 -71
- {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/entry_points.txt +0 -0
- {haiku_rag_slim-0.16.0.dist-info → haiku_rag_slim-0.24.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,149 +1,128 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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("
|
|
17
|
+
@field_validator("sub_questions")
|
|
43
18
|
@classmethod
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
50
|
-
"""
|
|
27
|
+
class Citation(BaseModel):
|
|
28
|
+
"""Resolved citation with full metadata for display/visual grounding."""
|
|
51
29
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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="
|
|
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
|
|
98
|
-
"""
|
|
56
|
+
class SearchAnswer(RawSearchAnswer):
|
|
57
|
+
"""Answer to a search query with resolved citations."""
|
|
99
58
|
|
|
100
|
-
|
|
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="
|
|
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
|
|
108
|
+
"""Result of research sufficiency evaluation."""
|
|
124
109
|
|
|
125
|
-
|
|
126
|
-
description="
|
|
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
|
-
|
|
2
|
-
|
|
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
|
|
6
|
-
- Question
|
|
7
|
-
-
|
|
71
|
+
- Original research question
|
|
72
|
+
- Question-answer pairs with supporting sources
|
|
73
|
+
- Previous evaluation (if any)
|
|
8
74
|
|
|
9
75
|
Tasks:
|
|
10
|
-
1.
|
|
11
|
-
2.
|
|
12
|
-
|
|
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
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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.
|
|
92
|
+
1. Directly answer the research question using gathered evidence.
|
|
78
93
|
2. Present findings clearly and concisely.
|
|
79
|
-
3. Draw evidence
|
|
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 (5
|
|
84
|
-
- executive_summary: 3
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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
|
-
|
|
64
|
-
default=None, description="
|
|
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,
|
|
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
|
|
75
|
-
config: The AppConfig object
|
|
76
|
-
|
|
77
|
-
|
|
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=
|
|
83
|
-
|
|
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"]
|