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.

Files changed (34) hide show
  1. haiku/rag/app.py +50 -14
  2. haiku/rag/cli.py +16 -4
  3. haiku/rag/client.py +3 -5
  4. haiku/rag/reranking/mxbai.py +1 -1
  5. haiku/rag/research/__init__.py +10 -27
  6. haiku/rag/research/common.py +53 -0
  7. haiku/rag/research/dependencies.py +5 -3
  8. haiku/rag/research/graph.py +29 -0
  9. haiku/rag/research/models.py +70 -0
  10. haiku/rag/research/nodes/evaluate.py +80 -0
  11. haiku/rag/research/nodes/plan.py +63 -0
  12. haiku/rag/research/nodes/search.py +91 -0
  13. haiku/rag/research/nodes/synthesize.py +51 -0
  14. haiku/rag/research/prompts.py +97 -113
  15. haiku/rag/research/state.py +25 -0
  16. haiku/rag/store/engine.py +42 -17
  17. haiku/rag/store/models/chunk.py +1 -0
  18. haiku/rag/store/repositories/chunk.py +60 -39
  19. haiku/rag/store/repositories/document.py +2 -2
  20. haiku/rag/store/repositories/settings.py +12 -5
  21. haiku/rag/store/upgrades/__init__.py +60 -1
  22. haiku/rag/store/upgrades/v0_9_3.py +112 -0
  23. {haiku_rag-0.9.2.dist-info → haiku_rag-0.10.0.dist-info}/METADATA +37 -1
  24. haiku_rag-0.10.0.dist-info/RECORD +53 -0
  25. haiku/rag/research/base.py +0 -130
  26. haiku/rag/research/evaluation_agent.py +0 -42
  27. haiku/rag/research/orchestrator.py +0 -300
  28. haiku/rag/research/presearch_agent.py +0 -34
  29. haiku/rag/research/search_agent.py +0 -65
  30. haiku/rag/research/synthesis_agent.py +0 -40
  31. haiku_rag-0.9.2.dist-info/RECORD +0 -50
  32. {haiku_rag-0.9.2.dist-info → haiku_rag-0.10.0.dist-info}/WHEEL +0 -0
  33. {haiku_rag-0.9.2.dist-info → haiku_rag-0.10.0.dist-info}/entry_points.txt +0 -0
  34. {haiku_rag-0.9.2.dist-info → haiku_rag-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1,60 @@
1
- upgrades = []
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.9.2
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,,
@@ -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