Merge pull request #151 from ggozad/fix/graph-config
Allow custom config when building research/deep ask graphs.
This commit is contained in:
commit
a4b82982c4
7 changed files with 33 additions and 13 deletions
|
|
@ -4,6 +4,10 @@
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **AG-UI Activity Events**: Activity events now correctly use structured dict content instead of strings
|
- **AG-UI Activity Events**: Activity events now correctly use structured dict content instead of strings
|
||||||
|
- **Graph Configuration**: Graph builder functions now properly accept and use non-global config (#149)
|
||||||
|
- `build_research_graph()` and `build_deep_qa_graph()` now pass config to all agents and model creation
|
||||||
|
- `get_model()` utility function accepts `config` parameter (defaults to global Config)
|
||||||
|
- Allows creating multiple graphs with different configurations in the same application
|
||||||
|
|
||||||
## [0.17.2] - 2025-11-19
|
## [0.17.2] - 2025-11-19
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ from pydantic_ai.output import ToolOutput
|
||||||
from pydantic_graph.beta import StepContext
|
from pydantic_graph.beta import StepContext
|
||||||
|
|
||||||
from haiku.rag.client import HaikuRAG
|
from haiku.rag.client import HaikuRAG
|
||||||
|
from haiku.rag.config import Config
|
||||||
|
from haiku.rag.config.models import AppConfig
|
||||||
from haiku.rag.graph.agui.emitter import AGUIEmitter
|
from haiku.rag.graph.agui.emitter import AGUIEmitter
|
||||||
from haiku.rag.graph.common import get_model
|
from haiku.rag.graph.common import get_model
|
||||||
from haiku.rag.graph.common.models import ResearchPlan, SearchAnswer
|
from haiku.rag.graph.common.models import ResearchPlan, SearchAnswer
|
||||||
|
|
@ -55,6 +57,7 @@ def create_plan_node[AgentDepsT: GraphAgentDeps](
|
||||||
deps_type: type[AgentDepsT],
|
deps_type: type[AgentDepsT],
|
||||||
activity_message: str = "Creating plan",
|
activity_message: str = "Creating plan",
|
||||||
output_retries: int | None = None,
|
output_retries: int | None = None,
|
||||||
|
config: AppConfig = Config,
|
||||||
) -> Callable[[StepContext[Any, Any, None]], Awaitable[None]]:
|
) -> Callable[[StepContext[Any, Any, None]], Awaitable[None]]:
|
||||||
"""Create a plan node for any graph.
|
"""Create a plan node for any graph.
|
||||||
|
|
||||||
|
|
@ -64,6 +67,7 @@ def create_plan_node[AgentDepsT: GraphAgentDeps](
|
||||||
deps_type: Type of dependencies for the agent (e.g., ResearchDependencies, DeepQADependencies)
|
deps_type: Type of dependencies for the agent (e.g., ResearchDependencies, DeepQADependencies)
|
||||||
activity_message: Message to show during planning activity
|
activity_message: Message to show during planning activity
|
||||||
output_retries: Number of output retries for the agent (optional)
|
output_retries: Number of output retries for the agent (optional)
|
||||||
|
config: AppConfig object (defaults to global Config)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Async function that can be used as a graph step
|
Async function that can be used as a graph step
|
||||||
|
|
@ -80,7 +84,7 @@ def create_plan_node[AgentDepsT: GraphAgentDeps](
|
||||||
try:
|
try:
|
||||||
# Build agent configuration
|
# Build agent configuration
|
||||||
agent_config = {
|
agent_config = {
|
||||||
"model": get_model(provider, model),
|
"model": get_model(provider, model, config),
|
||||||
"output_type": ResearchPlan,
|
"output_type": ResearchPlan,
|
||||||
"instructions": (
|
"instructions": (
|
||||||
PLAN_PROMPT
|
PLAN_PROMPT
|
||||||
|
|
@ -136,6 +140,7 @@ def create_search_node[AgentDepsT: GraphAgentDeps](
|
||||||
with_step_wrapper: bool = True,
|
with_step_wrapper: bool = True,
|
||||||
success_message_format: str = "Answered: {sub_q}",
|
success_message_format: str = "Answered: {sub_q}",
|
||||||
handle_exceptions: bool = False,
|
handle_exceptions: bool = False,
|
||||||
|
config: AppConfig = Config,
|
||||||
) -> Callable[[StepContext[Any, Any, str]], Awaitable[SearchAnswer]]:
|
) -> Callable[[StepContext[Any, Any, str]], Awaitable[SearchAnswer]]:
|
||||||
"""Create a search_one node for any graph.
|
"""Create a search_one node for any graph.
|
||||||
|
|
||||||
|
|
@ -146,6 +151,7 @@ def create_search_node[AgentDepsT: GraphAgentDeps](
|
||||||
with_step_wrapper: Whether to wrap with agui_emitter start/finish step
|
with_step_wrapper: Whether to wrap with agui_emitter start/finish step
|
||||||
success_message_format: Format string for success activity message
|
success_message_format: Format string for success activity message
|
||||||
handle_exceptions: Whether to handle exceptions with fallback answer
|
handle_exceptions: Whether to handle exceptions with fallback answer
|
||||||
|
config: AppConfig object (defaults to global Config)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Async function that can be used as a graph step
|
Async function that can be used as a graph step
|
||||||
|
|
@ -178,6 +184,7 @@ def create_search_node[AgentDepsT: GraphAgentDeps](
|
||||||
deps_type,
|
deps_type,
|
||||||
success_message_format,
|
success_message_format,
|
||||||
handle_exceptions,
|
handle_exceptions,
|
||||||
|
config,
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
if deps.agui_emitter and with_step_wrapper:
|
if deps.agui_emitter and with_step_wrapper:
|
||||||
|
|
@ -195,6 +202,7 @@ async def _do_search[AgentDepsT: GraphAgentDeps](
|
||||||
deps_type: type[AgentDepsT],
|
deps_type: type[AgentDepsT],
|
||||||
success_message_format: str,
|
success_message_format: str,
|
||||||
handle_exceptions: bool,
|
handle_exceptions: bool,
|
||||||
|
config: AppConfig,
|
||||||
) -> SearchAnswer:
|
) -> SearchAnswer:
|
||||||
"""Internal search implementation."""
|
"""Internal search implementation."""
|
||||||
if deps.agui_emitter:
|
if deps.agui_emitter:
|
||||||
|
|
@ -203,7 +211,7 @@ async def _do_search[AgentDepsT: GraphAgentDeps](
|
||||||
)
|
)
|
||||||
|
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
model=get_model(provider, model),
|
model=get_model(provider, model, config),
|
||||||
output_type=ToolOutput(SearchAnswer, max_retries=3),
|
output_type=ToolOutput(SearchAnswer, max_retries=3),
|
||||||
instructions=SEARCH_AGENT_PROMPT,
|
instructions=SEARCH_AGENT_PROMPT,
|
||||||
retries=3,
|
retries=3,
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,19 @@ from pydantic_ai.providers.ollama import OllamaProvider
|
||||||
from pydantic_ai.providers.openai import OpenAIProvider
|
from pydantic_ai.providers.openai import OpenAIProvider
|
||||||
|
|
||||||
from haiku.rag.config import Config
|
from haiku.rag.config import Config
|
||||||
|
from haiku.rag.config.models import AppConfig
|
||||||
|
|
||||||
|
|
||||||
def get_model(provider: str, model: str) -> OpenAIChatModel | str:
|
def get_model(
|
||||||
|
provider: str, model: str, config: AppConfig = Config
|
||||||
|
) -> OpenAIChatModel | str:
|
||||||
"""
|
"""
|
||||||
Get a model instance for the specified provider and model name.
|
Get a model instance for the specified provider and model name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
provider: The model provider ("ollama", "vllm", or other)
|
provider: The model provider ("ollama", "vllm", or other)
|
||||||
model: The model name
|
model: The model name
|
||||||
|
config: AppConfig object (defaults to global Config)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A configured model instance
|
A configured model instance
|
||||||
|
|
@ -24,13 +28,13 @@ def get_model(provider: str, model: str) -> OpenAIChatModel | str:
|
||||||
if provider == "ollama":
|
if provider == "ollama":
|
||||||
return OpenAIChatModel(
|
return OpenAIChatModel(
|
||||||
model_name=model,
|
model_name=model,
|
||||||
provider=OllamaProvider(base_url=f"{Config.providers.ollama.base_url}/v1"),
|
provider=OllamaProvider(base_url=f"{config.providers.ollama.base_url}/v1"),
|
||||||
)
|
)
|
||||||
elif provider == "vllm":
|
elif provider == "vllm":
|
||||||
return OpenAIChatModel(
|
return OpenAIChatModel(
|
||||||
model_name=model,
|
model_name=model,
|
||||||
provider=OpenAIProvider(
|
provider=OpenAIProvider(
|
||||||
base_url=f"{Config.providers.vllm.research_base_url or Config.providers.vllm.qa_base_url}/v1",
|
base_url=f"{config.providers.vllm.research_base_url or config.providers.vllm.qa_base_url}/v1",
|
||||||
api_key="none",
|
api_key="none",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ def build_deep_qa_graph(
|
||||||
deps_type=DeepQADependencies, # type: ignore[arg-type]
|
deps_type=DeepQADependencies, # type: ignore[arg-type]
|
||||||
activity_message="Planning approach",
|
activity_message="Planning approach",
|
||||||
output_retries=None, # Deep QA doesn't use output_retries
|
output_retries=None, # Deep QA doesn't use output_retries
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
) # type: ignore[arg-type]
|
) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
@ -57,6 +58,7 @@ def build_deep_qa_graph(
|
||||||
with_step_wrapper=False, # Deep QA doesn't wrap with agui_emitter step
|
with_step_wrapper=False, # Deep QA doesn't wrap with agui_emitter step
|
||||||
success_message_format="Answered: {sub_q}",
|
success_message_format="Answered: {sub_q}",
|
||||||
handle_exceptions=True,
|
handle_exceptions=True,
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
) # type: ignore[arg-type]
|
) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
@ -90,7 +92,7 @@ def build_deep_qa_graph(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
model=get_model(provider, model),
|
model=get_model(provider, model, config),
|
||||||
output_type=DeepQAEvaluation,
|
output_type=DeepQAEvaluation,
|
||||||
instructions=DECISION_PROMPT,
|
instructions=DECISION_PROMPT,
|
||||||
retries=3,
|
retries=3,
|
||||||
|
|
@ -168,7 +170,7 @@ def build_deep_qa_graph(
|
||||||
)
|
)
|
||||||
|
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
model=get_model(provider, model),
|
model=get_model(provider, model, config),
|
||||||
output_type=DeepQAAnswer,
|
output_type=DeepQAAnswer,
|
||||||
instructions=prompt_template,
|
instructions=prompt_template,
|
||||||
retries=3,
|
retries=3,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ def build_research_graph(
|
||||||
deps_type=ResearchDependencies, # type: ignore[arg-type]
|
deps_type=ResearchDependencies, # type: ignore[arg-type]
|
||||||
activity_message="Creating research plan",
|
activity_message="Creating research plan",
|
||||||
output_retries=3,
|
output_retries=3,
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
) # type: ignore[arg-type]
|
) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
@ -64,6 +65,7 @@ def build_research_graph(
|
||||||
with_step_wrapper=True,
|
with_step_wrapper=True,
|
||||||
success_message_format="Found answer with {confidence:.0%} confidence",
|
success_message_format="Found answer with {confidence:.0%} confidence",
|
||||||
handle_exceptions=True,
|
handle_exceptions=True,
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
) # type: ignore[arg-type]
|
) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
@ -97,7 +99,7 @@ def build_research_graph(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
model=get_model(provider, model),
|
model=get_model(provider, model, config),
|
||||||
output_type=InsightAnalysis,
|
output_type=InsightAnalysis,
|
||||||
instructions=INSIGHT_AGENT_PROMPT,
|
instructions=INSIGHT_AGENT_PROMPT,
|
||||||
retries=3,
|
retries=3,
|
||||||
|
|
@ -155,7 +157,7 @@ def build_research_graph(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
model=get_model(provider, model),
|
model=get_model(provider, model, config),
|
||||||
output_type=EvaluationResult,
|
output_type=EvaluationResult,
|
||||||
instructions=DECISION_AGENT_PROMPT,
|
instructions=DECISION_AGENT_PROMPT,
|
||||||
retries=3,
|
retries=3,
|
||||||
|
|
@ -231,7 +233,7 @@ def build_research_graph(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
model=get_model(provider, model),
|
model=get_model(provider, model, config),
|
||||||
output_type=ResearchReport,
|
output_type=ResearchReport,
|
||||||
instructions=SYNTHESIS_AGENT_PROMPT,
|
instructions=SYNTHESIS_AGENT_PROMPT,
|
||||||
retries=3,
|
retries=3,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ async def test_deep_qa_graph_end_to_end(monkeypatch, temp_db_path):
|
||||||
"""Test deep Q&A graph with mocked LLM using TestModel."""
|
"""Test deep Q&A graph with mocked LLM using TestModel."""
|
||||||
|
|
||||||
# Mock get_model to return TestModel which generates valid schema-compliant data
|
# Mock get_model to return TestModel which generates valid schema-compliant data
|
||||||
def test_model_factory(provider, model):
|
def test_model_factory(provider, model, config=None):
|
||||||
return TestModel()
|
return TestModel()
|
||||||
|
|
||||||
monkeypatch.setattr("haiku.rag.graph.common.utils.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.graph.common.utils.get_model", test_model_factory)
|
||||||
|
|
@ -47,7 +47,7 @@ async def test_deep_qa_with_citations(monkeypatch, temp_db_path):
|
||||||
"""Test deep Q&A with citations enabled using TestModel."""
|
"""Test deep Q&A with citations enabled using TestModel."""
|
||||||
|
|
||||||
# Mock get_model to return TestModel
|
# Mock get_model to return TestModel
|
||||||
def test_model_factory(provider, model):
|
def test_model_factory(provider, model, config=None):
|
||||||
return TestModel()
|
return TestModel()
|
||||||
|
|
||||||
monkeypatch.setattr("haiku.rag.graph.common.utils.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.graph.common.utils.get_model", test_model_factory)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ async def test_graph_end_to_end_with_test_model(monkeypatch, temp_db_path):
|
||||||
"""Test research graph with mocked LLM using AG-UI events."""
|
"""Test research graph with mocked LLM using AG-UI events."""
|
||||||
|
|
||||||
# Mock get_model to return TestModel which generates valid schema-compliant data
|
# Mock get_model to return TestModel which generates valid schema-compliant data
|
||||||
def test_model_factory(_provider, _model):
|
def test_model_factory(_provider, _model, _config=None):
|
||||||
return TestModel()
|
return TestModel()
|
||||||
|
|
||||||
monkeypatch.setattr("haiku.rag.graph.common.utils.get_model", test_model_factory)
|
monkeypatch.setattr("haiku.rag.graph.common.utils.get_model", test_model_factory)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue