Pydantic AIPydantic AI 2.51 · Python 3.10+
Dashboard
0%
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:

python
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})])
Example
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)

How the union came back

  • Each type became its own output tool, named final_result_Ticket and final_result_NeedsHuman.
  • The model picks one, and the output is a normal Python object of that type.
  • Adding str to 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:

python
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})])
Example
agent = Agent(FunctionModel(triage), output_type=open_ticket)
result = agent.run_sync("I was charged twice")
print(result.output)
print(queue)

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 typesOutput function
What you passA list of models or typesA callable
The outputOne of the typesWhatever the function returns
Runs your codeNo, you branch afterYes, 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 ModelRetry and take a RunContext first 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.
Try it yourself
  • Add a third type, Spam with no fields, to the list, and a rule in triage for it.
  • Put open_ticket and NeedsHuman in one list.
  • Raise ModelRetry in open_ticket when priority is above 5.

Every expert started right here.