进阶
用 Pydantic AI V2 写类型安全的生产级 Agent(FastAPI 风格)
如果你写过 FastAPI,一定迷恋过"类型标注即文档、即校验"的开发体验。2026 年 6 月发布的 Pydantic AI V2 把这套体验搬到了 AI Agent 上:工具入参有类型、结构化输出有类型、依赖注入也有类型,编译/运行前就能拦住一大堆低级错误。本教程带你从零搭一个生产级 Agent,并讲清它和"裸调 SDK"到底差在哪。
🔷 本教程适合:会用 Python、希望 Agent 代码"像后端服务一样可靠"的开发者。你要有一个 OpenAI / Anthropic / Gemini 的 API Key。不需要提前懂 Pydantic,跟着做就行。
先搞懂:Pydantic AI 强在哪?
用一句话理解:Pydantic AI 是"给 Agent 上类型系统"的框架——它底层还是调各家大模型,但把你和模型之间的"接口"用 Python 类型钉死,避免"模型返回了一坨对不上的 JSON"这种生产事故。
| 能力 | 裸调 SDK | Pydantic AI V2 |
|---|---|---|
| 工具入参 | 自己解析、容易类型错 | 函数签名即 schema,自动校验 |
| 模型输出 | 拿到字符串自己 json.loads | 直接拿到类型化对象 |
| 依赖(DB/配置) | 全局变量乱传 | RunContext 依赖注入,可测 |
| 评测 | 手写断言 | 内置 pytest fixture 与 LLM 评测 |
什么时候别用它?如果你只是想快速验证一个想法、跑个一次性脚本,裸 SDK 或 llm CLI 更轻。Pydantic AI 的回报在"要长期维护、要接真实业务"时才会显现。
Step 1:安装与最小依赖
1 装好 pydantic-ai
pip install pydantic-ai
# 想用哪家模型就装对应 extra(可选,不装也能跑 OpenAI)
pip install 'pydantic-ai[openai]'
pip install 'pydantic-ai[anthropic]'
# 设置 Key(以 OpenAI 为例)
export OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
💡 模型字符串写成
"openai:gpt-4o"、"anthropic:claude-..." 这种 "厂商:模型名" 格式,换模型只改一个字符串,业务代码不动。Step 2:你的第一个 Agent
2 三行跑通
from pydantic_ai import Agent
agent = Agent(
'openai:gpt-4o',
system_prompt='你是简洁的中文助手,回答不超过两句话。',
)
# 同步调用(脚本里最方便)
result = agent.run_sync('用一句话解释什么是类型安全。')
print(result.output)
🔑
run_sync 是同步版(适合脚本/测试),异步用 await agent.run(...)。返回对象里 result.output 是最终文本,result.data 是结构化结果(下一步讲)。Step 3:给 Agent 加类型化工具
3 用 @agent.tool 把函数变成工具
工具就是普通 Python 函数,参数和返回值的类型标注就是给模型的schema。模型要调工具时,Pydantic 会先校验入参:
from pydantic_ai import Agent
agent = Agent('openai:gpt-4o', system_prompt='需要时用工具查汇率。')
@agent.tool
def get_rate(from_cur: str, to_cur: str) -> float:
"""查询两种货币间的汇率。"""
# 真实场景调用汇率 API;演示返回固定值
rates = {'USD': 1.0, 'CNY': 7.2, 'EUR': 0.92}
return rates[to_cur] / rates[from_cur]
result = agent.run_sync('100 美元能换多少人民币?')
print(result.output)
类型就是契约:如果模型传了 from_cur="美元"(而不是 "USD"),Pydantic 的校验会失败并回抛错误让模型重试。这比手写解析稳得多。
Step 4:结构化输出(最香的特牲)
4 让模型直接返回对象,不是字符串
用 output_type 指定一个 Pydantic Model 或 dataclass,模型输出会被自动解析成对象:
from pydantic import BaseModel
from pydantic_ai import Agent
class WeatherReport(BaseModel):
city: str
temp_c: float
advice: str
agent = Agent('openai:gpt-4o', output_type=WeatherReport)
result = agent.run_sync('北京现在天气怎么样,我能穿短袖吗?')
r: WeatherReport = result.output
print(r.city, r.temp_c, r.advice) # 直接当对象用,不用 json.loads
💡 这一步直接消灭了"模型返回格式不对、下游解析崩"的高发故障。结构化输出还能配合"严格模式"强制模型遵守字段,非常适合接数据库、表单、API。
Step 5:依赖注入与多 Agent 协作
5 用 RunContext 注入 DB/配置,用 handoff 转交专家
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
@dataclass
class Deps:
user_id: str
db: dict # 真实场景是数据库连接
agent = Agent('openai:gpt-4o', deps_type=Deps)
@agent.tool
def get_user_name(ctx: RunContext[Deps]) -> str:
"""根据上下文拿到当前用户名。"""
return ctx.deps.db.get(ctx.deps.user_id, '朋友')
# 多 Agent:把特定任务 handoff 给专家 Agent
support = Agent('openai:gpt-4o', system_prompt='你是售后专家。')
main = Agent('openai:gpt-4o',
system_prompt='遇到售后问题,交给 support 处理。',
handoffs=[support])
result = main.run_sync('我的订单还没到', deps=Deps(user_id='u1', db={'u1': '小明'}))
print(result.output)
💡
deps 通过 ctx.deps 在工具里取用,不污染全局变量,单测时随便 mock。handoffs 让主 Agent 自己决定"这事该交给谁",是构建多 Agent 系统的干净写法。Step 6:用 pytest 做评测与守卫
6 把"Agent 表现"变成可回归的测试
# test_agent.py
from pydantic_ai import Agent
agent = Agent('openai:gpt-4o', output_type=bool,
system_prompt='判断一句话是否含负面情绪,只返回 true/false。')
def test_sentiment():
r = agent.run_sync('这个产品太难用了,想退款')
assert r.output is True
生产级的关键一步:Pydantic AI 配套 pydantic-ai-sci / logfire 做评测与追踪。把核心用例写成测试,模型一升级就能立刻发现"是不是变笨了"。这是裸 SDK 很难低成本做到的。
Step 7:部署与可观测性要点
7 上线前一次配齐
1) 模型供应商解耦:output_type / tool / deps 都和具体模型无关,
换 'anthropic:...' 只改 Agent 构造那一行。
2) 可观测:接入 Logfire(Pydantic 官方观测)一行开启
# pip install logfire && logfire configure
# export LOGFIRE_TOKEN=xxx
3) 超时与重试:给 Agent 包一层你服务的超时/限流,
别让单个慢模型拖垮整条请求链路。
4) 成本护栏:用 output_type 收敛输出、用 system_prompt 限范围,
避免模型乱挥发大 token 消耗。
🎉 到此你已掌握 Pydantic AI V2 的核心:类型化工具、结构化输出、依赖注入、handoff 多 Agent、可测可观测。它最适合"把 Agent 当正规后端服务来写"的团队。
常见问题速查
| 现象 | 大概率原因 & 解决 |
|---|---|
| 报 "model not found" | 模型字符串格式应为 厂商:模型名,如 openai:gpt-4o |
| 工具没触发 | system_prompt 没提示要用工具,或函数没标注类型 |
| output_type 解析失败 | 模型输出不守结构 → 开严格模式或放宽字段/加示例 |
| 测试偶发失败 | 模型有随机性 → 固定 seed 或断言"语义"而非"字面值" |