Back to Oh My Openagent

PydanticAI Reference (v1.x, 2026)

packages/omo-codex/plugin/skills/programming/references/python/pydantic-ai.md

4.5.128.2 KB
Original Source

PydanticAI Reference (v1.x, 2026)

Canonical patterns for wiring PydanticAI agents. Target: production usage, late-2025 / 2026. Source: ai.pydantic.dev and pydantic/pydantic-ai@cad9569.


1. Agent Constructor

python
from pydantic_ai import Agent

agent = Agent(
    'openai:gpt-5.2',               # model (str | Model | None)
    output_type=MyOutputModel,       # structured output type; default=str
    instructions='You are a...',     # static or callable instructions
    system_prompt='Be concise.',     # static system prompt(s)
    deps_type=MyDeps,                # dependency type for type-checking only
    name='my-agent',                 # optional, inferred from var name if omitted
    retries=1,                       # default retries for tools + output validation
    output_retries=None,             # override retries for output validation only
    tools=[my_tool],                 # list of Tool objects or plain functions
    defer_model_check=False,         # set True to skip env-var check at init time
    end_strategy='early',            # 'early' | 'graceful' | 'exhaustive'
)

Breaking change (v1.88.0): result_type was renamed to output_type. Use output_type.


2. Model Strings

Format: provider:model-name. The framework infers the provider from the prefix.

Provider prefixExample
openai:'openai:gpt-5.2', 'openai:gpt-4o'
anthropic:'anthropic:claude-sonnet-4-6', 'anthropic:claude-opus-4-1'
google-gla:'google-gla:gemini-3-flash-preview'
google-vertex:'google-vertex:gemini-3-pro-preview'
bedrock:'bedrock:anthropic.claude-sonnet-4-6'
xai: / grok:'xai:grok-3', 'grok:grok-3-fast'
deepseek:'deepseek:deepseek-chat'
cohere:'cohere:command-r-08-2024'
gateway/...'gateway/openai:gpt-5.2' (PydanticAI Gateway)

Model can also be omitted at construction and passed per-run: agent.run(prompt, model='openai:gpt-5.2').


3. Tools

Decorator syntax

python
from pydantic_ai import Agent, RunContext

agent = Agent('openai:gpt-5.2', deps_type=str)

@agent.tool                    # default: receives RunContext as first arg
async def greet(ctx: RunContext[str], name: str) -> str:
    return f"Hello {ctx.deps}, {name}!"

@agent.tool_plain              # no context needed
async def roll_dice(sides: int) -> int:
    import random
    return random.randint(1, sides)

RunContext[Deps]

First parameter of @agent.tool functions. Carries:

  • ctx.deps — the dependency instance
  • ctx.model — the model being used
  • ctx.usage — token usage so far
  • ctx.messages — conversation history
  • ctx.retry / ctx.max_retries — current retry count
  • ctx.agent — the running agent instance

Use @agent.tool_plain when the tool does not need any of the above.


4. Structured Output

Pass a Pydantic BaseModel (or bool, int, list[str], etc.) as output_type. The result is accessed via .output.

python
from pydantic import BaseModel
from pydantic_ai import Agent

class City(BaseModel):
    name: str
    country: str
    population_millions: float

agent = Agent('openai:gpt-5.2', output_type=City)
result = agent.run_sync('Tell me about Tokyo')
print(result.output)            # City(name='Tokyo', country='Japan', ...)
print(result.output.name)       # 'Tokyo'

Note: result.data was renamed; the canonical accessor is result.output.


5. Async vs Sync

MethodModeReturns
await agent.run(prompt, ...)asyncAgentRunResult[OutputDataT]
agent.run_sync(prompt, ...)syncAgentRunResult[OutputDataT]
async with agent.run_stream(prompt, ...) as response:async streamingStreamedRunResult
python
# Sync
result = agent.run_sync('What is the capital of Italy?')
print(result.output)

# Async
result = await agent.run('What is the capital of France?')
print(result.output)

# Streaming
async with agent.run_stream('What is the capital of the UK?') as response:
    async for text in response.stream_text():
        print(text, end='')
    # After streaming finishes:
    print(response.output)

run_sync() is a convenience wrapper over loop.run_until_complete(self.run(...)). Do not use it inside an active async context.


6. Dependencies

Use a @dataclass container, pass the type to deps_type, and pass an instance to deps at run time.

python
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext

@dataclass
class Deps:
    api_key: str
    http_client: httpx.AsyncClient

agent = Agent(
    'openai:gpt-5.2',
    deps_type=Deps,
)

@agent.tool
async def fetch_data(ctx: RunContext[Deps], endpoint: str) -> str:
    r = await ctx.deps.http_client.get(
        endpoint,
        headers={'Authorization': f'Bearer {ctx.deps.api_key}'},
    )
    r.raise_for_status()
    return r.text

async def main():
    async with httpx.AsyncClient() as client:
        deps = Deps(api_key='sk-...', http_client=client)
        result = await agent.run('Get /users', deps=deps)
        print(result.output)

7. Error Types & Retrying from a Tool

python
from pydantic_ai import Agent, ModelRetry, UnexpectedModelBehavior, capture_run_messages

agent = Agent('openai:gpt-5.2', retries=3)

@agent.tool_plain
def calc_volume(size: int) -> int:
    if size == 42:
        return size ** 3
    raise ModelRetry('Please try again with size 42.')

with capture_run_messages() as messages:
    try:
        result = agent.run_sync('Get the volume of a box with size 6.')
    except UnexpectedModelBehavior as e:
        print('Error:', e)          # "Tool 'calc_volume' exceeded max retries count of 3"
        print('Cause:', e.__cause__)  # ModelRetry('Please try again...')
        print('Messages:', messages)
  • ModelRetry — raise from a tool, output validator, or capability hook to ask the model to retry.
  • UnexpectedModelBehavior — raised when the retry limit is exceeded or the model API returns an unrecoverable error.
  • capture_run_messages() — context manager that records all messages exchanged during a run for debugging.

8. Logfire Integration

One-line setup if the logfire extra is installed (included in the default pydantic-ai package):

python
import logfire

logfire.configure()               # reads token from .logfire directory
logfire.instrument_pydantic_ai()  # auto-traces all agent runs

Alternatively, set instrument=True on the agent:

python
agent = Agent('openai:gpt-5.2', instrument=True)

9. Minimal Complete Snippets

(a) Basic agent with structured output

python
from pydantic import BaseModel
from pydantic_ai import Agent

class City(BaseModel):
    name: str
    country: str

agent = Agent('openai:gpt-5.2', output_type=City)
result = agent.run_sync('Tell me about Paris')
print(result.output)   # City(name='Paris', country='France')

(b) Agent with tools and dependencies

python
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext

@dataclass
class Deps:
    api_key: str

agent = Agent('openai:gpt-5.2', deps_type=Deps)

@agent.tool
async def get_secret(ctx: RunContext[Deps], code: str) -> str:
    if code == '1234':
        return f'secret-for-{ctx.deps.api_key}'
    return 'wrong code'

result = agent.run_sync('My code is 1234', deps=Deps(api_key='sk-abc'))
print(result.output)

(c) Async streaming

python
import anyio
from pydantic_ai import Agent

agent = Agent('openai:gpt-5.2')

async def main() -> None:
    async with agent.run_stream('Write a haiku about Python') as response:
        async for text in response.stream_text():
            print(text, end='')
        print('\n---')
        print('Final:', response.output)

anyio.run(main)

Version Notes

  • V1 reached API stability in September 2025. Breaking changes are reserved for V2 (earliest April 2026).
  • v1.88.0 renamed result_typeoutput_type and result_tool_name / result_tool_description were removed. Use output_type.
  • The canonical accessor for run results is result.output (not result.data).