Roadmap Phase 3: native structured outputs for all LLM call sites - #38
Merged
Conversation
Attach pydantic schemas to agents (pydantic-ai output_type) instead of asking for JSON in prose and repairing whatever comes back. The framework validates responses and retries with validation feedback on schema violations; callers receive typed objects or fall back to the existing heuristics. - New src/models/structured.py: create_structured_agent / run_structured / try_run_structured, plus slim role prompts — the 'respond with ONLY JSON' scaffolding is unnecessary when the schema is enforced by the framework - Converted call sites: per-paper extraction, collective synthesis, gap analysis, and LLM query expansion - Extraction pins identity fields (paper ID, title) from retrieval metadata so model output can never relabel a paper, and backfills evidence from grounded passages when the model omits it - Circuit breaker and heuristic fallbacks behave identically on the structured path - Config-gated by llm.structured_outputs (default true); disabling it routes through the legacy prose-JSON repair path unchanged, kept for backends that cannot honor response schemas - Legacy-path tests now pin structured_outputs=false explicitly; new tests cover the structured path end to end using pydantic-ai's TestModel and FunctionModel (no network) - .env.example, the environment-variables reference, and the heuristic-vs-LLM page document the new flag
This was referenced Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the structured-outputs item from Phase 3 of the improvement roadmap. Every LLM call site now attaches its pydantic schema to the agent (
output_type) so pydantic-ai enforces the schema natively — validating responses and retrying with validation feedback on violations — instead of asking for JSON in prose and repairing whatever comes back. Callers receive typed objects or fall back to the existing heuristics.Changes
src/models/structured.py—create_structured_agent,run_structured,try_run_structured, plus slim structured-mode role prompts: with the schema enforced by the framework, the"respond with ONLY JSON …"scaffolding in the legacy prompts is unnecessary noise (the legacy prompts remain for the legacy path).llm.structured_outputs(defaulttrue). Disabling routes through the legacy prose-JSON repair path unchanged, kept for backends that cannot honor response schemas. Once the structured path is proven against local Ollama models, the roadmap's follow-up is retiring the ~2,900-line repair stack insrc/utils/.Documentation
.env.example, the environment-variables reference, and the heuristic-vs-LLM page documentRA_LLM__STRUCTURED_OUTPUTS.Verification
pipenv run pytest tests/— 426 passed (9 new tests:run_structuredagainst pydantic-ai'sTestModel, failure handling against aFunctionModelthat raises, identity pinning, circuit-breaker behavior on structured failures, structured synthesis/gap/expansion paths — all offline), 1 skippedstructured_outputs=falseexplicitly, so both paths stay coveredruff check src tests setups— clean;scripts/check_docs_policy.py— passesRA_SYNTHESIS__LLM_ENABLED=truebefore flipping any defaults that depend on it