Chapter 01

Pydantic AI 是什么 · 为何是它

Pydantic 团队的 AI 野心、类型即契约、与三家主流框架的定位差异,以及一段 10 行的 Hello World 会告诉你:这不是另一个 LangChain

一、先别急着写代码:Agent 这件事,到底谁在做?

2024 年底到 2025 年,LLM Agent 框架爆炸式增长。一个 Python 工程师打开 GitHub,能在一下午刷到 10 个"生产级 Agent 框架"。但真正活跃、背后有稳定团队、能长期维护的,其实就那么几家——而且它们的设计哲学截然不同。

在你决定学 Pydantic AI 之前,先把牌桌看清楚:

框架出品方核心哲学上手难度适合场景
LangChainLangChain Inc.万物皆 Chain,胶水式组合中等,但调试痛苦快速原型、复杂 pipeline
LangGraphLangChain Inc.状态机驱动的 Agent偏高,要学图论多步工作流、人在环路
OpenAI Agents SDKOpenAIAssistants API 的生产包装强绑 OpenAI/Azure 的团队
CrewAI / AutoGen社区角色扮演式多 Agent多 Agent 协作演示
Pydantic AIPydantic 团队类型即契约,Python 原生极低(会 FastAPI 就会)工程化、可测试的生产 Agent

Pydantic AI 不是最早的,也不是最火的。它在 2024 年底正式发布 v0.0.x,2025 年才走向稳定。但它解决了一个被其他框架长期忽略的问题:

核心矛盾 LLM 的输出是字符串,但你的业务代码需要结构化数据。大多数框架把"解析字符串"当成附加插件;Pydantic AI 把它当成第一性原理——Agent 的输入、输出、工具参数、依赖,全都是 Pydantic 模型,从头到尾。

二、Pydantic 团队是谁?凭什么入场?

如果你写过一天 Python 后端,大概率用过这几个库的其中一个——而它们共同的作者是 Samuel Colvin:

Pydantic
Python 最流行的数据校验库,下载量每月 3 亿次以上。FastAPI、LangChain、OpenAI SDK、HuggingFace 全部依赖它。2023 年 Pydantic v2 用 Rust 重写校验核心,速度提升 5-50 倍。
FastAPI
虽然作者是 Sebastián Ramírez,但其"类型注解驱动 + 自动 OpenAPI + 依赖注入"的设计范式完全基于 Pydantic。FastAPI 的市场占有率这两年已经接近 Flask。
Logfire
Pydantic 团队 2024 年推出的可观测平台,基于 OpenTelemetry,Python 原生。和 Pydantic AI 深度集成——你写一行 logfire.instrument_pydantic_ai(),Agent 的所有调用、token、成本、trace 全都自动上报。

也就是说,Pydantic 团队手里握着三张牌:最流行的校验库、Python 最火的后端框架同款设计范式、自家的观测平台。做 Agent 框架,他们是最有资格的"地主"。

为什么是 2024 才做 Samuel Colvin 在访谈里说过:"LLM 早期用得少的不是 Agent 能力,是结构化输出。2024 年 OpenAI/Anthropic 的 function calling/structured output 真正稳定后,Pydantic 的价值在 LLM 场景才彻底释放出来——这时候入场正合适。"

三、"类型即契约":这四个字到底什么意思?

这是 Pydantic AI 整本书的核心。我拆开讲:

契约 1:Agent 的输出是类型

传统做法你大概率见过:

# 传统 LLM 调用:字符串进,字符串出
import openai
import json

response = openai.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "北京现在天气?用 JSON 回,字段 city, temp, condition"}],
)
text = response.choices[0].message.content
data = json.loads(text)  # ← 可能爆炸:LLM 漏了个 { 或多了个逗号
city = data["city"]        # ← 可能爆炸:LLM 把字段叫成了 "city_name"
temp = data["temp"]        # ← 可能是 "22°C" 字符串,不是 int

每一步都是潜在炸点,线上每天都有人被这些坑炸到。Pydantic AI 的做法:

from pydantic import BaseModel
from pydantic_ai import Agent

class Weather(BaseModel):
    city: str
    temp: int          # 必须是 int,LLM 给字符串自动转/重试
    condition: str

agent = Agent("openai:gpt-4o", output_type=Weather)

result = agent.run_sync("北京现在天气?")
print(result.output.city)       # IDE 会自动补全 .city
print(result.output.temp + 5)   # int 直接运算,没有 "22°C" 之类的字符串陷阱

result.output 的类型就是 Weather。你的 mypy/pyright 能检查它,IDE 能补全它,pytest 能断言它。LLM 输出不符合?Pydantic AI 会把错误发回去让模型自动重试——不是你再写 try/except。

契约 2:工具的参数是类型

这里和 FastAPI 几乎一模一样:

@agent.tool_plain
def get_order(order_id: int, include_items: bool = False) -> dict:
    """根据订单 ID 查询订单详情。"""
    return db.query_order(order_id, include_items)

Pydantic AI 自动把这个函数转成给 LLM 看的 JSON schema——参数名、类型、默认值、docstring 全都抽出来塞给 function calling。你不写一行 schema,LLM 就知道怎么调。

契约 3:依赖是类型

FastAPI 的 Depends() 用过吧?Pydantic AI 的 deps_type 是一样的思想:

from dataclasses import dataclass
from pydantic_ai import Agent, RunContext

@dataclass
class Deps:
    db: Database
    user_id: int

agent = Agent("openai:gpt-4o", deps_type=Deps)

@agent.tool
def my_orders(ctx: RunContext[Deps]) -> list[dict]:
    return ctx.deps.db.list_orders_for(ctx.deps.user_id)

# 调用时注入
result = agent.run_sync("我的订单都有哪些?", deps=Deps(db=real_db, user_id=42))

测试时用 agent.override(deps=Deps(db=mock_db, user_id=1)) 替换——和 FastAPI 的 app.dependency_overrides 一个味道。

一句话总结"类型即契约":你写的每一个 Agent、工具、依赖,在 Python 层面都是一段普通 Python 代码,有完整的类型标注;在 LLM 层面它自动变成 function schema / structured output 约束。你不需要在 Python 的世界和 LLM 的世界之间手动搭桥——类型就是桥。

四、定位坐标:Pydantic AI 和隔壁四家有什么不一样?

vs LangChain:"拼乐高"与"写代码"

LangChain

  • 万物皆 Runnable,| 管道风
  • 模板抽象多(PromptTemplate / OutputParser / Chain / Retriever / ...)
  • IDE 补全几乎失效,堆栈深到崩溃
  • 好处:胶水多,拼起来快
  • 坏处:出错时你不知道哪一层炸了

Pydantic AI

  • 只有 Agent + Tool + Model 三个核心概念
  • 就是普通 Python 函数 + 装饰器
  • IDE 完整补全,栈短到一眼看穿
  • 好处:代码就是代码,调试就是调试
  • 坏处:生态比 LangChain 小(但你基本也用不到那么多组件)

vs OpenAI Agents SDK:"一家亲"与"provider 中立"

OpenAI 2025 年初推出的 Agents SDK,设计很好——但只支持 OpenAI/Azure OpenAI。如果你哪天要换成 Claude、Gemini、Groq,或者想用 Ollama 本地跑,你得整个重写。

Pydantic AI 从第一天就是 provider-agnostic:

# 切模型就改一个字符串
agent = Agent("openai:gpt-4o")
agent = Agent("anthropic:claude-sonnet-4-5")
agent = Agent("google-gla:gemini-2.5-pro")
agent = Agent("groq:llama-3.3-70b")
agent = Agent("ollama:qwen2.5")
agent = Agent("bedrock:anthropic.claude-sonnet-4-v1:0")

# 剩下所有代码一个字不用改

这点对生产系统巨重要——LLM provider 的定价和能力每季度都在变,锁死一家等于锁死成本。

vs LangGraph:"状态机优先"与"Agent 优先"

LangGraph 是状态机驱动。你先画图(Node + Edge),再把 Agent 当成图里的一个节点。对于多步、有分支、人在环路的复杂工作流,LangGraph 是合理的选择。

但 80% 的场景其实是:一个 Agent + 几个工具 + 一个 structured output。在这种场景用 LangGraph 就是杀鸡用牛刀。Pydantic AI 的哲学是:

简单场景写 Agent,复杂场景再上 Graph Pydantic AI 也有 pydantic_graph 子包(第 7 章会讲),但它是可选的——需要状态机时才用。默认路径是一段平铺直叙的 Python,不逼你先画图再写代码。

vs CrewAI / AutoGen:"角色扮演"与"工程代码"

CrewAI 之类的框架强调"多 Agent 协作"——你定义 CEO Agent、产品经理 Agent、工程师 Agent,让它们互相对话。演示视频效果很炫,真做业务会发现:

Pydantic AI 也支持多 Agent(第 8 章),但把它视作"一个 Agent 可以把另一个 Agent 当作工具来调"——本质是一个普通函数调用,可以 mock、可以测、可以限成本。

五、Hello World:十行代码看懂它在做什么

pip install pydantic-ai

# 或者按 provider 精简安装
pip install "pydantic-ai-slim[openai]"
pip install "pydantic-ai-slim[anthropic]"

设置 API Key(用哪家设哪家):

export OPENAI_API_KEY=sk-...
# 或
export ANTHROPIC_API_KEY=sk-ant-...

from pydantic_ai import Agent

agent = Agent(
    "openai:gpt-4o-mini",
    system_prompt="你是一个凝练的技术答疑助手,回答不超过 3 句话。",
)

result = agent.run_sync("解释一下 Python 的 GIL。")
print(result.output)

运行:

GIL(全局解释器锁)是 CPython 解释器在同一时刻只允许一个线程执行 Python 字节码的机制。
它简化了 CPython 的内存管理,但也导致 CPU 密集任务无法用多线程真正并行。
可以改用多进程或把热点交给 C 扩展(如 NumPy)绕开 GIL。

把"字符串返回"升级成"结构化返回"

from pydantic import BaseModel, Field
from pydantic_ai import Agent

class Concept(BaseModel):
    name: str
    one_liner: str = Field(description="不超过 30 字的定义")
    pitfalls: list[str] = Field(description="最常见的 2-3 个坑")

agent = Agent("openai:gpt-4o-mini", output_type=Concept)

result = agent.run_sync("解释 Python GIL")

print(result.output.name)           # "GIL" (str)
print(result.output.one_liner)      # "CPython 的线程互斥锁..."
for p in result.output.pitfalls:  # list[str]
    print("- ", p)

这一下你就体感到了:

六、运行时看见的东西:Agent 到底发了什么

很多新手觉得 Agent 框架"魔法太多"。Pydantic AI 特别坦诚——你可以把任意一次调用的完整消息流打印出来:

result = agent.run_sync("解释 Python GIL")

print(result.all_messages())
[
  ModelRequest(parts=[
    SystemPromptPart(content='你是一个凝练的技术答疑助手...'),
    UserPromptPart(content='解释 Python GIL'),
  ]),
  ModelResponse(parts=[
    ToolCallPart(tool_name='final_result', args={'name': 'GIL', ...}),
  ]),
  ModelRequest(parts=[
    ToolReturnPart(tool_name='final_result', content='Final result processed.'),
  ]),
]

所有看似"魔法"的东西——结构化输出、工具调用、重试——底层都是普通的 ModelRequest / ModelResponse 消息。你能看到每一个字节是怎么发给 LLM 的。出问题时这点就是救命稻草。

七、这门教程的 10 章要带你走完什么

  1. 本章:搞清楚 Pydantic AI 是谁、和谁不一样、第一段代码跑起来
  2. 第 2 章:Agent 构造参数全解 / system prompt 动静态 / provider 切换 / run · arun · stream 三条 API
  3. 第 3 章:output_type 全貌——BaseModel / Union / TypedDict / dataclass / 验证失败重试机制
  4. 第 4 章:Tool 装饰器、从签名推 schema、RunContext、多工具并行、ModelRetry
  5. 第 5 章:Dependency Injection 全套——deps_type / RunContext / override / FastAPI 对比
  6. 第 6 章:流式(文本流 · 结构化流) + 消息历史管理 + 多轮对话模式
  7. 第 7 章:pydantic_graph 状态机——BaseNode / End / 持久化 checkpoint / 实战客服分流
  8. 第 8 章:多 Agent 协作——Agent 当工具、programmatic handoff、delegate、成本控制
  9. 第 9 章:测试与 Eval——TestModel / FunctionModel / capture_run_messages / pytest 集成
  10. 第 10 章:Logfire 观测、FastAPI 集成、Docker 部署、生产 checklist

八、本章小结

记住这三句话再进入下一章:
① Pydantic AI 是 Pydantic 团队做的 provider 中立 的 Agent 框架。
② 它的核心设计哲学是类型即契约——Agent 的输入输出、工具参数、依赖,全部 Pydantic 化。
③ 它不替代 LangGraph(复杂状态机)或 LangChain(超大拼乐高生态),但在 需要可测试、可类型检查、provider 可切换 的生产 Agent 上,它是目前最工程化的选择。

延伸阅读