From aa9fdf051dcde71f40f3cbb3fc8d2b45f6b231e1 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Tue, 24 Mar 2026 14:04:57 +0200 Subject: [PATCH] Remove support for structured output --- CHANGELOG.md | 1 + haiku_rag_slim/haiku/rag/agents/qa/agent.py | 4 +-- .../haiku/rag/agents/research/graph.py | 8 ++--- haiku_rag_slim/haiku/rag/agents/rlm/agent.py | 4 +-- haiku_rag_slim/haiku/rag/utils.py | 14 -------- tests/agents/rlm/test_agent.py | 17 ++-------- tests/test_utils.py | 32 ------------------- 7 files changed, 11 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e4966a..5ff4a10e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - **LLMJudge**: Custom evaluator now accepts `ModelConfig` instead of a model name string - **Docling upgrade**: docling-core ≥2.70.2 (schema 1.10.0), docling ≥2.81.0. Adds field data model support for structured form/KV content, wide table chunking fixes, and rich table cell hang fix +- **pydantic-ai ≥1.70.0**: Bumped minimum version. Removed `structured_output_type` helper — all supported providers now handle native structured output, so agents pass result types directly ## [0.34.1] - 2026-03-16 diff --git a/haiku_rag_slim/haiku/rag/agents/qa/agent.py b/haiku_rag_slim/haiku/rag/agents/qa/agent.py index 48ddb0ea..78a35448 100644 --- a/haiku_rag_slim/haiku/rag/agents/qa/agent.py +++ b/haiku_rag_slim/haiku/rag/agents/qa/agent.py @@ -13,7 +13,7 @@ from haiku.rag.config import Config from haiku.rag.config.models import AppConfig, ModelConfig from haiku.rag.store.models import SearchResult from haiku.rag.tools.search import create_search_toolset -from haiku.rag.utils import get_model, structured_output_type +from haiku.rag.utils import get_model @dataclass @@ -66,7 +66,7 @@ class QuestionAnswerAgent: agent: Agent[_QARunDeps, RawSearchAnswer] = Agent( # ty: ignore[invalid-assignment] model=model, deps_type=_QARunDeps, - output_type=structured_output_type(RawSearchAnswer, model), + output_type=RawSearchAnswer, instructions=system_prompt, toolsets=[search_toolset], retries=3, diff --git a/haiku_rag_slim/haiku/rag/agents/research/graph.py b/haiku_rag_slim/haiku/rag/agents/research/graph.py index 0278e3c0..320b9d7a 100644 --- a/haiku_rag_slim/haiku/rag/agents/research/graph.py +++ b/haiku_rag_slim/haiku/rag/agents/research/graph.py @@ -19,7 +19,7 @@ from haiku.rag.agents.research.prompts import ( from haiku.rag.agents.research.state import ResearchDeps, ResearchState from haiku.rag.config import Config from haiku.rag.config.models import AppConfig -from haiku.rag.utils import build_prompt, get_model, structured_output_type +from haiku.rag.utils import build_prompt, get_model def format_context_for_prompt(context: ResearchContext) -> str: @@ -68,7 +68,7 @@ async def _iterative_plan_logic( model = get_model(model_config, config) plan_agent: Agent[ResearchDependencies, IterativePlanResult] = Agent( # type: ignore[assignment] model=model, - output_type=structured_output_type(IterativePlanResult, model), + output_type=IterativePlanResult, instructions=effective_prompt, retries=3, deps_type=ResearchDependencies, @@ -118,7 +118,7 @@ async def _search_one_step_logic( model = get_model(model_config, config) agent: Agent[ResearchDependencies, RawSearchAnswer] = Agent( # type: ignore[assignment] model=model, - output_type=structured_output_type(RawSearchAnswer, model), + output_type=RawSearchAnswer, instructions=search_prompt, retries=3, deps_type=ResearchDependencies, @@ -220,7 +220,7 @@ def build_research_graph( model = get_model(model_config, config) agent: Agent[ResearchDependencies, ResearchReport] = Agent( # type: ignore[assignment] model=model, - output_type=structured_output_type(ResearchReport, model), + output_type=ResearchReport, instructions=synthesis_prompt, retries=3, deps_type=ResearchDependencies, diff --git a/haiku_rag_slim/haiku/rag/agents/rlm/agent.py b/haiku_rag_slim/haiku/rag/agents/rlm/agent.py index 55ff5c3f..4c234832 100644 --- a/haiku_rag_slim/haiku/rag/agents/rlm/agent.py +++ b/haiku_rag_slim/haiku/rag/agents/rlm/agent.py @@ -4,7 +4,7 @@ from haiku.rag.agents.rlm.dependencies import RLMDeps from haiku.rag.agents.rlm.models import CodeExecution, RLMResult from haiku.rag.agents.rlm.prompts import RLM_SYSTEM_PROMPT from haiku.rag.config.models import AppConfig -from haiku.rag.utils import get_model, structured_output_type +from haiku.rag.utils import get_model def create_rlm_agent(config: AppConfig) -> Agent[RLMDeps, RLMResult]: @@ -25,7 +25,7 @@ def create_rlm_agent(config: AppConfig) -> Agent[RLMDeps, RLMResult]: agent: Agent[RLMDeps, RLMResult] = Agent( # type: ignore[invalid-assignment] model, deps_type=RLMDeps, - output_type=structured_output_type(RLMResult, model), + output_type=RLMResult, instructions=RLM_SYSTEM_PROMPT, retries=3, ) diff --git a/haiku_rag_slim/haiku/rag/utils.py b/haiku_rag_slim/haiku/rag/utils.py index aac551d0..43f82f05 100644 --- a/haiku_rag_slim/haiku/rag/utils.py +++ b/haiku_rag_slim/haiku/rag/utils.py @@ -318,20 +318,6 @@ def get_model( return f"{provider}:{model}" -def structured_output_type( - result_type: type, - model: Any, - max_retries: int = 3, -) -> Any: - """Return a NativeOutput or ToolOutput wrapper based on model capability.""" - from pydantic_ai.models import Model - from pydantic_ai.output import NativeOutput, ToolOutput - - if isinstance(model, Model) and model.profile.supports_json_schema_output: - return NativeOutput(result_type) - return ToolOutput(result_type, max_retries=max_retries) - - def format_bytes(num_bytes: int) -> str: """Format bytes as human-readable string.""" size = float(num_bytes) diff --git a/tests/agents/rlm/test_agent.py b/tests/agents/rlm/test_agent.py index 3c852b13..60e567c1 100644 --- a/tests/agents/rlm/test_agent.py +++ b/tests/agents/rlm/test_agent.py @@ -15,24 +15,11 @@ def vcr_cassette_dir(): class TestCreateRLMAgent: - def test_creates_agent_native_output_when_supported(self): - from pydantic_ai.output import NativeOutput - + def test_creates_agent(self): agent = create_rlm_agent(Config) assert isinstance(agent, Agent) assert agent.deps_type is RLMDeps - assert isinstance(agent.output_type, NativeOutput) - assert agent.output_type.outputs is RLMResult - - def test_creates_agent_native_output_for_ollama(self): - from pydantic_ai.output import NativeOutput - - config = AppConfig() - config.rlm.model.name = "qwen3" - agent = create_rlm_agent(config) - assert isinstance(agent, Agent) - assert isinstance(agent.output_type, NativeOutput) - assert agent.output_type.outputs is RLMResult + assert agent.output_type is RLMResult def test_agent_has_execute_code_tool(self): agent = create_rlm_agent(Config) diff --git a/tests/test_utils.py b/tests/test_utils.py index 1d4bf995..fc9d52d9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -139,38 +139,6 @@ Emoji test: 🚀 ✅ 📝""" assert "🚀" in result_markdown -def test_structured_output_type_native(): - from pydantic_ai.output import NativeOutput - - from haiku.rag.utils import structured_output_type - - model = get_model(ModelConfig(provider="openai", name="gpt-4o")) - result = structured_output_type(str, model) - assert isinstance(result, NativeOutput) - assert result.outputs is str - - -def test_structured_output_type_ollama_native(): - from pydantic_ai.output import NativeOutput - - from haiku.rag.utils import structured_output_type - - model = get_model(ModelConfig(provider="ollama", name="qwen3")) - result = structured_output_type(str, model) - assert isinstance(result, NativeOutput) - assert result.outputs is str - - -def test_structured_output_type_string_model(): - from pydantic_ai.output import ToolOutput - - from haiku.rag.utils import structured_output_type - - result = structured_output_type(str, "unknown:model") - assert isinstance(result, ToolOutput) - assert result.output is str - - def test_get_model_ollama(): """Test get_model returns OpenAIChatModel for Ollama.""" model_config = ModelConfig(provider="ollama", name="llama3")