1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
29 small wins to finish your pathNext lesson →
Output functions and several output types
An output function is a function used as the output_type: the model calls it to finish, and its return value becomes the run's output.
Last updated: 28 Sep, 2026 · Pydantic AI 2.51
The typed output in Structured output: a typed ticket was one model class. An agent can accept more than one kind of answer, and it can run your code on the answer before the run ends.
Accepting one of several output types
A list of types gives the model one output tool per type; it picks one, and your code checks which with isinstance:
from typing import Literal
from pydantic import BaseModel
from pydantic_ai import Agent, ModelResponse, ToolCallPart
from pydantic_ai.models.function import FunctionModel
class Ticket(BaseModel):
category: Literal["billing", "shipping", "other"]
priority: int
class NeedsHuman(BaseModel):
reason: str
def triage(messages, info):
ticket = messages[0].parts[-1].content
if "lawyer" in ticket:
return ModelResponse(parts=[ToolCallPart("final_result_NeedsHuman", {"reason": "legal threat"})])
return ModelResponse(parts=[ToolCallPart("final_result_Ticket", {"category": "billing", "priority": 4})])agent = Agent(FunctionModel(triage), output_type=[Ticket, NeedsHuman])
for text in ["I was charged twice", "My lawyer will hear about this"]:
output = agent.run_sync(text).output
if isinstance(output, NeedsHuman):
print("to a person:", output.reason)
else:
print("queued:", output)Output
queued: category='billing' priority=4 to a person: legal threat
How the union came back
- Each type became its own output tool, named
final_result_Ticketandfinal_result_NeedsHuman. - The model picks one, and the output is a normal Python object of that type.
- Adding
strto the list would let a plain text answer end the run too.
Running code on the answer with an output function
A function as output_type turns its parameters into the output tool's arguments and its docstring into the tool's description:
from pydantic_ai import Agent, ModelResponse, ToolCallPart
from pydantic_ai.models.function import FunctionModel
queue = []
def open_ticket(category: str, priority: int) -> str:
"""Put a ticket in the support queue."""
queue.append({"category": category, "priority": priority})
return f"T-{len(queue)}"
def triage(messages, info):
tool = info.output_tools[0]
print("output tool:", tool.name, "-", tool.description)
return ModelResponse(parts=[ToolCallPart(tool.name, {"category": "billing", "priority": 4})])agent = Agent(FunctionModel(triage), output_type=open_ticket)
result = agent.run_sync("I was charged twice")
print(result.output)
print(queue)Output
output tool: final_result - Put a ticket in the support queue.
T-1
[{'category': 'billing', 'priority': 4}]What the output function did
- The model called it to finish; Pydantic validated the arguments, the function ran, and its return value is
result.output. - The queue changed, so the function did real work once the answer was known.
- The model never sees that return value, unlike a tool's, because the run is already over.
A union of types vs an output function
| Union of types | Output function | |
|---|---|---|
| What you pass | A list of models or types | A callable |
| The output | One of the types | Whatever the function returns |
| Runs your code | No, you branch after | Yes, as the run finishes |
When to use each
- A union when the answer is one of a few shapes and your code decides what to do next.
- An output function when the answer should trigger work, such as saving a ticket.
- Either can raise
ModelRetryand take aRunContextfirst argument.
Watch out. An output function's return value ends the run, so the model never reads it. Put anything the model still needs to act on in a tool, covered in Function tools in Pydantic AI, not in an output function.
Related
- Previous: Output validators: rules Pydantic cannot check
- Next: Function tools: letting the model look things up
- Reference: Output functions
Try it yourself
- Add a third type,
Spamwith no fields, to the list, and a rule intriagefor it. - Put
open_ticketandNeedsHumanin one list. - Raise
ModelRetryinopen_ticketwhenpriorityis above 5.
Every expert started right here.