Tool

Build production-grade GenAI agents with type-safe Python

Typed Python AI SDK for building agents that run anywhere - web, terminal, voice, or durable background queues - with any model.

Works with openaianthropicgeminideepseekgrok

91
Spark score
out of 100
Updated 10 days ago
Source checked Sep 10, 2026
Version 2.42.0

Add to Favorites

Why it matters

Build reliable, production-ready generative AI agents and workflows in Python with full type safety, structured outputs, observability, and support for virtually every LLM provider-bringing the FastAPI developer experience to GenAI application development.

Outcomes

What it gets done

01

Create type-safe agents with structured outputs validated by Pydantic

02

Connect agents to external tools via MCP and custom capabilities

03

Monitor agent behavior and costs with built-in Logfire observability

04

Build durable workflows with human-in-the-loop approval and streaming

Source

Get it from source

Spark does not host a copy of it.

Open source

Reports

Agent outcome reports

No reports yet

Overview

Pydantic Ai

Pydantic AI is a typed Python SDK for building AI agents with a single extensible agent loop that works with any model provider and runs across web, terminal, voice, and durable background execution. Use it for typed, structured LLM work in Python - data extraction, tool-calling agents, coding agents, or durable multi-agent workflows - where type safety end to end matters.

What it does

Pydantic AI is a typed Python AI SDK for building agents - a single extensible agent loop that works with any model provider, a string swap away, and runs the same agent behind a web frontend, in a terminal, on a voice call, on a durable background queue, or as a plain object you call run() on, with image generation and embeddings built in.

When to use - and when NOT to

Use it for typed, structured LLM work in Python: validated data extraction with a guaranteed output type, tool-calling agents, long-running multi-agent collaboration, or a full coding agent - anywhere you want the LLM's output, your IDE, and your type checker to agree on the same type. The companion package Pydantic AI Harness adds the pieces long-running agents need, memory, sub-agents, context management, a complete coding agent, as composable capabilities rather than requiring you to build them from scratch.

Inputs and outputs

A basic agent is created with Agent('openai:gpt-5.6-sol', output_type=Sentiment), where Sentiment is an ordinary Pydantic BaseModel; @agent.tool functions receive a RunContext carrying dependencies, and their signature and docstring become the tool schema with arguments validated before your code runs, guaranteeing the run returns the declared type. The bundled Coder harness capability is a composition of FileSystem, Shell, RepoContext, Planning, SubAgents, and context-management blocks (ClearToolResults, WarnNearLimits, ToolOutputLimits) that can be used as a single unit or assembled individually. Attaching TemporalDurability, or the DBOS/Prefect equivalents, turns every model and tool call into a durable activity inside a workflow engine, so a long-running agent run survives restarts and failures. The same agent definition also runs as a live voice session (OpenAI Realtime, Gemini Live, Azure, or xAI Grok Voice), with tools and MCP capabilities available mid-conversation while the model keeps talking; a separate standalone ImageGenerator API generates images outside any agent run, while provider-native output_type=BinaryImage handles the case where an agent run itself decides to generate one.

Integrations

Every major model provider through a uniform string identifier, such as anthropic:claude-fable-5 or openai:gpt-5.6-sol; web frontends, terminal/CLI (clai), and voice-call interfaces; durable-execution backends Temporal, DBOS, and Prefect, first-party and co-maintained, plus Restate, Kitaru, and Airflow; and capabilities like WebSearch, WebFetch, and Advisor, a second-opinion call to a different model when the primary agent is stuck. The Capability primitive bundles tools, instructions, hooks, and model settings into a single reusable, composable unit - deferred capabilities can even load on demand mid-conversation, the way a skill would. The Pydantic AI Gateway offers one API key across every supported provider with built-in failover and cost monitoring, and OpenTelemetry-native instrumentation plugs into Pydantic Logfire for tracing and cost tracking, with Pydantic Evals for pytest-style agent behavior testing.

Who it's for

Python developers building anything from simple typed data extraction to complex, long-running multi-agent systems who want strict typing end to end, a model-agnostic agent loop, and pre-built capabilities for coding agents and durable workflows instead of assembling each piece from scratch. That spans a single-file sentiment classifier that returns a validated Pydantic model, a terminal-based coding agent with workspace-rooted file access and an allowlisted shell, and a research agent running for hours inside a Temporal, DBOS, or Prefect workflow that must survive process restarts and long waits between steps.

Source README

How Python does AI

CI Coverage PyPI versions license Join Slack

Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end.


Pydantic AI is the Python AI SDK: a typed, extensible agent loop with every model a string swap away. The same agent runs everywhere you need it: behind a web frontend, in the terminal, on a voice call, on a durable background queue, or as a plain object you call run() on. Image generation and embeddings come in the same box.

Pydantic AI Harness has everything an agent needs for complex, long-running work, snapped on as capabilities, from memory, sub-agents, and context management to a complete coding agent.

View the complete documentation at pydantic.dev/docs/ai.

What are you building?

From simple typed data extraction to complex, long-running multi-agent collaboration, Pydantic AI and Pydantic AI Harness have got you covered.

Coding agent

A complete coding agent in your terminal: workspace-rooted file access, allowlisted shell, repo orientation, planning, and context management that survives long sessions. Here with web search and a second-opinion advisor snapped on alongside:

uv add pydantic-ai pydantic-ai-harness
from pydantic_ai import Agent
from pydantic_ai.capabilities import WebSearch
from pydantic_ai_harness import Advisor, Coder

agent = Agent(
    'anthropic:claude-fable-5',
    capabilities=[
        Coder(),  # files, shell, repo context, planning, sub-agents, context management
        WebSearch(),  # look up docs and error messages on the web
        Advisor('openai:gpt-5.6-sol'),  # a second opinion from another model when stuck
    ],
)
agent.to_cli_sync()

Coder is a regular combined capability, not a black box: use it whole, or use the blocks it bundles directly; the two are equivalent:

capabilities = [
    FileSystem('.'), Shell(cwd='.'), RepoContext(), Planning(), SubAgents(...),
    ClearToolResults(), WarnNearLimits(), ToolOutputLimits(),
]

Run the file and you're chatting with the agent in your terminal. To try it before writing any code, run the exported coder_agent with clai (the Pydantic AI CLI), via uvx:

uvx --with pydantic-ai-harness clai -a pydantic_ai_harness.coder:coder_agent -m anthropic:claude-fable-5

Build this → Coder, from the Harness

Data extraction

Give the agent an output type and tools, and every run comes back validated and typed:

uv add pydantic-ai
from typing import Literal

from pydantic import BaseModel, Field

from pydantic_ai import Agent, RunContext


class Sentiment(BaseModel):
    label: Literal['positive', 'negative', 'neutral']
    score: float = Field(ge=-1, le=1)


agent = Agent('openai:gpt-5.6-sol', output_type=Sentiment)


@agent.tool
def recent_reviews(ctx: RunContext[None], product: str) -> list[str]:
    """Fetch recent review snippets for a product."""
    return ['The new release fixed everything I complained about!']


result = agent.run_sync('How are people feeling about the Extract app?')
print(result.output)
#> label='positive' score=0.9

The @agent.tool function receives a RunContext that carries your dependencies in; the rest of its signature and its docstring become the tool schema, arguments are validated before your code runs, and the run is guaranteed to return a Sentiment, so your IDE, type checker, and the LLM all agree on the returned type.

Build this → Agents, Function Tools, and Structured Output

Durable workflow

Attach TemporalDurability and the same agent runs inside a Temporal workflow under durable execution: every model and tool call becomes a durable activity, so a run working through a background queue survives restarts, failures, and long waits:

uv add "pydantic-ai[temporal]"
from temporalio import workflow

from pydantic_ai import Agent
from pydantic_ai.capabilities import WebFetch, WebSearch
from pydantic_ai.durable_exec.temporal import PydanticAIWorkflow, TemporalDurability

agent = Agent(
    'openai:gpt-5.6-sol',
    instructions='Research the topic and write a structured brief.',
    name='researcher',
    capabilities=[WebSearch(), WebFetch(), TemporalDurability()],
)


@workflow.defn
class ResearchWorkflow(PydanticAIWorkflow):
    __pydantic_ai_agents__ = [agent]

    @workflow.run
    async def run(self, topic: str) -> str:
        result = await agent.run(f'Write a brief on: {topic}')
        return result.output

DBOS and Prefect attach the same way, first-party and co-maintained, with Restate, Kitaru, and Airflow integrations besides.

Build this → Durable Execution

Realtime voice

Put the same agent on a live voice session, tools and capabilities included:

uv add "pydantic-ai[openai-realtime]"
import asyncio

from pydantic_ai import Agent
from pydantic_ai.capabilities import MCP

agent = Agent(
    instructions='You are a helpful voice assistant.',
    capabilities=[MCP('https://internal.example.com/mcp')],  # capabilities work in voice too
)

@agent.tool_plain
def order_status(order_id: str) -> str:
    """Look up the status of an order."""
    return f'Order {order_id}: shipped, arriving Thursday.'

async with agent.realtime('openai:gpt-realtime-2.1').session() as session:
    microphone = asyncio.create_task(session.send_audio(microphone_chunks()))  # your microphone → the model
    speaker = asyncio.create_task(play_audio(session.stream_audio()))  # model audio → your speaker
    async for part in session.stream_transcripts():
        print(f'{part.speaker}: {part.transcript}')

The model calls your tools mid-conversation while it keeps talking, and every session is instrumented; voice is just another frontend, on OpenAI Realtime, Gemini Live, Azure, and xAI Grok Voice.

Build this → Realtime Voice

Image generation

Generate an image with a dedicated image model, no agent run required:

uv add pydantic-ai
from pathlib import Path

from pydantic_ai import ImageGenerator

generator = ImageGenerator('openai:gpt-image-2')
result = generator.generate_sync('A minimalist logo for a coffee shop called Extract.')
Path('logo.png').write_bytes(result.image.data)

That standalone image API is for when your application decides; when an agent run decides, there is provider-native generation with output_type=BinaryImage for a typed image output, and the ImageGeneration capability with its fallbacks for models that generate no images of their own.

Build this → Image Generation

Why Pydantic AI

Built by the Pydantic team: Pydantic Validation is the validation layer of the OpenAI SDK, the Anthropic SDK, the Google ADK, LangChain, and most of the AI ecosystem (and the foundation FastAPI was built on). Pydantic AI brings that same feeling to agents.

Putting it together: a bank support agent

A typed support agent showing several features working together: dependency injection, function tools, structured output, a reusable capability bundling the customer context, and an on-demand capability the model loads only when the conversation calls for it:

from dataclasses import dataclass

from pydantic import BaseModel, Field

from pydantic_ai import Agent, Capability, RunContext

from bank_database import DatabaseConn


@dataclass
class SupportDependencies:  # inject any client: DB pools, HTTP APIs, user info
    customer_id: int
    db: DatabaseConn


class SupportOutput(BaseModel):
    support_advice: str = Field(description='Advice returned to the customer')
    block_card: bool = Field(description="Whether to block the customer's card")
    risk: int = Field(description='Risk level of query', ge=0, le=10)


customer_context = Capability[SupportDependencies](  # a reusable unit of tools + instructions
    id='customer-context',
    description="Who the customer is and what's on their account.",
)


@customer_context.instructions
async def add_customer_name(ctx: RunContext[SupportDependencies]) -> str:
    customer_name = await ctx.deps.db.customer_name(id=ctx.deps.customer_id)
    return f"The customer's name is {customer_name!r}"


@customer_context.tool  # signature and docstring become the tool schema the LLM sees
async def customer_balance(
    ctx: RunContext[SupportDependencies], include_pending: bool
) -> float:
    """Returns the customer's current account balance."""
    return await ctx.deps.db.customer_balance(
        id=ctx.deps.customer_id,
        include_pending=include_pending,
    )


refunds = Capability[SupportDependencies](  # deferred: loads on demand, like a skill
    id='refunds',
    description='Refund eligibility and refund status.',
    defer_loading=True,
)


@refunds.tool
async def refund_status(ctx: RunContext[SupportDependencies]) -> str:
    """Look up the refund status for the customer's most recent charge."""
    return await ctx.deps.db.refund_status(id=ctx.deps.customer_id)


support_agent = Agent(
    'openai:gpt-5.6-sol',
    deps_type=SupportDependencies,
    output_type=SupportOutput,  # the run returns a validated SupportOutput, typed as such
    instructions=(
        'You are a support agent in our bank, give the '
        'customer support and judge the risk level of their query.'
    ),
    capabilities=[customer_context, refunds],
)


...  # in a real use case: more tools, longer instructions


async def main():
    deps = SupportDependencies(customer_id=123, db=DatabaseConn())
    result = await support_agent.run('What is my balance?', deps=deps)
    print(result.output)
    """
    support_advice='Hello John, your current account balance, including pending transactions, is $123.45.' block_card=False risk=1
    """

    result = await support_agent.run('I just lost my card!', deps=deps)
    print(result.output)
    """
    support_advice="I'm sorry to hear that, John. We are temporarily blocking your card to prevent unauthorized transactions." block_card=True risk=8
    """

    result = await support_agent.run(  # the model loads `refunds` on demand, then answers
        'Was I refunded for the duplicate charge on my last statement?', deps=deps
    )
    print(result.output)
    """
    support_advice='Good news, John: the duplicate charge on your last statement was refunded on 2026-05-01.' block_card=False risk=1
    """

For the annotated walkthrough and Logfire tracing, see the same example in the docs.

Next Steps

Part of the Pydantic Stack

Everything you need to ship production-grade AI agents:

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.