pydantic-ai 不提供 Memory 类、不做自动 context 截断、不自动重试 HTTP、不预设 Planner-Executor。这些克制不是缺陷,而是它能跑生产的原因 — 因为 agent 框架真正难的从来不是"做更多",而是"不做错"。
LangChain 1300+ 个抽象、CrewAI 把"角色"做成一等公民、AutoGen 在 0.4 重写了三次。pydantic-ai 在 2024.11 进场,2025 跨过 1.0,2026 年成为唯一一个 agent loop 本身就用自己 graph 库实现的框架 — 4 节点状态机 · 0 个 Memory 类 · 1 个 list[ModelMessage] · retry 按 tool 名隔离。它没有发明新概念,它把"什么不该做"想清楚了。
2022 年底 LangChain 出生时,Agent 框架还是个稀缺品。三年后,这个赛道挤满了名字 — LangChain、LangGraph、CrewAI、AutoGen、Haystack、LlamaIndex、Semantic Kernel、Phidata。每家都在卖一套不同的"抽象哲学":有的把 Agent 等同于 Chain,有的把它建模成会议室里的角色扮演,有的干脆把它升格成状态机的特化。然后在 2024 年 11 月,Pydantic 团队从一个新的角度切入这个市场 — 不是发明新概念,而是把 FastAPI 的设计语言搬过来。
Pydantic 不是一个新名字。它是 OpenAI SDK、Google ADK、Anthropic SDK、LangChain、LlamaIndex、AutoGPT、Transformers、CrewAI、Instructor 共同依赖的数据校验层。"全行业都在用我们的校验,为什么不直接做一个真正用得舒服的 agent 框架?" — pydantic-ai 的 README 第一句话就是这个反问。
但这份 brief 不打算重复 README。它要回答一个更尖锐的问题: 当所有 agent 框架都在'做更多'的时候,pydantic-ai 凭'做更少'怎么能赢?
用一句话概括 pydantic-ai 的崛起: 当其他框架在卷"抽象数量"时,它选择卷"抽象密度"。整个核心库少于 30 个公开类,但每个类背后都有 3-5 个具体的工程问题被解到位 — 比如 RunContext.retries: dict[str, int] 这一个字段同时回答了"retry 怎么计数"、"按什么粒度隔离"、"output validator 该不该共享配额"三个问题。
LangChain 在 2023 年最被夸的设计是它的 Memory 子类家族 — ConversationBufferMemory、ConversationBufferWindowMemory、ConversationSummaryMemory、VectorStoreRetrieverMemory、EntityMemory。每个类对应一种"对话记忆"策略,开箱即用。结果两年后呢?大部分生产团队都用自己的 Memory 包了一层,因为内置的 Memory 类要么太死板,要么对 tool call 的 message pairing 处理不对。
pydantic-ai 的回答是: 我们不做 Memory。会话状态压成list[ModelMessage] — 一个 Python list,带强类型 schema。整个框架只有这一条"状态主线"。要做记忆,你自己拿这条 list 去做你想要的事。
| 设计维度 | LangChain (Battery-Included) | pydantic-ai (Strong Primitives) |
|---|---|---|
| 会话状态容器 | ~7 种 Memory 类 + Session | 1 个 list[ModelMessage] |
| 消息序列化 | Memory 子类各自实现 save/load | TypeAdapter(list[ModelMessage]) · 一行 |
| context window 截断 | 5 种内置策略(window/token/summary...) | 0 内置 · 1 个 ProcessHistory 钩子 |
| 动态 system prompt | 每次重渲染 LCEL template | 持久化为 SystemPromptPart(dynamic_ref) · 历史自带反射 |
| 业务依赖注入 | 塞进 Memory 或全局 chain config | Agent[Deps, Output] · IDE 跳转 · 不可变 |
| State schema 长期演化 | 字段膨胀 · 团队规约难统一 | 永远是消息列表 · schema 不外溢 |
| tool 失败时 | OutputParserException / 通用 Exception | UserError / AgentRunError / UnexpectedModelBehavior / ModelHTTPError 严格四类 |
| 并行 tool 失败 | asyncio.gather · fail-fast · 一个挂全挂 | asyncio.wait(FIRST_COMPLETED) · partial failure tolerant |
| retry 计数 | 全局 · RunnableRetry 黑盒重跑 | 按 tool 名隔离 · 错误结构化喂回 LLM |
| 多 Agent 编排 | AgentExecutor / Chain 组合 / Plan-Execute 工具包 | 3 种原语: tool delegation · programmatic handoff · pydantic_graph |
这张表里每一行,LangChain 都给了"更多",pydantic-ai 都给了"更少"。看起来 pydantic-ai 在偷懒。但当你写过一个生产系统,你会发现每一项 pydantic-ai 的"少",都是它把某个 LangChain 的"多"想清楚之后,主动删掉的。比如 retry — LangChain 的 RunnableRetry 是黑盒重试(同一个 chain 重跑,LLM 看不到错误信息),根本构不成 self-correcting loop。pydantic-ai 把错误结构化喂回去,LLM 真能从错误里学会下一次该传什么参数。
"我们偏好 strong primitives、powerful abstractions、general solutions and extension points,而不是狭窄的特定方案、未经时间检验的固执意见、或'什么都塞'的臃肿。" — pydantic-ai CLAUDE.md · 仓库根目录
"做更多容易。
想清楚不做什么 — 那才难。"
把 AI Agent 工程师 JD 的三个问题翻译成系统设计语言,本质上是 agent 框架必须回答的三个根问题:
① 状态与上下文管理 = "对话的事实"放在哪里?谁拥有它?谁能改它?长了怎么办?
② Tool Calling 稳定性 = LLM 是不可靠的执行者,工具调用是脆弱的桥梁,框架在中间承担多少容错责任?
③ 多 Agent 流程 = 当问题超出单 LLM 一次推理的边界,框架提供什么编排原语?
下面三段,每段用一张表回答 pydantic-ai 对该问题的具体答案。
| 子问题 | pydantic-ai 的答案 | 代码位置 |
|---|---|---|
| 会话状态在哪? | GraphAgentState.message_history · 一个 Python list | _agent_graph.py:117 |
| 消息结构? | ModelMessage = Annotated[ModelRequest|ModelResponse, Discriminator('kind')] | messages.py:2359 |
| 序列化? | ModelMessagesTypeAdapter = TypeAdapter(list[ModelMessage]) | messages.py:2363 |
| 怎么截断 context? | 框架不做 · ProcessHistory capability 是用户写的 hook | capabilities/process_history.py:28 |
| 多轮 trace 关联? | conversation_id / run_id 戳在每条消息上 · 无需外置 session 表 | messages.py:1564-1572 |
| 业务依赖怎么注入? | Agent[Deps, Output] · build_run_context 每节点重建 RunContext[Deps] | _agent_graph.py:1387 |
| 动态 system prompt? | SystemPromptPart(dynamic_ref=fn.__qualname__) · 持久化在历史里 · resume 时按 ref 重算 | _agent_graph.py:402 |
| token / 调用次数? | UsageLimits 三检查点 · check_before_request / check_tokens / check_before_tool_call | usage.py:262, 378 |
关键观察: pydantic-ai 把"业务依赖(deps)"和"会话状态(state)"严格分离。LangGraph 把所有东西塞 TypedDict 让 reducer 合并,pydantic-ai 把业务数据放在 deps 里(不可变服务句柄),state 永远是消息列表 — schema 不会因为业务字段膨胀而失控。代价是没有 LangGraph 那种"任意 state 字段自动 checkpoint"的开箱即用;收益是 deps 永远类型安全,state schema 不会爆炸。
| 失败层 | 触发条件 | 处理路径 | 是否回喂 LLM? |
|---|---|---|---|
| L1 协议 | LLM 给的 args JSON 过不了 schema | ToolRetryError → RetryPromptPart(ValidationError.errors()) | ✓ 完整错误列表 |
| L1 协议 | LLM 幻觉的 tool 名 | raise ModelRetry(f'Unknown tool name: ... Available: ...') | ✓ 连可用工具都告诉它 |
| L1 协议 | tool 主动 raise ModelRetry("...") | 包成 ToolRetryError(RetryPromptPart(content=message)) | ✓ 开发者自定义 |
| L2 业务 | tool 抛 ValueError 等业务异常 | 框架不 catch · 穿透 except BaseException · cancel 兄弟任务 · 终结 run | ✗ 业务方必须自己 try/except |
| L3 传输 | @tool(timeout=N) 超时 | anyio.fail_after → ModelRetry('Timed out after N seconds.') | ✓ 自动转 retry |
| L4 模型 | provider 4xx/5xx | ModelHTTPError · 不进 retry · 立刻终结 | ✗ 要重试自装 retries.py transport |
| L4 模型 | 空响应 / content filter | UnexpectedModelBehavior / ContentFilterError / 消耗 output_retries 重试 | 部分 |
这是 pydantic-ai 最容易让新人踩坑的设计 — tool 函数体里抛 ValueError 不会被框架 catch,会直接穿透到 _agent_graph.py:1906 的 except BaseException,触发 cancel_and_drain(*tasks) 把同一轮所有兄弟 tool 任务取消,然后 re-raise 终结整个 run。生产环境里 每一个 tool body 必须自己 try/except,把业务异常转成 ModelRetry,否则一次 KeyError 能让 multi-tool 并行的整轮全挂 — 包括成功的那些。
self-correcting loop 的核心机制: RunContext.retries: dict[str, int] — 按 tool 名分别计数,不是全局。一个不稳定的 tool 不会偷走整个 run 的 retry 预算。Output validator 走完全独立的 output_retries_used 配额,与 tool retry 隔离。这种"细颗粒度配额隔离"是 LangChain 的 RunnableRetry 全局黑盒模式根本做不到的。
| 模式 | 什么时候用 | 机制 | 核心限制 |
|---|---|---|---|
| ① Agent-as-tool | LLM 自主决定调谁(动态分派) | @agent.tool 里 await other_agent.run(usage=ctx.usage, deps=ctx.deps) | 嵌套层数过深会 token 爆炸 |
| ② Programmatic handoff | application 层确定的多步流程 | 普通 Python 函数调用 + structured output 类型对齐 | 无专门 API · 全靠类型系统 |
| ③ pydantic_graph | 需要循环 · 分支 · checkpoint · resume | BaseNode + return type annotation 即边 + StatePersistence | 学习曲线陡 · 不适合简单场景 |
关键洞察 — agent-as-tool 的 usage 累加机制: parent agent 把 ctx.usage 这个 RunUsage 对象引用传给 child agent,child 的 ModelRequestNode._append_response 直接 incr 同一个对象。所以 parent 的 UsageLimits 自动覆盖整个 sub-tree — 不需要手动累加。LangChain 的多 agent 串联里,token 计数是个噩梦,因为没有这个引用共享设计。
docs/graph.md 原话: "Don't use a nail gun unless you need a nail gun." 翻译成中文: "graph 是钉枪 — 没必要时别用。" pydantic_graph 是为了"必经的多步 + 循环判断 + 持久化恢复"准备的,不是为了让你把简单的 ReAct loop 也画成图。简单场景用 agent.tool,中等用 programmatic handoff,复杂才上 graph。
读 pydantic-ai 官方文档,你看到的是"How to use"。读源码,你看到的是"Why we built it this way"。下面八个设计决策没有写在显眼的地方,但每一个都解决了 LangChain/LangGraph 用其他方式回避或妥协的工程难题。这是面试时拿出来讲最有杀伤力的内容。
GraphAgentState.message_history 这条 list 同时被 capture_run_messages(用户 API)、RunContext(tool/system_prompt 内部)、graph 各节点(UserPromptNode/ModelRequestNode/CallToolsNode)共享同一份引用。任何节点 mutate(append、history processor replace)立刻对所有视图生效,整个框架不需要 message bus。一行 Python 引用语义解决了 LangGraph 用 reducer 解的问题。
capabilities/process_history.py:28 全部代码不超过 80 行,核心就是 before_model_request 钩子的薄包装。processor 签名 (list[ModelMessage]) → list[ModelMessage]。要做 token-aware 截断、PII 脱敏、用小模型 summarize 旧消息 — 全是用户自己写 processor。框架给的承诺只有两条: 调用时机(在每次 model request 前)、约束(tool-call/return 不能拆散)。这种"给协议、不给实现"的设计,等于把 Memory 子类的复杂度全部移到了边界外。
@agent.system_prompt(dynamic=True) 不是每次重拼 prompt,而是生成 SystemPromptPart(dynamic_ref=fn.__qualname__) 持久化在 message_history 里。UserPromptNode._reevaluate_dynamic_prompts(_agent_graph.py:402)在每次 resume 时按 ref 找到原 runner 重算后替换。动态性被 encode 进消息本身,让历史自带反射能力。LangChain 的 LCEL template 是每次重渲染,resume 时如果换了机器、原来的 closure 没了 — 就废了。
tool_manager.py:184-191 的 _wrap_error_as_retry 直接调用 err.errors(include_url=False, include_context=False) 把 Pydantic 完整错误列表(含字段路径、input value、error type)打包进 RetryPromptPart.content,作为下一轮 ModelRequest 喂回 LLM。LLM 看到的不是 "tool failed",而是"参数 customer_id 类型应为 int 但收到 string '12a3'"。这是 self-correcting loop 真正的样子 — LangChain 的 RunnableRetry 黑盒重试根本做不到。
RunContext.retries: dict[str, int](tool_manager.py:177-181)— 一个不稳定的 tool 不会偷走整个 run 的 retry 预算。output_retries_used(_agent_graph.py:158-175)又是完全独立的配额,与 tool retry 隔离。这种细颗粒度配额隔离是默认就有的,不需要任何配置。LangChain 的全局 retry 计数下,一个坏 tool 能把整个 run 给"饿死"。
_agent_graph.py:1872-1901: 同一轮多个 tool call 用 asyncio.create_task + asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)。故意不用 asyncio.TaskGroup — 因为 TaskGroup 的"一个失败全部取消"与"工具失败也算 retry 信号、要喂回 LLM"语义直接冲突。每个 _call_tool 内部 catch ToolRetryError 转 RetryPromptPart 作为正常返回,单个工具失败不取消并行兄弟任务。LangChain 的 parallel_tool_calls 走 asyncio.gather 是 fail-fast,生产 partial-failure 容忍度差很多。
_agent_graph.py:2066-2095 的 build_agent_graph() 返回 Graph[GraphAgentState, ...]。ReAct loop 不是 while 循环,是 4 节点状态机 — UserPromptNode → ModelRequestNode → CallToolsNode → (ModelRequestNode | End)。用户在 graph 节点里调 Agent.run 等于在 graph 里嵌套 graph。LangGraph 的 agent 抽象和 graph 抽象是两个分裂的东西 — pydantic-ai 是真正的"自吃狗粮"。
BaseNode.get_node_def(basenode.py:104-136)用 get_type_hints(cls.run) 读 run 方法的 return annotation,拆 union 作为出边。async def run(self, ctx) → AnotherNode | End[int] 这一行就定义了拓扑的两条出边。没有 add_edge 调用。LangGraph 是动态的,pydantic_graph 是声明式的 — 代价是更难写运行时动态图,收益是 graph 在 build 时就 fully validate,IDE 跳转能力强,wrong return type 在 Pyright 阶段就报错。
"FastAPI revolutionized web development by offering an innovative and ergonomic design, built on the foundation of Pydantic Validation and modern Python features like type hints. We built Pydantic AI with one simple aim: to bring that FastAPI feeling to GenAI app and agent development." — pydantic-ai README · 设计宣言
这是 JD 加分项里"对哪些方法适合生产、哪些只适合 demo 有清晰判断"的直接答案。不是泛泛而谈,是 pydantic-ai 在源码里、文档里、issue 里反复出现的具体边界。
不改任何配置就能上线的能力
默认值偏 demo,生产前必须显式改
无论怎么配,框架都不解决的问题
单 Agent · 中小规模 tool 集 · 团队有 Python/Pydantic 背景 · 需要 production 但不需要 Temporal 级别的 durable execution
多 Agent · 复杂工作流 · 需要 RAG/Memory · 需要 human-in-the-loop · 需要分布式
团队不熟 Python/类型系统 · 严重依赖现有 LangChain 生态(eg. 一堆 LangChain 自定义 Tool)· 需要可视化拖拽 agent builder
① Agent(retries={'tools': 3}) · 默认 1 偏紧
② pip install 'pydantic-ai-slim[retries]' + 挂 AsyncTenacityTransport(wait=wait_retry_after(max_wait=300), stop=stop_after_attempt(5))
③ graph 持久化用 FullStatePersistence 或自实现 DB-backed StatePersistence
④ 每个 tool body 加 try: ... except BusinessError as e: raise ModelRetry(str(e))
⑤ 长对话挂 capabilities=[ProcessHistory(token_aware_truncate)] · 自己写 processor
这份 brief 的核心结论很简单: pydantic-ai 不是"功能更少的 LangChain",它是"想得更清楚的 LangChain"。每一个看似 missing 的功能(Memory / 自动截断 / 自动 HTTP retry / Planner-Executor),都不是 pydantic-ai 团队没想到,而是他们想清楚之后决定不做的。
原因有两个: 第一,这些功能大多数情况下需要业务方根据自己的语义来定 — 框架默认实现要么太死板(命中场景窄),要么默认错(比如 ConversationBufferWindowMemory 默认不知道你的 tool-call/return 必须配对)。第二,这些"battery"会让 API 表面积膨胀,长期来看团队规约和 backwards-compat 都难维护 — 这就是为什么 LangChain 在 v0.1→v0.3 之间反复 breaking change,而 pydantic-ai 在 1.0 之后冻结 API 还能保持简单。
Q1 答案: 不在同一层。pydantic-ai 是 agent 框架 + 一个 type-hint-driven 的 graph 子库(pydantic_graph);LangGraph 是 graph 框架 + 一个 agent 抽象。pydantic-ai 的 agent loop 本身就用自己的 graph 库实现。简单 ReAct 场景选 pydantic-ai,需要可视化复杂工作流选 LangGraph。
Q2 答案: LLM 自主决定调谁(动态分派)→ agent-as-tool(共享 RunUsage)。application 层确定的多步流程 → programmatic handoff。需要循环/分支/checkpoint/resume → pydantic_graph。官方文档原话: graph 是钉枪,没必要时别用。
Q3 答案: 四层失败各走各路。L1(参数错/工具名错/主动 ModelRetry)框架结构化喂回 LLM 形成 self-correcting loop。L2 业务异常(ValueError 等)框架不 catch,直接终结 run + cancel 兄弟任务。L3 timeout 自动转 ModelRetry。L4 provider HTTP 错误不重试,要自装 retries.py。生产里 tool body 必须自己 try/except。
Q4 答案: 框架不做自动截断。挂 capabilities=[ProcessHistory(my_processor)],processor 签名是 list[ModelMessage] → list[ModelMessage]。要 token-aware 截断、PII 脱敏、用小模型 summarize 旧消息都在 processor 里写。约束是必须保证 tool-call/return 配对。
Q5 答案: 默认能跑,默认不能扛生产。至少改 5 处: retries 调到 3,装 retries.py 的 httpx transport,graph 持久化换 FullStatePersistence,每个 tool body 加 try/except 转 ModelRetry,长对话挂 ProcessHistory + 自写 processor。
Q6 答案: 适合生产 — schema 校验回喂、tool timeout、partial failure tolerant、retry budget 按 tool 名隔离、UsageLimits、conversation_id 自带 trace。只适合 demo — 默认 retries=1、HTTP 无自动 retry、SimpleStatePersistence、tool 默认无 try/except、无自动 context 截断。框架不管 — 语义 Memory、跨进程 rate limit、OTel 上报、provider failover。
这份 brief 的金句、表格、file:line 引用,都是为面试准备的"弹药"。重点记三组对比 — pydantic-ai vs LangChain(state schema 不外溢)、asyncio.wait vs gather(partial failure)、agent loop 是 graph 实例(vs LangGraph 两个分裂抽象)。
关键纪律: 不要背"功能列表",要讲"为什么不做"的设计决策。面试官能立刻分辨"你读过文档"还是"你读过源码"。
pydantic-ai 是为你设计的。Pydantic 校验你熟,dataclass 你熟,async/await 你熟,deps 注入概念就是 FastAPI 的 Depends 翻版。从 Hello World 到生产可能就 2 天 — Day 1 跑通 Agent + tool + UsageLimits + structured output;Day 2 改 5 处默认配置上生产。
关键纪律: 不要从 LangChain 教程出发学习,从 docs/agent.md → docs/tools.md → docs/multi-agent-applications.md 顺序读,跳过所有"对比 LangChain"的章节,直接学 pydantic-ai 的原语。
心智迁移要点: ① Memory 子类→自写 ProcessHistory processor(自由度更高但要自己写);② RunnableRetry→ToolRetryError + 按 tool 名 retries;③ Chain 组合→agent-as-tool 或 programmatic handoff;④ State dict→Agent[Deps, Output] 的 deps + structured output;⑤ AgentExecutor.invoke→Agent.run / Agent.iter。最难的迁移是 Memory 那层 — 你的 ConversationSummaryMemory 没有对应实现,要自己用小模型 + ProcessHistory hook 拼。
关键纪律: 不要一次性整体迁移。先把一个独立的 sub-flow 用 pydantic-ai 重写,跑一个月对比可观测性数据(Logfire/OTel),再决定要不要扩。pydantic-ai 1.0 之后冻结 API 这一点对迁移团队是巨大利好 — 不会一年改三次。
| # | 信号 | 触发判断 / 动作 |
|---|---|---|
| 1 | pydantic-ai 1.x → 2.x 是否 breaking | 如果保持 1.x 长期稳定 → 生产采用率会显著上升 |
| 2 | durable_exec 的 Temporal/DBOS 用户案例 | 第一个公开的"用 pydantic-ai+Temporal 跑了一年"案例出现 → 企业级背书 |
| 3 | MCP server 模式的成熟度 | 越来越多 SaaS 直接发布 MCP server → pydantic-ai 自动获益(不需要写新 integration) |
| 4 | Capability 第三方包数量 | >20 个高质量第三方 capability 包 → "extensibility by design"哲学被验证 |
| 5 | pydantic-evals 的 LLM-as-judge 采用 | 评估子库被独立项目引用 → "测试与代码同等重要"的 pydantic 哲学延伸到 agent |
| 6 | retries.py 是否默认接入 | 如果某天 v1.x 把 AsyncTenacityTransport 默认开启 → demo/prod 差距进一步缩小 |
| 7 | GraphBuilder API 是否稳定 | 新版 Fork/Join/Decision API 是否在 2026 H2 冻结 → graph 用户增长率拐点 |
| 8 | agent_spec (YAML/JSON 定义 agent) | 无代码定义 agent 普及度 → 低代码 agent 用户进入 |
| 9 | LangChain 是否吸收 pydantic-ai 设计 | LangChain v0.4 是否引入"自吃狗粮"的 agent-as-graph → 行业共识在形成 |
| 10 | OpenAI Assistants API 等官方 SDK 是否影响 pydantic-ai 地位 | OpenAI 自带 agent SDK 成熟 → 跨 provider 框架需求是否被压缩 |
"你不需要更多的 Memory 类。
你需要的是,框架别替你决定。"
· pydantic-ai 源码(2026-05 clone)· /Users/epingpong/CodeBuddy/agent-dev/pydantic-ai/
· pydantic-ai 官方文档 · ai.pydantic.dev
· README + CLAUDE.md(仓库根目录)设计哲学陈述 · github.com/pydantic/pydantic-ai
· docs/multi-agent-applications.md · 三种模式官方分类 · ai.pydantic.dev
· docs/graph.md · pydantic_graph 哲学("nail gun"那段)· ai.pydantic.dev
· docs/retries.md + docs/message-history.md · 生产级别细节 · ai.pydantic.dev/retries
· LangChain v0.3 文档(对比基准)· python.langchain.com
· LangGraph 文档(对比基准)· langchain-ai.github.io/langgraph
· 关联调研: langgraph-investigation-v1.md / max-ai-design-v1.md / pydantic-ai-investigation-v1.md(本地 agent-dev 目录)