Skip to content

Structured Output + Tool Calling is not working with Gemini in Open AI Agents SDK #2257

@sebastianherreramonterrosa

Description

I'm trying this Agent with tools and structured output in Open AI Agents SDK with Gemini.

import os
import random
from typing import List, Literal

from agents import Agent, ItemHelpers, Runner, function_tool, set_tracing_disabled
from agents.extensions.models.litellm_model import LitellmModel
from pydantic import BaseModel, Field

set_tracing_disabled(disabled=True)
os.environ["GOOGLE_API_KEY"] = "..."

model = LitellmModel(model="gemini/gemini-3-flash-preview", api_key=os.getenv("GOOGLE_API_KEY"))
class Joke(BaseModel):
    setup: str = Field(description="Joke setup")
    punchline: str = Field(description="Joke punchline")


class JokesResponse(BaseModel):
    count: int = Field(description="Total number of jokes generated")
    style: Literal["dad", "dark", "absurd", "clean"] = Field(description="Chosen style")
    jokes: List[Joke] = Field(description="List of jokes")


class JokePlan(BaseModel):
    count: int = Field(ge=1, le=5, description="How many jokes to tell (1..5)")
    style: Literal["dad", "dark", "absurd", "clean"] = Field(description="Joke style")


class JokerAgentToolStructured:
    def __init__(self):
        self.agent = Agent(
            name="Joker+Structured",
            instructions=(
                "First, ALWAYS call the `make_joke_plan` tool.\n"
                "Then generate the FINAL response as JSON strictly compatible with `JokesResponse`:\n"
                "- `count` must match the plan\n"
                "- `style` must match the plan\n"
                "- `jokes` must contain exactly `count` elements\n"
                "Keep the jokes in Spanish."
            ),
            tools=[self.make_joke_plan],
            output_type=JokesResponse,
            model=model,
        )

    @staticmethod
    @function_tool
    def make_joke_plan() -> JokePlan:
        """Decide how many jokes and which style to use."""
        return JokePlan(
            count=random.randint(1, 5),
            style=random.choice(["dad", "absurd", "clean", "dark"]),
        )

    async def run(self, input: str):
        result = Runner.run_streamed(self.agent, input=input)

        print("=== Run starting ===")

        async for event in result.stream_events():
            if event.type == "raw_response_event":
                continue

            elif event.type == "agent_updated_stream_event":
                print(f"Agent updated: {event.new_agent.name}")
                continue

            elif event.type == "run_item_stream_event":
                item = event.item

                if item.type == "tool_call_item":
                    print("-- Tool was called")

                elif item.type == "tool_call_output_item":
                    print(f"-- Tool output: {item.output}")

                elif item.type == "message_output_item":
                    print(f"-- Message output:\n{ItemHelpers.text_message_output(item)}")

        print("=== Run complete ===")

        final: JokesResponse = result.final_output
        print("\n=== Final structured output (Pydantic object) ===")
        print(final.model_dump_json(indent=2))


async def main():
    agent = JokerAgentToolStructured()
    await agent.run("I want jokes for a work meeting.")

await main()

Relevant log output

File c:\Users\sejohermo\AppData\Local\Programs\Python\Python312\Lib\site-packages\agents\models\chatcmpl_stream_handler.py:91, in ChatCmplStreamHandler.handle_stream(cls, response, stream)
     89 state = StreamingState()
     90 sequence_number = SequenceNumber()
---> 91 async for chunk in stream:
     92     if not state.started:
     93         state.started = True

File c:\Users\sejohermo\AppData\Local\Programs\Python\Python312\Lib\site-packages\litellm\litellm_core_utils\streaming_handler.py:2013, in CustomStreamWrapper.__anext__(self)
   2010 except Exception as e:
   2011     from litellm.exceptions import MidStreamFallbackError
-> 2013     raise MidStreamFallbackError(
   2014         message=str(e),
   2015         model=self.model,
   2016         llm_provider=self.custom_llm_provider or "anthropic",
   2017         original_exception=e,
   2018         generated_content=self.response_uptil_now,
   2019         is_pre_first_chunk=not self.sent_first_chunk,
   2020     )

MidStreamFallbackError: litellm.ServiceUnavailableError: litellm.MidStreamFallbackError: litellm.BadRequestError: Vertex_ai_betaException BadRequestError - b'{\n  "error": {\n    "code": 400,\n    "message": "Request contains an invalid argument.",\n    "status": "INVALID_ARGUMENT"\n  }\n}\n' Original exception: BadRequestError: litellm.BadRequestError: Vertex_ai_betaException BadRequestError - b'{\n  "error": {\n    "code": 400,\n    "message": "Request contains an invalid argument.",\n    "status": "INVALID_ARGUMENT"\n  }\n}\n'

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions