Skip to content

Conversation

@devv-shayan
Copy link

@devv-shayan devv-shayan commented Nov 10, 2025

Add structured output with tools support for models with limited structured output

Summary

Adds an opt-in structured output with tools feature to LitellmModel, enabling the use of tools and structured outputs simultaneously on models that don’t natively support both (specifically Google Gemini via LiteLLM).

Problem:
Models like Gemini return
BadRequestError: Function calling with a response mime type 'application/json' is unsupported
when trying to use tools together with structured outputs (via response_schema).

Solution:
A new enable_structured_output_with_tools parameter on the LitellmModel class that:

  • Injects JSON formatting instructions into the system prompt
  • Disables native response_format to avoid API errors
  • Parses the model’s JSON response into the specified Pydantic model
  • Defaults to False for backward compatibility

Test plan

Unit tests (14 tests added):

  • tests/utils/test_prompts.py – Tests for prompt generation and injection logic

    • Validates JSON schema extraction from Pydantic models
    • Tests conditional injection based on tools/output_schema presence
    • Verifies opt-in behavior (only injects when explicitly enabled)

Integration test:

  • tests/test_gemini_local.py – Manual test script for Gemini (requires API key)

    • Tests actual Gemini integration with tools + structured output
    • Verifies structured output with tools works end-to-end

Verification performed:

make lint     # ✅ All checks passed
make format   # ✅ 359 files formatted correctly
make mypy     # ✅ No type errors
make tests    # ✅ 995 passed, 3 skipped, 20 failed*

20 failures are pre-existing Windows-specific SQLite file locking issues (not related to this PR). All 14 new tests pass.

Test coverage:

  • Updated test mock models for optional enable_structured_output_with_tools parameter
  • Verified backward compatibility
  • Tested both enabled and disabled modes
  • Validated structured output parsing with nested Pydantic models

Issue number

#2032

Checks

  • I've added new tests (if relevant)
  • I've added/updated the relevant documentation
  • I've run make lint and make format
  • I've made sure tests pass

Documentation

Added comprehensive documentation:

  • docs/models/structured_output_with_tools.md – Complete guide with examples

    • Problem explanation and sample errors
    • Step-by-step setup and code samples
    • “When to Use” guide for various model providers
    • Debugging tips and best practices
  • docs/models/litellm.md – Added Gemini integration example

  • mkdocs.yml – Added new doc page to navigation

Files Changed

Source code:

  • src/agents/extensions/models/litellm_model.py – Added enable_structured_output_with_tools parameter and implementation
  • src/agents/util/_prompts.py – Utility for JSON prompt generation
  • src/agents/models/interface.py – Updated for consistency
  • src/agents/models/openai_chatcompletions.py – Pass-through (ignores parameter)
  • src/agents/models/openai_responses.py – Pass-through (ignores parameter)
  • src/agents/run.py – No longer handles structured output logic

Tests:

  • tests/utils/test_prompts.py – 14 new unit tests
  • tests/test_gemini_local.py – Integration test
  • Updated mock models for consistency

Documentation:

  • docs/models/structured_output_with_tools.md – New comprehensive guide
  • docs/models/litellm.md – Added Gemini example
  • mkdocs.yml – Updated navigation

Implementation Details

Design decisions:

  1. Opt-in by defaultenable_structured_output_with_tools=False ensures no impact on existing integrations.
  2. LiteLLM-focused – Implementation is isolated to LitellmModel; OpenAI models continue to use native support.
  3. Interface consistency – All models accept the parameter for uniform signatures.
  4. User control – Users enable it explicitly based on their model’s capabilities.

How it works:

  1. User sets enable_structured_output_with_tools=True when initializing LitellmModel.
  2. The model checks for both tools and an output schema.
  3. If both are present, JSON instructions are generated from the Pydantic schema.
  4. Instructions are injected into the system prompt.
  5. Native response_format is disabled to avoid API errors.
  6. The model returns JSON text output.
  7. The SDK parses and validates it against the Pydantic model.

Example Usage

from agents.extensions.models.litellm_model import LitellmModel
from agents import function_tool
from pydantic import BaseModel, Field

class WeatherReport(BaseModel):
    city: str = Field(description="City name")
    temperature: float = Field(description="Temperature in Celsius")

@function_tool
def get_weather(city: str) -> dict:
    return {"city": city, "temperature": 22.5}

# Gemini now supports tools + structured output!
model = LitellmModel(
    "gemini/gemini-2.5-flash",
    enable_structured_output_with_tools=True,
)

agent = Agent(
    name="WeatherBot",
    model=model,
    tools=[get_weather],
    output_type=WeatherReport,
)

Backward Compatibility

Fully backward compatible

  • Parameter is optional and defaults to False
  • Existing integrations remain unchanged
  • OpenAI models ignore it (use native structured output support)
  • Only affects LitellmModel when explicitly enabled

support for tools on limited models (e.g., Gemini)

Enable using tools and structured outputs together on models (e.g., Gemini) that don't natively
support both simultaneously. Introduce an opt-in parameter enable_structured_output_with_tools
to the Agent class, which injects JSON formatting instructions into the system prompt for
LitellmModel as a workaround.

Changes:
- Add enable_structured_output_with_tools parameter to Agent (default: False)
- Implement prompt injection utilities in src/agents/util/_prompts.py
- Update LitellmModel to inject JSON instructions when enabled
- Extend model interfaces to accept enable_structured_output_with_tools
- Add comprehensive unit tests (13 total) and one integration test
- Add documentation in docs/models/structured_output_with_tools.md
- Update docs/agents.md and docs/models/litellm.md with usage examples
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@devv-shayan devv-shayan changed the title feat(agent): add structured output Add structured output with tools support for models with limited structured output Nov 10, 2025
 when enabled

Maintain backward compatibility with third-party Model implementations by
only passing the enable_structured_output_with_tools parameter when it's
explicitly enabled (True). This prevents TypeErrors in custom Model classes
that don't support the new parameter yet.

- Built-in models have default False, so they work either way
- Third-party models without the parameter won't crash
- Feature still works when explicitly enabled

Fixes backward compatibility issue raised in code review.
@seratch
Copy link
Member

seratch commented Nov 10, 2025

@devv-shayan Thanks for looking into this. However, as I mentioned at #2032 (comment), we prefer resolving this only by the changes on the LiteLLM or our LiteLLM adapter side. So, we don't accept this large diff for this.

@devv-shayan
Copy link
Author

devv-shayan commented Nov 11, 2025

Sure I'll work on it to make it on litellm adapter side

Moved the enable_structured_output_with_tools parameter from the Agent class to
LitellmModel.__init__() to minimize the diff and isolate changes within the LiteLLM
adapter as requested during code review.

Changes:
- Added enable_structured_output_with_tools parameter to LitellmModel.__init__()
- Stored as instance variable and used throughout LitellmModel
- Removed parameter from Agent class and related validation
- Removed parameter from Model interface (get_response / stream_response)
- Removed parameter from Runner (no longer passed to model calls)
- Removed parameter from OpenAI model implementations
- Reverted test mock models to original signatures
- Updated test_gemini_local.py for model-level configuration
- Updated documentation to reflect model-level usage

Before:
  Agent(model=..., enable_structured_output_with_tools=True)

After:
  Agent(model=LitellmModel(..., enable_structured_output_with_tools=True))
@devv-shayan devv-shayan marked this pull request as ready for review November 11, 2025 06:59
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

 prompt

The JSON prompt injection was only triggered when tools list was non-empty,
but handoffs are converted to function tools and added separately. This meant
that agents using only handoffs with output_schema would not get the prompt
injection even when enable_structured_output_with_tools=True, causing Gemini
to error with 'Function calling with response mime type application/json is
unsupported.'

Changes:
- Combine tools and handoffs before checking if JSON prompt should be injected
- Add test case for handoffs-only scenario
- Update inline comment to clarify why handoffs must be included

This ensures the opt-in flag works correctly for multi-agent scenarios where
an agent might use handoffs without regular tools.
@devv-shayan devv-shayan force-pushed the feat/structured-output-with-tools branch from 180609c to 1d140ff Compare November 11, 2025 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants