Make graph settings part of config for both deep ask and research graphs
This commit is contained in:
parent
fa93226a28
commit
782fbafb3d
14 changed files with 206 additions and 139 deletions
28
README.md
28
README.md
|
|
@ -88,8 +88,8 @@ To customize settings, create a `haiku.rag.yaml` config file (see [Configuration
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from haiku.rag.client import HaikuRAG
|
from haiku.rag.client import HaikuRAG
|
||||||
|
from haiku.rag.config import Config
|
||||||
from haiku.rag.research import (
|
from haiku.rag.research import (
|
||||||
PlanNode,
|
|
||||||
ResearchContext,
|
ResearchContext,
|
||||||
ResearchDeps,
|
ResearchDeps,
|
||||||
ResearchState,
|
ResearchState,
|
||||||
|
|
@ -115,34 +115,22 @@ async with HaikuRAG("database.lancedb") as client:
|
||||||
print(answer)
|
print(answer)
|
||||||
|
|
||||||
# Multi‑agent research pipeline (Plan → Search → Evaluate → Synthesize)
|
# Multi‑agent research pipeline (Plan → Search → Evaluate → Synthesize)
|
||||||
graph = build_research_graph()
|
# Graph settings (provider, model, max_iterations, etc.) come from config
|
||||||
|
graph = build_research_graph(config=Config)
|
||||||
question = (
|
question = (
|
||||||
"What are the main drivers and trends of global temperature "
|
"What are the main drivers and trends of global temperature "
|
||||||
"anomalies since 1990?"
|
"anomalies since 1990?"
|
||||||
)
|
)
|
||||||
state = ResearchState(
|
context = ResearchContext(original_question=question)
|
||||||
context=ResearchContext(original_question=question),
|
state = ResearchState.from_config(context=context, config=Config)
|
||||||
max_iterations=2,
|
|
||||||
confidence_threshold=0.8,
|
|
||||||
max_concurrency=2,
|
|
||||||
)
|
|
||||||
deps = ResearchDeps(client=client)
|
deps = ResearchDeps(client=client)
|
||||||
|
|
||||||
# Blocking run (final result only)
|
# Blocking run (final result only)
|
||||||
result = await graph.run(
|
report = await graph.run(state=state, deps=deps)
|
||||||
PlanNode(provider="openai", model="gpt-4o-mini"),
|
print(report.title)
|
||||||
state=state,
|
|
||||||
deps=deps,
|
|
||||||
)
|
|
||||||
print(result.output.title)
|
|
||||||
|
|
||||||
# Streaming progress (log/report/error events)
|
# Streaming progress (log/report/error events)
|
||||||
async for event in stream_research_graph(
|
async for event in stream_research_graph(graph, state, deps):
|
||||||
graph,
|
|
||||||
PlanNode(provider="openai", model="gpt-4o-mini"),
|
|
||||||
state,
|
|
||||||
deps,
|
|
||||||
):
|
|
||||||
if event.type == "log":
|
if event.type == "log":
|
||||||
iteration = event.state.iterations if event.state else state.iterations
|
iteration = event.state.iterations if event.state else state.iterations
|
||||||
print(f"[{iteration}] {event.message}")
|
print(f"[{iteration}] {event.message}")
|
||||||
|
|
|
||||||
107
docs/agents.md
107
docs/agents.md
|
|
@ -78,7 +78,7 @@ Key differences from Research:
|
||||||
|
|
||||||
Note on parallel execution:
|
Note on parallel execution:
|
||||||
- The `search_one` node is mapped over all questions in a batch
|
- The `search_one` node is mapped over all questions in a batch
|
||||||
- Parallelism is controlled via `max_concurrency` using asyncio.Semaphore
|
- Parallelism is controlled via `max_concurrency`
|
||||||
- All questions in an iteration are processed before evaluation
|
- All questions in an iteration are processed before evaluation
|
||||||
|
|
||||||
CLI usage:
|
CLI usage:
|
||||||
|
|
@ -95,25 +95,19 @@ Python usage:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from haiku.rag.client import HaikuRAG
|
from haiku.rag.client import HaikuRAG
|
||||||
|
from haiku.rag.config import Config
|
||||||
from haiku.rag.qa.deep.dependencies import DeepQAContext
|
from haiku.rag.qa.deep.dependencies import DeepQAContext
|
||||||
from haiku.rag.qa.deep.graph import build_deep_qa_graph
|
from haiku.rag.qa.deep.graph import build_deep_qa_graph
|
||||||
from haiku.rag.qa.deep.state import DeepQADeps, DeepQAState
|
from haiku.rag.qa.deep.state import DeepQADeps, DeepQAState
|
||||||
|
|
||||||
async with HaikuRAG(path_to_db) as client:
|
async with HaikuRAG(path_to_db) as client:
|
||||||
graph = build_deep_qa_graph(
|
# Use global config (recommended)
|
||||||
provider="openai",
|
graph = build_deep_qa_graph(config=Config)
|
||||||
model="gpt-4o-mini"
|
|
||||||
)
|
|
||||||
context = DeepQAContext(
|
context = DeepQAContext(
|
||||||
original_question="What are the main features of haiku.rag?",
|
original_question="What are the main features of haiku.rag?",
|
||||||
use_citations=True
|
use_citations=True
|
||||||
)
|
)
|
||||||
state = DeepQAState(
|
state = DeepQAState.from_config(context=context, config=Config)
|
||||||
context=context,
|
|
||||||
max_sub_questions=3,
|
|
||||||
max_iterations=2,
|
|
||||||
max_concurrency=1
|
|
||||||
)
|
|
||||||
deps = DeepQADeps(client=client)
|
deps = DeepQADeps(client=client)
|
||||||
|
|
||||||
result = await graph.run(
|
result = await graph.run(
|
||||||
|
|
@ -125,6 +119,33 @@ async with HaikuRAG(path_to_db) as client:
|
||||||
print(result.sources)
|
print(result.sources)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternative usage with custom config:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Create a custom config with different settings
|
||||||
|
from haiku.rag.config.models import AppConfig, QAConfig
|
||||||
|
|
||||||
|
custom_config = AppConfig(
|
||||||
|
qa=QAConfig(
|
||||||
|
provider="openai",
|
||||||
|
model="gpt-4o-mini",
|
||||||
|
max_sub_questions=5,
|
||||||
|
max_iterations=3,
|
||||||
|
max_concurrency=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
graph = build_deep_qa_graph(config=custom_config)
|
||||||
|
context = DeepQAContext(
|
||||||
|
original_question="What are the main features of haiku.rag?",
|
||||||
|
use_citations=True
|
||||||
|
)
|
||||||
|
state = DeepQAState.from_config(context=context, config=custom_config)
|
||||||
|
deps = DeepQADeps(client=client)
|
||||||
|
|
||||||
|
result = await graph.run(state=state, deps=deps)
|
||||||
|
```
|
||||||
|
|
||||||
### Research Graph
|
### Research Graph
|
||||||
|
|
||||||
The research workflow is implemented as a typed pydantic‑graph. It plans, searches (in parallel batches), evaluates, and synthesizes into a final report — with clear stop conditions and shared state.
|
The research workflow is implemented as a typed pydantic‑graph. It plans, searches (in parallel batches), evaluates, and synthesizes into a final report — with clear stop conditions and shared state.
|
||||||
|
|
@ -166,39 +187,34 @@ Primary models:
|
||||||
|
|
||||||
Note on parallel execution:
|
Note on parallel execution:
|
||||||
- The `search_one` node is mapped over all questions in a batch
|
- The `search_one` node is mapped over all questions in a batch
|
||||||
- Parallelism is controlled via `max_concurrency` using asyncio.Semaphore
|
- Parallelism is controlled via `max_concurrency`
|
||||||
- Analysis and decision nodes process results after each batch completes
|
- Analysis and decision nodes process results after each batch completes
|
||||||
|
|
||||||
CLI usage:
|
CLI usage:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
haiku-rag research "How does haiku.rag organize and query documents?" \
|
# Basic usage (uses config from file or defaults)
|
||||||
--max-iterations 2 \
|
haiku-rag research "How does haiku.rag organize and query documents?" --verbose
|
||||||
--confidence-threshold 0.8 \
|
|
||||||
--max-concurrency 3 \
|
# With custom config file
|
||||||
--verbose
|
haiku-rag --config my-research-config.yaml research "How does haiku.rag organize and query documents?" --verbose
|
||||||
```
|
```
|
||||||
|
|
||||||
Python usage (blocking result):
|
Python usage (blocking result):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from haiku.rag.client import HaikuRAG
|
from haiku.rag.client import HaikuRAG
|
||||||
|
from haiku.rag.config import Config
|
||||||
from haiku.rag.research.dependencies import ResearchContext
|
from haiku.rag.research.dependencies import ResearchContext
|
||||||
from haiku.rag.research.graph import build_research_graph
|
from haiku.rag.research.graph import build_research_graph
|
||||||
from haiku.rag.research.state import ResearchDeps, ResearchState
|
from haiku.rag.research.state import ResearchDeps, ResearchState
|
||||||
|
|
||||||
async with HaikuRAG(path_to_db) as client:
|
async with HaikuRAG(path_to_db) as client:
|
||||||
graph = build_research_graph(
|
# Use global config (recommended)
|
||||||
provider="openai",
|
graph = build_research_graph(config=Config)
|
||||||
model="gpt-4o-mini"
|
|
||||||
)
|
|
||||||
question = "What are the main drivers and trends of global temperature anomalies since 1990?"
|
question = "What are the main drivers and trends of global temperature anomalies since 1990?"
|
||||||
state = ResearchState(
|
context = ResearchContext(original_question=question)
|
||||||
context=ResearchContext(original_question=question),
|
state = ResearchState.from_config(context=context, config=Config)
|
||||||
max_iterations=2,
|
|
||||||
confidence_threshold=0.8,
|
|
||||||
max_concurrency=2,
|
|
||||||
)
|
|
||||||
deps = ResearchDeps(client=client)
|
deps = ResearchDeps(client=client)
|
||||||
|
|
||||||
result = await graph.run(
|
result = await graph.run(
|
||||||
|
|
@ -211,27 +227,44 @@ async with HaikuRAG(path_to_db) as client:
|
||||||
print(report.executive_summary)
|
print(report.executive_summary)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternative usage with custom config:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from haiku.rag.config.models import AppConfig, ResearchConfig
|
||||||
|
|
||||||
|
custom_config = AppConfig(
|
||||||
|
research=ResearchConfig(
|
||||||
|
provider="openai",
|
||||||
|
model="gpt-4o-mini",
|
||||||
|
max_iterations=5,
|
||||||
|
confidence_threshold=0.85,
|
||||||
|
max_concurrency=3,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
graph = build_research_graph(config=custom_config)
|
||||||
|
context = ResearchContext(original_question=question)
|
||||||
|
state = ResearchState.from_config(context=context, config=custom_config)
|
||||||
|
deps = ResearchDeps(client=client)
|
||||||
|
|
||||||
|
result = await graph.run(state=state, deps=deps)
|
||||||
|
```
|
||||||
|
|
||||||
Python usage (streamed events):
|
Python usage (streamed events):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from haiku.rag.client import HaikuRAG
|
from haiku.rag.client import HaikuRAG
|
||||||
|
from haiku.rag.config import Config
|
||||||
from haiku.rag.research.dependencies import ResearchContext
|
from haiku.rag.research.dependencies import ResearchContext
|
||||||
from haiku.rag.research.graph import build_research_graph
|
from haiku.rag.research.graph import build_research_graph
|
||||||
from haiku.rag.research.state import ResearchDeps, ResearchState
|
from haiku.rag.research.state import ResearchDeps, ResearchState
|
||||||
from haiku.rag.research.stream import stream_research_graph
|
from haiku.rag.research.stream import stream_research_graph
|
||||||
|
|
||||||
async with HaikuRAG(path_to_db) as client:
|
async with HaikuRAG(path_to_db) as client:
|
||||||
graph = build_research_graph(
|
graph = build_research_graph(config=Config)
|
||||||
provider="openai",
|
|
||||||
model="gpt-4o-mini"
|
|
||||||
)
|
|
||||||
question = "What are the main drivers and trends of global temperature anomalies since 1990?"
|
question = "What are the main drivers and trends of global temperature anomalies since 1990?"
|
||||||
state = ResearchState(
|
context = ResearchContext(original_question=question)
|
||||||
context=ResearchContext(original_question=question),
|
state = ResearchState.from_config(context=context, config=Config)
|
||||||
max_iterations=2,
|
|
||||||
confidence_threshold=0.8,
|
|
||||||
max_concurrency=2,
|
|
||||||
)
|
|
||||||
deps = ResearchDeps(client=client)
|
deps = ResearchDeps(client=client)
|
||||||
|
|
||||||
async for event in stream_research_graph(
|
async for event in stream_research_graph(
|
||||||
|
|
|
||||||
|
|
@ -204,23 +204,29 @@ class HaikuRAGApp:
|
||||||
deep: bool = False,
|
deep: bool = False,
|
||||||
verbose: bool = False,
|
verbose: bool = False,
|
||||||
):
|
):
|
||||||
|
"""Ask a question using the RAG system.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: The question to ask
|
||||||
|
cite: Include citations in the answer
|
||||||
|
deep: Use deep QA mode (multi-step reasoning)
|
||||||
|
verbose: Show verbose output
|
||||||
|
"""
|
||||||
async with HaikuRAG(db_path=self.db_path) as self.client:
|
async with HaikuRAG(db_path=self.db_path) as self.client:
|
||||||
try:
|
try:
|
||||||
if deep:
|
if deep:
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
|
from haiku.rag.config import Config
|
||||||
from haiku.rag.qa.deep.dependencies import DeepQAContext
|
from haiku.rag.qa.deep.dependencies import DeepQAContext
|
||||||
from haiku.rag.qa.deep.graph import build_deep_qa_graph
|
from haiku.rag.qa.deep.graph import build_deep_qa_graph
|
||||||
from haiku.rag.qa.deep.state import DeepQADeps, DeepQAState
|
from haiku.rag.qa.deep.state import DeepQADeps, DeepQAState
|
||||||
|
|
||||||
graph = build_deep_qa_graph(
|
graph = build_deep_qa_graph(config=Config)
|
||||||
provider=Config.qa.provider,
|
|
||||||
model=Config.qa.model,
|
|
||||||
)
|
|
||||||
context = DeepQAContext(
|
context = DeepQAContext(
|
||||||
original_question=question, use_citations=cite
|
original_question=question, use_citations=cite
|
||||||
)
|
)
|
||||||
state = DeepQAState(context=context)
|
state = DeepQAState.from_config(context=context, config=Config)
|
||||||
deps = DeepQADeps(
|
deps = DeepQADeps(
|
||||||
client=self.client, console=Console() if verbose else None
|
client=self.client, console=Console() if verbose else None
|
||||||
)
|
)
|
||||||
|
|
@ -240,28 +246,26 @@ class HaikuRAGApp:
|
||||||
async def research(
|
async def research(
|
||||||
self,
|
self,
|
||||||
question: str,
|
question: str,
|
||||||
max_iterations: int = 3,
|
|
||||||
confidence_threshold: float = 0.8,
|
|
||||||
verbose: bool = False,
|
verbose: bool = False,
|
||||||
):
|
):
|
||||||
"""Run research via the pydantic-graph pipeline (default)."""
|
"""Run research via the pydantic-graph pipeline.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
question: The research question
|
||||||
|
verbose: Show verbose output
|
||||||
|
"""
|
||||||
async with HaikuRAG(db_path=self.db_path) as client:
|
async with HaikuRAG(db_path=self.db_path) as client:
|
||||||
try:
|
try:
|
||||||
|
from haiku.rag.config import Config
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
self.console.print("[bold cyan]Starting research[/bold cyan]")
|
self.console.print("[bold cyan]Starting research[/bold cyan]")
|
||||||
self.console.print(f"[bold blue]Question:[/bold blue] {question}")
|
self.console.print(f"[bold blue]Question:[/bold blue] {question}")
|
||||||
self.console.print()
|
self.console.print()
|
||||||
|
|
||||||
graph = build_research_graph(
|
graph = build_research_graph(config=Config)
|
||||||
provider=Config.research.provider or Config.qa.provider,
|
|
||||||
model=Config.research.model or Config.qa.model,
|
|
||||||
)
|
|
||||||
context = ResearchContext(original_question=question)
|
context = ResearchContext(original_question=question)
|
||||||
state = ResearchState(
|
state = ResearchState.from_config(context=context, config=Config)
|
||||||
context=context,
|
|
||||||
max_iterations=max_iterations,
|
|
||||||
confidence_threshold=confidence_threshold,
|
|
||||||
)
|
|
||||||
deps = ResearchDeps(
|
deps = ResearchDeps(
|
||||||
client=client, console=self.console if verbose else None
|
client=client, console=self.console if verbose else None
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -290,17 +290,6 @@ def research(
|
||||||
question: str = typer.Argument(
|
question: str = typer.Argument(
|
||||||
help="The research question to investigate",
|
help="The research question to investigate",
|
||||||
),
|
),
|
||||||
max_iterations: int = typer.Option(
|
|
||||||
3,
|
|
||||||
"--max-iterations",
|
|
||||||
"-n",
|
|
||||||
help="Maximum search/analyze iterations",
|
|
||||||
),
|
|
||||||
confidence_threshold: float = typer.Option(
|
|
||||||
0.8,
|
|
||||||
"--confidence-threshold",
|
|
||||||
help="Minimum confidence (0-1) to stop",
|
|
||||||
),
|
|
||||||
db: Path = typer.Option(
|
db: Path = typer.Option(
|
||||||
Config.storage.data_dir / "haiku.rag.lancedb",
|
Config.storage.data_dir / "haiku.rag.lancedb",
|
||||||
"--db",
|
"--db",
|
||||||
|
|
@ -315,14 +304,7 @@ def research(
|
||||||
from haiku.rag.app import HaikuRAGApp
|
from haiku.rag.app import HaikuRAGApp
|
||||||
|
|
||||||
app = HaikuRAGApp(db_path=db)
|
app = HaikuRAGApp(db_path=db)
|
||||||
asyncio.run(
|
asyncio.run(app.research(question=question, verbose=verbose))
|
||||||
app.research(
|
|
||||||
question=question,
|
|
||||||
max_iterations=max_iterations,
|
|
||||||
confidence_threshold=confidence_threshold,
|
|
||||||
verbose=verbose,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("settings", help="Display current configuration settings")
|
@cli.command("settings", help="Display current configuration settings")
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,17 @@ class RerankingConfig(BaseModel):
|
||||||
class QAConfig(BaseModel):
|
class QAConfig(BaseModel):
|
||||||
provider: str = "ollama"
|
provider: str = "ollama"
|
||||||
model: str = "gpt-oss"
|
model: str = "gpt-oss"
|
||||||
|
max_sub_questions: int = 3
|
||||||
|
max_iterations: int = 2
|
||||||
|
max_concurrency: int = 1
|
||||||
|
|
||||||
|
|
||||||
class ResearchConfig(BaseModel):
|
class ResearchConfig(BaseModel):
|
||||||
provider: str = "ollama"
|
provider: str = "ollama"
|
||||||
model: str = "gpt-oss"
|
model: str = "gpt-oss"
|
||||||
|
max_iterations: int = 3
|
||||||
|
confidence_threshold: float = 0.8
|
||||||
|
max_concurrency: int = 1
|
||||||
|
|
||||||
|
|
||||||
class ProcessingConfig(BaseModel):
|
class ProcessingConfig(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
from haiku.rag.client import HaikuRAG
|
|
||||||
from haiku.rag.research.models import ResearchReport
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from haiku.rag.client import HaikuRAG
|
||||||
from haiku.rag.config import Config
|
from haiku.rag.config import Config
|
||||||
|
from haiku.rag.research.models import ResearchReport
|
||||||
|
|
||||||
|
|
||||||
class SearchResult(BaseModel):
|
class SearchResult(BaseModel):
|
||||||
|
|
@ -191,20 +191,16 @@ def create_mcp_server(db_path: Path) -> FastMCP:
|
||||||
try:
|
try:
|
||||||
async with HaikuRAG(db_path) as rag:
|
async with HaikuRAG(db_path) as rag:
|
||||||
if deep:
|
if deep:
|
||||||
|
from haiku.rag.config import Config
|
||||||
from haiku.rag.qa.deep.dependencies import DeepQAContext
|
from haiku.rag.qa.deep.dependencies import DeepQAContext
|
||||||
from haiku.rag.qa.deep.graph import build_deep_qa_graph
|
from haiku.rag.qa.deep.graph import build_deep_qa_graph
|
||||||
from haiku.rag.qa.deep.state import DeepQADeps, DeepQAState
|
from haiku.rag.qa.deep.state import DeepQADeps, DeepQAState
|
||||||
|
|
||||||
from haiku.rag.config import Config
|
graph = build_deep_qa_graph(config=Config)
|
||||||
|
|
||||||
graph = build_deep_qa_graph(
|
|
||||||
provider=Config.qa.provider,
|
|
||||||
model=Config.qa.model,
|
|
||||||
)
|
|
||||||
context = DeepQAContext(
|
context = DeepQAContext(
|
||||||
original_question=question, use_citations=cite
|
original_question=question, use_citations=cite
|
||||||
)
|
)
|
||||||
state = DeepQAState(context=context)
|
state = DeepQAState.from_config(context=context, config=Config)
|
||||||
deps = DeepQADeps(client=rag)
|
deps = DeepQADeps(client=rag)
|
||||||
|
|
||||||
result = await graph.run(state=state, deps=deps)
|
result = await graph.run(state=state, deps=deps)
|
||||||
|
|
@ -218,9 +214,6 @@ def create_mcp_server(db_path: Path) -> FastMCP:
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def research_question(
|
async def research_question(
|
||||||
question: str,
|
question: str,
|
||||||
max_iterations: int = 3,
|
|
||||||
confidence_threshold: float = 0.8,
|
|
||||||
max_concurrency: int = 1,
|
|
||||||
) -> ResearchReport | None:
|
) -> ResearchReport | None:
|
||||||
"""Run multi-agent research to investigate a complex question.
|
"""Run multi-agent research to investigate a complex question.
|
||||||
|
|
||||||
|
|
@ -229,9 +222,6 @@ def create_mcp_server(db_path: Path) -> FastMCP:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
question: The research question to investigate.
|
question: The research question to investigate.
|
||||||
max_iterations: Maximum search/analyze iterations (default: 3).
|
|
||||||
confidence_threshold: Minimum confidence score (0-1) to stop early (default: 0.8).
|
|
||||||
max_concurrency: Maximum concurrent sub-questions to process (default: 1).
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A research report with findings, or None if an error occurred.
|
A research report with findings, or None if an error occurred.
|
||||||
|
|
@ -242,16 +232,9 @@ def create_mcp_server(db_path: Path) -> FastMCP:
|
||||||
from haiku.rag.research.state import ResearchDeps, ResearchState
|
from haiku.rag.research.state import ResearchDeps, ResearchState
|
||||||
|
|
||||||
async with HaikuRAG(db_path) as rag:
|
async with HaikuRAG(db_path) as rag:
|
||||||
graph = build_research_graph(
|
graph = build_research_graph(config=Config)
|
||||||
provider=Config.research.provider or Config.qa.provider,
|
context = ResearchContext(original_question=question)
|
||||||
model=Config.research.model or Config.qa.model,
|
state = ResearchState.from_config(context=context, config=Config)
|
||||||
)
|
|
||||||
state = ResearchState(
|
|
||||||
context=ResearchContext(original_question=question),
|
|
||||||
max_iterations=max_iterations,
|
|
||||||
confidence_threshold=confidence_threshold,
|
|
||||||
max_concurrency=max_concurrency,
|
|
||||||
)
|
|
||||||
deps = ResearchDeps(client=rag)
|
deps = ResearchDeps(client=rag)
|
||||||
|
|
||||||
result = await graph.run(state=state, deps=deps)
|
result = await graph.run(state=state, deps=deps)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic_ai import Agent, RunContext
|
||||||
|
from pydantic_ai.format_prompt import format_as_xml
|
||||||
|
from pydantic_ai.output import ToolOutput
|
||||||
|
from pydantic_graph.beta import Graph, GraphBuilder, StepContext
|
||||||
|
from pydantic_graph.beta.join import reduce_list_append
|
||||||
|
|
||||||
|
from haiku.rag.config import Config
|
||||||
|
from haiku.rag.config.models import AppConfig
|
||||||
from haiku.rag.graph_common import get_model, log
|
from haiku.rag.graph_common import get_model, log
|
||||||
from haiku.rag.graph_common.models import ResearchPlan, SearchAnswer
|
from haiku.rag.graph_common.models import ResearchPlan, SearchAnswer
|
||||||
from haiku.rag.graph_common.prompts import PLAN_PROMPT, SEARCH_AGENT_PROMPT
|
from haiku.rag.graph_common.prompts import PLAN_PROMPT, SEARCH_AGENT_PROMPT
|
||||||
|
|
@ -11,16 +19,21 @@ from haiku.rag.qa.deep.prompts import (
|
||||||
SYNTHESIS_PROMPT_WITH_CITATIONS,
|
SYNTHESIS_PROMPT_WITH_CITATIONS,
|
||||||
)
|
)
|
||||||
from haiku.rag.qa.deep.state import DeepQADeps, DeepQAState
|
from haiku.rag.qa.deep.state import DeepQADeps, DeepQAState
|
||||||
from pydantic_ai import Agent, RunContext
|
|
||||||
from pydantic_ai.format_prompt import format_as_xml
|
|
||||||
from pydantic_ai.output import ToolOutput
|
|
||||||
from pydantic_graph.beta import Graph, GraphBuilder, StepContext
|
|
||||||
from pydantic_graph.beta.join import reduce_list_append
|
|
||||||
|
|
||||||
|
|
||||||
def build_deep_qa_graph(
|
def build_deep_qa_graph(
|
||||||
provider: str, model: str
|
config: AppConfig = Config,
|
||||||
) -> Graph[DeepQAState, DeepQADeps, None, DeepQAAnswer]:
|
) -> Graph[DeepQAState, DeepQADeps, None, DeepQAAnswer]:
|
||||||
|
"""Build the Deep QA graph.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: AppConfig object (uses config.qa for provider, model, and graph parameters)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Configured Deep QA graph
|
||||||
|
"""
|
||||||
|
provider = config.qa.provider
|
||||||
|
model = config.qa.model
|
||||||
g = GraphBuilder(
|
g = GraphBuilder(
|
||||||
state_type=DeepQAState,
|
state_type=DeepQAState,
|
||||||
deps_type=DeepQADeps,
|
deps_type=DeepQADeps,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
from haiku.rag.client import HaikuRAG
|
from haiku.rag.client import HaikuRAG
|
||||||
from haiku.rag.qa.deep.dependencies import DeepQAContext
|
from haiku.rag.qa.deep.dependencies import DeepQAContext
|
||||||
from rich.console import Console
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from haiku.rag.config.models import AppConfig
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -24,3 +29,21 @@ class DeepQAState:
|
||||||
max_iterations: int = 2
|
max_iterations: int = 2
|
||||||
max_concurrency: int = 1
|
max_concurrency: int = 1
|
||||||
iterations: int = 0
|
iterations: int = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_config(cls, context: DeepQAContext, config: "AppConfig") -> "DeepQAState":
|
||||||
|
"""Create a DeepQAState from an AppConfig.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
context: The DeepQAContext containing the question and settings
|
||||||
|
config: The AppConfig object (uses config.qa for state parameters)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A configured DeepQAState instance
|
||||||
|
"""
|
||||||
|
return cls(
|
||||||
|
context=context,
|
||||||
|
max_sub_questions=config.qa.max_sub_questions,
|
||||||
|
max_iterations=config.qa.max_iterations,
|
||||||
|
max_concurrency=config.qa.max_concurrency,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic_ai import Agent, RunContext
|
||||||
|
from pydantic_ai.format_prompt import format_as_xml
|
||||||
|
from pydantic_ai.output import ToolOutput
|
||||||
|
from pydantic_graph.beta import Graph, GraphBuilder, StepContext
|
||||||
|
from pydantic_graph.beta.join import reduce_list_append
|
||||||
|
|
||||||
|
from haiku.rag.config import Config
|
||||||
|
from haiku.rag.config.models import AppConfig
|
||||||
from haiku.rag.graph_common import get_model, log
|
from haiku.rag.graph_common import get_model, log
|
||||||
from haiku.rag.graph_common.models import ResearchPlan, SearchAnswer
|
from haiku.rag.graph_common.models import ResearchPlan, SearchAnswer
|
||||||
from haiku.rag.graph_common.prompts import PLAN_PROMPT, SEARCH_AGENT_PROMPT
|
from haiku.rag.graph_common.prompts import PLAN_PROMPT, SEARCH_AGENT_PROMPT
|
||||||
|
|
@ -19,16 +27,21 @@ from haiku.rag.research.prompts import (
|
||||||
SYNTHESIS_AGENT_PROMPT,
|
SYNTHESIS_AGENT_PROMPT,
|
||||||
)
|
)
|
||||||
from haiku.rag.research.state import ResearchDeps, ResearchState
|
from haiku.rag.research.state import ResearchDeps, ResearchState
|
||||||
from pydantic_ai import Agent, RunContext
|
|
||||||
from pydantic_ai.format_prompt import format_as_xml
|
|
||||||
from pydantic_ai.output import ToolOutput
|
|
||||||
from pydantic_graph.beta import Graph, GraphBuilder, StepContext
|
|
||||||
from pydantic_graph.beta.join import reduce_list_append
|
|
||||||
|
|
||||||
|
|
||||||
def build_research_graph(
|
def build_research_graph(
|
||||||
provider: str, model: str
|
config: AppConfig = Config,
|
||||||
) -> Graph[ResearchState, ResearchDeps, None, ResearchReport]:
|
) -> Graph[ResearchState, ResearchDeps, None, ResearchReport]:
|
||||||
|
"""Build the Research graph.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: AppConfig object (uses config.research for provider, model, and graph parameters)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Configured Research graph
|
||||||
|
"""
|
||||||
|
provider = config.research.provider
|
||||||
|
model = config.research.model
|
||||||
g = GraphBuilder(
|
g = GraphBuilder(
|
||||||
state_type=ResearchState,
|
state_type=ResearchState,
|
||||||
deps_type=ResearchDeps,
|
deps_type=ResearchDeps,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
from haiku.rag.client import HaikuRAG
|
from haiku.rag.client import HaikuRAG
|
||||||
from haiku.rag.research.dependencies import ResearchContext
|
from haiku.rag.research.dependencies import ResearchContext
|
||||||
from haiku.rag.research.models import EvaluationResult, InsightAnalysis
|
from haiku.rag.research.models import EvaluationResult, InsightAnalysis
|
||||||
from haiku.rag.research.stream import ResearchStream
|
from haiku.rag.research.stream import ResearchStream
|
||||||
from rich.console import Console
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from haiku.rag.config.models import AppConfig
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -31,3 +36,23 @@ class ResearchState:
|
||||||
max_concurrency: int = 1
|
max_concurrency: int = 1
|
||||||
last_eval: EvaluationResult | None = None
|
last_eval: EvaluationResult | None = None
|
||||||
last_analysis: InsightAnalysis | None = None
|
last_analysis: InsightAnalysis | None = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_config(
|
||||||
|
cls, context: ResearchContext, config: "AppConfig"
|
||||||
|
) -> "ResearchState":
|
||||||
|
"""Create a ResearchState from an AppConfig.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
context: The ResearchContext containing the question and settings
|
||||||
|
config: The AppConfig object (uses config.research for state parameters)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A configured ResearchState instance
|
||||||
|
"""
|
||||||
|
return cls(
|
||||||
|
context=context,
|
||||||
|
max_iterations=config.research.max_iterations,
|
||||||
|
confidence_threshold=config.research.confidence_threshold,
|
||||||
|
max_concurrency=config.research.max_concurrency,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ async def test_deep_qa_graph_end_to_end(monkeypatch, temp_db_path):
|
||||||
monkeypatch.setattr("haiku.rag.graph_common.utils.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.graph_common.utils.get_model", test_model_factory)
|
||||||
monkeypatch.setattr("haiku.rag.qa.deep.graph.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.qa.deep.graph.get_model", test_model_factory)
|
||||||
|
|
||||||
graph = build_deep_qa_graph(provider="test", model="test")
|
graph = build_deep_qa_graph()
|
||||||
|
|
||||||
state = DeepQAState(
|
state = DeepQAState(
|
||||||
context=DeepQAContext(
|
context=DeepQAContext(
|
||||||
|
|
@ -53,7 +53,7 @@ async def test_deep_qa_with_citations(monkeypatch, temp_db_path):
|
||||||
monkeypatch.setattr("haiku.rag.graph_common.utils.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.graph_common.utils.get_model", test_model_factory)
|
||||||
monkeypatch.setattr("haiku.rag.qa.deep.graph.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.qa.deep.graph.get_model", test_model_factory)
|
||||||
|
|
||||||
graph = build_deep_qa_graph(provider="test", model="test")
|
graph = build_deep_qa_graph()
|
||||||
|
|
||||||
state = DeepQAState(
|
state = DeepQAState(
|
||||||
context=DeepQAContext(original_question="What is Python?", use_citations=True),
|
context=DeepQAContext(original_question="What is Python?", use_citations=True),
|
||||||
|
|
|
||||||
|
|
@ -309,9 +309,6 @@ async def test_mcp_research_question():
|
||||||
|
|
||||||
result = await research_tool.fn( # type: ignore[attr-defined]
|
result = await research_tool.fn( # type: ignore[attr-defined]
|
||||||
question="Research question?",
|
question="Research question?",
|
||||||
max_iterations=1,
|
|
||||||
confidence_threshold=0.5,
|
|
||||||
max_concurrency=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result is not None
|
assert result is not None
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from haiku.rag.research.state import ResearchState
|
||||||
|
|
||||||
|
|
||||||
def test_build_graph_and_state():
|
def test_build_graph_and_state():
|
||||||
graph = build_research_graph(provider="openai", model="gpt-4")
|
graph = build_research_graph()
|
||||||
assert graph is not None
|
assert graph is not None
|
||||||
|
|
||||||
state = ResearchState(
|
state = ResearchState(
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ async def test_graph_end_to_end_with_test_model(monkeypatch, temp_db_path):
|
||||||
monkeypatch.setattr("haiku.rag.graph_common.utils.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.graph_common.utils.get_model", test_model_factory)
|
||||||
monkeypatch.setattr("haiku.rag.research.graph.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.research.graph.get_model", test_model_factory)
|
||||||
|
|
||||||
graph = build_research_graph(provider="test", model="test")
|
graph = build_research_graph()
|
||||||
|
|
||||||
state = ResearchState(
|
state = ResearchState(
|
||||||
context=ResearchContext(original_question="What is haiku.rag?"),
|
context=ResearchContext(original_question="What is haiku.rag?"),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue