haiku.rag 0.9.2__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.
- haiku/rag/app.py +50 -14
- haiku/rag/cli.py +16 -4
- haiku/rag/client.py +3 -5
- haiku/rag/reranking/mxbai.py +1 -1
- haiku/rag/research/__init__.py +10 -27
- haiku/rag/research/common.py +53 -0
- haiku/rag/research/dependencies.py +5 -3
- haiku/rag/research/graph.py +29 -0
- haiku/rag/research/models.py +70 -0
- haiku/rag/research/nodes/evaluate.py +80 -0
- haiku/rag/research/nodes/plan.py +63 -0
- haiku/rag/research/nodes/search.py +91 -0
- haiku/rag/research/nodes/synthesize.py +51 -0
- haiku/rag/research/prompts.py +97 -113
- haiku/rag/research/state.py +25 -0
- haiku/rag/store/engine.py +42 -17
- haiku/rag/store/models/chunk.py +1 -0
- haiku/rag/store/repositories/chunk.py +60 -39
- haiku/rag/store/repositories/document.py +2 -2
- haiku/rag/store/repositories/settings.py +12 -5
- haiku/rag/store/upgrades/__init__.py +60 -1
- haiku/rag/store/upgrades/v0_9_3.py +112 -0
- {haiku_rag-0.9.2.dist-info → haiku_rag-0.10.0.dist-info}/METADATA +37 -1
- haiku_rag-0.10.0.dist-info/RECORD +53 -0
- haiku/rag/research/base.py +0 -130
- haiku/rag/research/evaluation_agent.py +0 -42
- haiku/rag/research/orchestrator.py +0 -300
- haiku/rag/research/presearch_agent.py +0 -34
- haiku/rag/research/search_agent.py +0 -65
- haiku/rag/research/synthesis_agent.py +0 -40
- haiku_rag-0.9.2.dist-info/RECORD +0 -50
- {haiku_rag-0.9.2.dist-info → haiku_rag-0.10.0.dist-info}/WHEEL +0 -0
- {haiku_rag-0.9.2.dist-info → haiku_rag-0.10.0.dist-info}/entry_points.txt +0 -0
- {haiku_rag-0.9.2.dist-info → haiku_rag-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1 +1,60 @@
|
|
|
1
|
-
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from packaging.version import Version, parse
|
|
6
|
+
|
|
7
|
+
from haiku.rag.store.engine import Store
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Upgrade:
|
|
14
|
+
"""Represents a database upgrade step."""
|
|
15
|
+
|
|
16
|
+
version: str
|
|
17
|
+
apply: Callable[[Store], None]
|
|
18
|
+
description: str = ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Registry of upgrade steps (ordered by version)
|
|
22
|
+
upgrades: list[Upgrade] = []
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_pending_upgrades(store: Store, from_version: str, to_version: str) -> None:
|
|
26
|
+
"""Run upgrades where from_version < step.version <= to_version."""
|
|
27
|
+
v_from: Version = parse(from_version)
|
|
28
|
+
v_to: Version = parse(to_version)
|
|
29
|
+
|
|
30
|
+
# Ensure that tests/development run available code upgrades even if the
|
|
31
|
+
# installed package version hasn't been bumped to include them yet.
|
|
32
|
+
if upgrades:
|
|
33
|
+
highest_step_version: Version = max(parse(u.version) for u in upgrades)
|
|
34
|
+
if highest_step_version > v_to:
|
|
35
|
+
v_to = highest_step_version
|
|
36
|
+
|
|
37
|
+
# Determine applicable steps
|
|
38
|
+
sorted_steps = sorted(upgrades, key=lambda u: parse(u.version))
|
|
39
|
+
applicable = [s for s in sorted_steps if v_from < parse(s.version) <= v_to]
|
|
40
|
+
if applicable:
|
|
41
|
+
logger.info("%d upgrade step(s) pending", len(applicable))
|
|
42
|
+
|
|
43
|
+
# Apply in ascending order
|
|
44
|
+
for idx, step in enumerate(applicable, start=1):
|
|
45
|
+
logger.info(
|
|
46
|
+
"Applying upgrade %s: %s (%d/%d)",
|
|
47
|
+
step.version,
|
|
48
|
+
step.description or "",
|
|
49
|
+
idx,
|
|
50
|
+
len(applicable),
|
|
51
|
+
)
|
|
52
|
+
step.apply(store)
|
|
53
|
+
logger.info("Completed upgrade %s", step.version)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
from .v0_9_3 import upgrade_fts_phrase as upgrade_0_9_3_fts # noqa: E402
|
|
57
|
+
from .v0_9_3 import upgrade_order as upgrade_0_9_3_order # noqa: E402
|
|
58
|
+
|
|
59
|
+
upgrades.append(upgrade_0_9_3_order)
|
|
60
|
+
upgrades.append(upgrade_0_9_3_fts)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from lancedb.pydantic import LanceModel, Vector
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from haiku.rag.store.engine import Store
|
|
7
|
+
from haiku.rag.store.upgrades import Upgrade
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _infer_vector_dim(store: Store) -> int:
|
|
11
|
+
"""Infer vector dimension from existing data; fallback to embedder config."""
|
|
12
|
+
try:
|
|
13
|
+
arrow = store.chunks_table.search().limit(1).to_arrow()
|
|
14
|
+
rows = arrow.to_pylist()
|
|
15
|
+
if rows:
|
|
16
|
+
vec = rows[0].get("vector")
|
|
17
|
+
if isinstance(vec, list) and vec:
|
|
18
|
+
return len(vec)
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
# Fallback to configured embedder vector dim
|
|
22
|
+
return getattr(store.embedder, "_vector_dim", 1024)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _apply_chunk_order(store: Store) -> None:
|
|
26
|
+
"""Add integer 'order' column to chunks and backfill from metadata."""
|
|
27
|
+
|
|
28
|
+
vector_dim = _infer_vector_dim(store)
|
|
29
|
+
|
|
30
|
+
class ChunkRecordV2(LanceModel):
|
|
31
|
+
id: str
|
|
32
|
+
document_id: str
|
|
33
|
+
content: str
|
|
34
|
+
metadata: str = Field(default="{}")
|
|
35
|
+
order: int = Field(default=0)
|
|
36
|
+
vector: Vector(vector_dim) = Field( # type: ignore
|
|
37
|
+
default_factory=lambda: [0.0] * vector_dim
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Read existing chunks
|
|
41
|
+
try:
|
|
42
|
+
chunks_arrow = store.chunks_table.search().to_arrow()
|
|
43
|
+
rows = chunks_arrow.to_pylist()
|
|
44
|
+
except Exception:
|
|
45
|
+
rows = []
|
|
46
|
+
|
|
47
|
+
new_chunk_records: list[ChunkRecordV2] = []
|
|
48
|
+
for row in rows:
|
|
49
|
+
md_raw = row.get("metadata") or "{}"
|
|
50
|
+
try:
|
|
51
|
+
md = json.loads(md_raw) if isinstance(md_raw, str) else md_raw
|
|
52
|
+
except Exception:
|
|
53
|
+
md = {}
|
|
54
|
+
# Extract and normalize order
|
|
55
|
+
order_val = 0
|
|
56
|
+
try:
|
|
57
|
+
if isinstance(md, dict) and "order" in md:
|
|
58
|
+
order_val = int(md["order"]) # type: ignore[arg-type]
|
|
59
|
+
except Exception:
|
|
60
|
+
order_val = 0
|
|
61
|
+
|
|
62
|
+
if isinstance(md, dict) and "order" in md:
|
|
63
|
+
md = {k: v for k, v in md.items() if k != "order"}
|
|
64
|
+
|
|
65
|
+
vec = row.get("vector") or [0.0] * vector_dim
|
|
66
|
+
|
|
67
|
+
new_chunk_records.append(
|
|
68
|
+
ChunkRecordV2(
|
|
69
|
+
id=row.get("id"),
|
|
70
|
+
document_id=row.get("document_id"),
|
|
71
|
+
content=row.get("content", ""),
|
|
72
|
+
metadata=json.dumps(md),
|
|
73
|
+
order=order_val,
|
|
74
|
+
vector=vec,
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Recreate chunks table with new schema
|
|
79
|
+
try:
|
|
80
|
+
store.db.drop_table("chunks")
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
store.chunks_table = store.db.create_table("chunks", schema=ChunkRecordV2)
|
|
85
|
+
store.chunks_table.create_fts_index("content", replace=True)
|
|
86
|
+
|
|
87
|
+
if new_chunk_records:
|
|
88
|
+
store.chunks_table.add(new_chunk_records)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
upgrade_order = Upgrade(
|
|
92
|
+
version="0.9.3",
|
|
93
|
+
apply=_apply_chunk_order,
|
|
94
|
+
description="Add 'order' column to chunks and backfill from metadata",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _apply_fts_phrase_support(store: Store) -> None:
|
|
99
|
+
"""Recreate FTS index with phrase query support and no stop-word removal."""
|
|
100
|
+
try:
|
|
101
|
+
store.chunks_table.create_fts_index(
|
|
102
|
+
"content", replace=True, with_position=True, remove_stop_words=False
|
|
103
|
+
)
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
upgrade_fts_phrase = Upgrade(
|
|
109
|
+
version="0.9.3",
|
|
110
|
+
apply=_apply_fts_phrase_support,
|
|
111
|
+
description="Enable FTS phrase queries (with positions) and keep stop-words",
|
|
112
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haiku.rag
|
|
3
|
-
Version: 0.
|
|
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
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
haiku/rag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
haiku/rag/app.py,sha256=m5agkPrJhbzEbdC01CU_GR2Gj4voFuAGmxR7DS2K9is,12934
|
|
3
|
+
haiku/rag/chunker.py,sha256=PVe6ysv8UlacUd4Zb3_8RFWIaWDXnzBAy2VDJ4TaUsE,1555
|
|
4
|
+
haiku/rag/cli.py,sha256=oXEQoRTlzrrJ9hC27_Dht9ElBb9q_wTEESnXdNy3eW8,10257
|
|
5
|
+
haiku/rag/client.py,sha256=QgJQu7g7JjAzWN6R10NeDqpFf89Dml_LiWce4QRHLHc,21177
|
|
6
|
+
haiku/rag/config.py,sha256=SPEIv2IElZmZh4Wsp8gk7ViRW5ZzD-UGmIqRAXscDdI,2134
|
|
7
|
+
haiku/rag/logging.py,sha256=dm65AwADpcQsH5OAPtRA-4hsw0w5DK-sGOvzYkj6jzw,1720
|
|
8
|
+
haiku/rag/mcp.py,sha256=bR9Y-Nz-hvjiql20Y0KE0hwNGwyjmPGX8K9d-qmXptY,4683
|
|
9
|
+
haiku/rag/migration.py,sha256=M--KnSF3lxgKjxmokb4vuzGH-pV8eg0C_8e7jvPqW8Y,11058
|
|
10
|
+
haiku/rag/monitor.py,sha256=r386nkhdlsU8UECwIuVwnrSlgMk3vNIuUZGNIzkZuec,2770
|
|
11
|
+
haiku/rag/reader.py,sha256=qkPTMJuQ_o4sK-8zpDl9WFYe_MJ7aL_gUw6rczIpW-g,3274
|
|
12
|
+
haiku/rag/utils.py,sha256=aiuPu_rrfpyIvJJq0o5boUIIvCdNzdpKwAIPYYn3iG8,4965
|
|
13
|
+
haiku/rag/embeddings/__init__.py,sha256=44IfDITGIFTflGT6UEmiYOwpWFVbYv5smLY59D0YeCs,1419
|
|
14
|
+
haiku/rag/embeddings/base.py,sha256=BnSviKrlzjv3L0sZJs_T-pxfawd-bcTak-rsX-D2f3A,497
|
|
15
|
+
haiku/rag/embeddings/ollama.py,sha256=LuLlHH6RGoO9_gFCIlbmesuXOj017gTw6z-p8Ez0CfE,595
|
|
16
|
+
haiku/rag/embeddings/openai.py,sha256=fIFCk-jpUtaW0xsnrQnJ824O0UCjaGG2sgvBzREhilc,503
|
|
17
|
+
haiku/rag/embeddings/vllm.py,sha256=vhaUnCn6VMkfSluLhWKtSV-sekFaPsp4pKo2N7-SBCY,626
|
|
18
|
+
haiku/rag/embeddings/voyageai.py,sha256=UW-MW4tJKnPB6Fs2P7A3yt-ZeRm46H9npckchSriPX8,661
|
|
19
|
+
haiku/rag/qa/__init__.py,sha256=Sl7Kzrg9CuBOcMF01wc1NtQhUNWjJI0MhIHfCWrb8V4,434
|
|
20
|
+
haiku/rag/qa/agent.py,sha256=f4Keh-ESgctNbTg96QL95HYjINVLOcxa8t8crx92MMk,3081
|
|
21
|
+
haiku/rag/qa/prompts.py,sha256=LhRfDtO8Pb06lpr4PpwEaKUYItZ5OiIkeqcCogcssHY,3347
|
|
22
|
+
haiku/rag/reranking/__init__.py,sha256=IRXHs4qPu6VbGJQpzSwhgtVWWumURH_vEoVFE-extlo,894
|
|
23
|
+
haiku/rag/reranking/base.py,sha256=LM9yUSSJ414UgBZhFTgxGprlRqzfTe4I1vgjricz2JY,405
|
|
24
|
+
haiku/rag/reranking/cohere.py,sha256=1iTdiaa8vvb6oHVB2qpWzUOVkyfUcimVSZp6Qr4aq4c,1049
|
|
25
|
+
haiku/rag/reranking/mxbai.py,sha256=uveGFIdmNmepd2EQsvYr64wv0ra2_wB845hdSZXy5Cw,908
|
|
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=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
|
|
38
|
+
haiku/rag/store/__init__.py,sha256=hq0W0DAC7ysqhWSP2M2uHX8cbG6kbr-sWHxhq6qQcY0,103
|
|
39
|
+
haiku/rag/store/engine.py,sha256=-3MZJYft2XTWaLuyKha8DKhWQeU5E5CBeskXXF5fXso,9555
|
|
40
|
+
haiku/rag/store/models/__init__.py,sha256=s0E72zneGlowvZrFWaNxHYjOAUjgWdLxzdYsnvNRVlY,88
|
|
41
|
+
haiku/rag/store/models/chunk.py,sha256=Ww_hj3DMwJLNM33l1GvIP84yzDFc6cxfiWcotUfWSYg,383
|
|
42
|
+
haiku/rag/store/models/document.py,sha256=zSSpt6pyrMJAIXGQvIcqojcqUzwZnhp3WxVokaWxNRc,396
|
|
43
|
+
haiku/rag/store/repositories/__init__.py,sha256=Olv5dLfBQINRV3HrsfUpjzkZ7Qm7goEYyMNykgo_DaY,291
|
|
44
|
+
haiku/rag/store/repositories/chunk.py,sha256=O2SEhQy3ZptWjwwpxS-L8KNq2tEqEBqheHfLw-M_FqA,15012
|
|
45
|
+
haiku/rag/store/repositories/document.py,sha256=m11SamQoGYs5ODfmarJGU1yIcqtgmnba-5bGOPQuYrI,7773
|
|
46
|
+
haiku/rag/store/repositories/settings.py,sha256=7XMBMavU8zRgdBoQzQg0Obfa7UKjuVnBugidTC6sEW0,5548
|
|
47
|
+
haiku/rag/store/upgrades/__init__.py,sha256=gDOxiq3wdZPr3JoenjNYxx0cpgZJhbaFKNX2fzXRq1Q,1852
|
|
48
|
+
haiku/rag/store/upgrades/v0_9_3.py,sha256=NrjNilQSgDtFWRbL3ZUtzQzJ8tf9u0dDRJtnDFwwbdw,3322
|
|
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,,
|
haiku/rag/research/base.py
DELETED
|
@@ -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
|
-
system_prompt=self.get_system_prompt(),
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# Register tools
|
|
52
|
-
self.register_tools()
|
|
53
|
-
|
|
54
|
-
def _get_model(self, provider: str, model: str):
|
|
55
|
-
"""Get the appropriate model object for the provider."""
|
|
56
|
-
if provider == "ollama":
|
|
57
|
-
return OpenAIChatModel(
|
|
58
|
-
model_name=model,
|
|
59
|
-
provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"),
|
|
60
|
-
)
|
|
61
|
-
elif provider == "vllm":
|
|
62
|
-
return OpenAIChatModel(
|
|
63
|
-
model_name=model,
|
|
64
|
-
provider=OpenAIProvider(
|
|
65
|
-
base_url=f"{Config.VLLM_RESEARCH_BASE_URL or Config.VLLM_QA_BASE_URL}/v1",
|
|
66
|
-
api_key="none",
|
|
67
|
-
),
|
|
68
|
-
)
|
|
69
|
-
else:
|
|
70
|
-
# For all other providers, use the provider:model format
|
|
71
|
-
return f"{provider}:{model}"
|
|
72
|
-
|
|
73
|
-
@abstractmethod
|
|
74
|
-
def get_system_prompt(self) -> str:
|
|
75
|
-
"""Return the system prompt for this agent."""
|
|
76
|
-
pass
|
|
77
|
-
|
|
78
|
-
@abstractmethod
|
|
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,42 +0,0 @@
|
|
|
1
|
-
from pydantic import BaseModel, Field
|
|
2
|
-
|
|
3
|
-
from haiku.rag.research.base import BaseResearchAgent
|
|
4
|
-
from haiku.rag.research.prompts import EVALUATION_AGENT_PROMPT
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class EvaluationResult(BaseModel):
|
|
8
|
-
"""Result of analysis and evaluation."""
|
|
9
|
-
|
|
10
|
-
key_insights: list[str] = Field(
|
|
11
|
-
description="Main insights extracted from the research so far"
|
|
12
|
-
)
|
|
13
|
-
new_questions: list[str] = Field(
|
|
14
|
-
description="New sub-questions to add to the research (max 3)",
|
|
15
|
-
max_length=3,
|
|
16
|
-
default=[],
|
|
17
|
-
)
|
|
18
|
-
confidence_score: float = Field(
|
|
19
|
-
description="Confidence level in the completeness of research (0-1)",
|
|
20
|
-
ge=0.0,
|
|
21
|
-
le=1.0,
|
|
22
|
-
)
|
|
23
|
-
is_sufficient: bool = Field(
|
|
24
|
-
description="Whether the research is sufficient to answer the original question"
|
|
25
|
-
)
|
|
26
|
-
reasoning: str = Field(
|
|
27
|
-
description="Explanation of why the research is or isn't complete"
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class AnalysisEvaluationAgent(BaseResearchAgent[EvaluationResult]):
|
|
32
|
-
"""Agent that analyzes findings and evaluates research completeness."""
|
|
33
|
-
|
|
34
|
-
def __init__(self, provider: str, model: str) -> None:
|
|
35
|
-
super().__init__(provider, model, output_type=EvaluationResult)
|
|
36
|
-
|
|
37
|
-
def get_system_prompt(self) -> str:
|
|
38
|
-
return EVALUATION_AGENT_PROMPT
|
|
39
|
-
|
|
40
|
-
def register_tools(self) -> None:
|
|
41
|
-
"""No additional tools needed - uses LLM capabilities directly."""
|
|
42
|
-
pass
|