Skip to content

Impossible to stream text responses ahead of final result when requiring structured output #2158

@Blue9

Description

@Blue9

Initial Checks

Description

This is a follow-up from a Slack conversation: https://pydanticlogfire.slack.com/archives/C083V7PMHHA/p1751066192272409

I want my agent to stream text, make tool calls, stream more text if needed, and make a final tool call for its structured output (I'm using Gemini which does not support tool calls and native structured output at the same time). To do so, I have set output_type to [str, FinalOutput] where FinalOutput is my structured output type.

This streams text as expected, but the final_result tool call is not always made. My understanding is that since the end node can either be FinalOutput or text, it will sometimes output text and there is no guarantee it outputs a FinalOutput. In my repro script, I have two example queries. One of them will call the final output tool call, and one of them will not (although this is dependent on the model's output being reproducible which isn't guaranteed).

I think what I would ideally want to do is to set output_type to FinalOutput so that it is always called but still enable streaming text output. That is currently prevented by this line in _agent_graph.py.

Is there a way to have pydantic-ai consistently stream text output AND make a final result tool call? One workaround is to just prompt the LLM to make the final result tool call, but that is not guaranteed.

Example Code

"""
To run: python <name>.py.

Requires GEMINI_API_KEY environment variable (available at https://aistudio.google.com/apikey).
Alternatively you can change the model but this will change the reproducibility of the script.
"""

from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai.messages import (
    FinalResultEvent,
    FunctionToolCallEvent,
    FunctionToolResultEvent,
    PartDeltaEvent,
    PartStartEvent,
    TextPart,
    TextPartDelta,
)
from pydantic_ai.models import KnownModelName


class FinalOutput(BaseModel):
    lines: list[str]


QUERY = "Write a short poem about Python."  # Does not call final tool call.
# QUERY = "Write a poem about Python."  # Does call final tool call.

MODEL: KnownModelName = "google-gla:gemini-2.5-flash"
AGENT = Agent(
    model=MODEL,
    model_settings={"temperature": 0},
    output_type=[str, FinalOutput],
)


async def main():
    # Based on https://ai.pydantic.dev/agents/#streaming
    async with AGENT.iter(user_prompt=QUERY) as run:
        async for node in run:
            if Agent.is_user_prompt_node(node):
                print("\nUser prompt:", node.user_prompt)
            elif Agent.is_model_request_node(node):
                async with node.stream(run.ctx) as request_stream:
                    async for event in request_stream:
                        if isinstance(event, PartStartEvent) and isinstance(event.part, TextPart):
                            print("\nText start:", event.part.content)
                        elif isinstance(event, PartDeltaEvent) and isinstance(event.delta, TextPartDelta):
                            print("\nText delta:", event.delta.content_delta)
                        elif isinstance(event, FinalResultEvent):
                            print("\nFinal output tool call:", event.tool_name)
            elif Agent.is_call_tools_node(node):
                async with node.stream(run.ctx) as handle_stream:
                    async for event in handle_stream:
                        if isinstance(event, FunctionToolCallEvent):
                            print("\nTool call event:", event)
                        elif isinstance(event, FunctionToolResultEvent):
                            print("\nTool result event:", event)
            elif Agent.is_end_node(node):
                print("\nFinal output:", node.data.output)


if __name__ == "__main__":
    import asyncio

    asyncio.run(main())

Python, Pydantic AI & LLM client version

Python 3.11.13
pydantic-ai 0.4.0

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions