你经营过一家深夜小馆吗?有一位想象力过人的老主顾——AI大模型——每次点菜都说“随便来点好吃的”。你端上面,他说想吃炒饭;端上炒饭,他又想喝汤。后厨被折腾得人仰马翻,顾客满意度却还是零。直到有一天,你设计了一张“标准点菜单”:菜名必须写清,辣度只能在0到3之间选,数量必须是正整数。老主顾照样满嘴跑火车,但你把他的话翻译成单子,格式不对当场打回重填。从此每一道菜都精准命中需求,后厨终于安静了。 Pydantic AI 做的就是这样一件事。它基于 Pydantic 类型系统,给大模型套上一张“标准点菜单”:你定义好输入输出长什么样,框架负责拦截乱写的、纠正跑偏的、验证不过就重试,直到拿到符合规范的结构化结果。开发 AI Agent 不再靠“祈祷模型别胡说”,而是靠一套可靠的、可校验的工业流程。
对 AI 从业者而言,掌握类型安全 Agent 开发,意味着你的 LLM 应用从“演示玩具”升级为“生产级工具”,结构化输出和工具调用的可靠性直接决定项目能否落地企业。
可视化演示
动态演示Pydantic AI让Ag的核心机制,通过入门/进阶/实战/优化四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Pydantic AI让Ag:Pyda Pydantic AI框架概览 Agent定义与工具函数注册机 核心机制可视化 Pydantic AI让Ag整体架构与关键组件
类型安全价值
老主顾的那句“随便来点好吃的”,本质上是把一堆模糊需求抛给了后厨。LLM 也一样——它的原生输出是自由文本,语法正确、内容通顺,但结构完全不可预测。今天返回“宫保鸡丁,辣度:中”,明天可能返回 {"dish": "宫保鸡丁", "spicy": "微辣"}。对下游程序来说,这两种格式就是两套截然不同的接口,解析逻辑根本没法写。
Pydantic AI 的改变在于:它要求你先把“菜单”定好——输出必须长成什么样、每个字段是什么类型、取值范围是什么。这背后是 Pydantic 的强项:Python 类型注解不只是写给人看的注释,而是运行时强制执行的校验规则。你声明 spicy_level: int = Field(ge=0, le=3),那么返回 4 就是非法,直接拦截。
有了这张“标准点菜单”,从“碰运气”变成“可复制、可核验”。后厨不再猜谜,程序不再写脆弱的正则去碰输出——这是类型安全带来的第一层价值。
- LLM(x)大模型接收用户输入 x,生成一段原始自然语言输出,内容自由、格式随意。
- 验证器 V_typePydantic 根据你声明的模型结构,对这段输出的每个字段做类型、范围、必填校验。
- Extract_struct只有通过校验的输出,才会被转换成结构化对象,安全地交给下游函数或数据库。
Agent核心配置
开餐厅要先定经营配置:主厨是谁、备哪些菜、上菜时限多久。Pydantic AI 的 Agent 对象就是这个配置单,你在初始化时一次性声明好整个助理的行为边界。
最常见的四个配置项是:模型 model——决定背后用哪个大模型,是 GPT 还是开源模型;输出类型 result_type——规定每次回复必须长成什么数据结构,对应“标准点菜单”的版式;依赖类型 deps_type——声明工具运行时需要的后备数据;重试次数 retries——校验失败时最多重新生成几次。
回头想想深夜小馆:主厨定好了、菜单印好了、备料规则明确了,剩下的就是按流程走。Agent 配置与经营配置本质上是同一种智慧——先定义边界,再释放自由度。
- 模型 M指定底层大模型,决定 Agent 的理解能力和生成风格。
- 工具集 T登记在册的工具函数集合,Agent 在需要时可以按需调用。
- 输出类型 O声明每次回复的结构化返回类型,如订单对象。
- 重试策略 R输出验证不过时,允许自动重新生成的最大次数。
工具注册机制
后厨的每道菜都有固定编号和做法卡,厨师不会临时发明。Pydantic AI 的工具注册机制同样如此:你定义的普通 Python 函数,通过 @agent.tool 装饰器注册后,函数的类型注解会被自动解析成一份参数 Schema——包括每个参数的类型、默认值、取值范围和描述。
LLM 在对话中如果需要查库存、下订单或调用外部 API,必须先按这份 Schema “点单”:参数名要对、类型要匹配、范围要合法。框架在真正执行函数之前,会用 Pydantic 校验 LLM 传进来的参数,不合法就拒之门外。这等于给工具的每一扇门都装了安检闸机。
想想深夜小馆的菜单:客人点“麻婆豆腐,不要辣”——后厨不会端出一盘宫保鸡丁,因为编号对不上、做法也对不上。工具注册机制就是这层“编号对账”,让大模型的每一通操作都有据可查。
- 工具名 n_iLLM 在生成调用指令时使用的函数名称,必须与注册名一致。
- 参数 Schema s_i由函数类型注解自动推导的输入约束,描述每个参数的类型与范围。
- 执行函数 f_i校验通过后实际运行的 Python 函数体,返回结果给 Agent。
输出验证与重试
标准点菜单设计得再好,也架不住老主顾偶尔把“辣度5”写上去(菜单规定只能选0到3)。这时候服务员最合理的做法不是硬着头皮下单,而是退回重填。Pydantic AI 的 Result 对象封装了这种语义:每次 Agent 运行,你会得到一个包含结构化输出的结果对象;如果一次生成的输出没通过校验,框架不会把半成品交给你,而是自动让模型再生成一次。
重试不是无限循环。Agent(retries=3) 声明了最多重试 3 次,超过上限就返回最后一次错误详情。这像后厨规定退单最多三次,再退就请店长出面。有限次重试既保证了可靠性,又控制了成本——每次重试都是额外的 Token 消耗。
深夜小馆的“退单重填”流程看似多花了几分钟,却换来了客人满意的确定性。类型安全的重试机制同理:多花一点 Token,换来的是一个工业级的结果——这正是企业系统最看重的东西。
- 单次失败概率 p一次调用中,LLM 输出不符合类型校验的概率,Prompt 越模糊 p 越高。
- 重试上限 k框架允许重新生成的次数,由
retries参数设定。 - 最终失败率 p^k连续 k 次尝试全部失败的概率。p=0.2、k=3 时,最终失败率仅 0.008。
对话状态管理
熟客再次光临时,如果你还记得“他上次嫌太辣”,体验会完全不同。Pydantic AI 的对话历史机制就是这份“熟客档案”:多轮对话中,Agent 可以把每一轮的用户消息和助手回复按顺序存起来,下一次运行时作为上下文一并交给模型。
这让 Agent 能理解指代关系——“再加一份,微辣”里的“再”指的是上一轮点的宫保鸡丁,而不是独立的新请求。对开发者而言,Pydantic AI 让你能显式控制历史消息的传递:哪些轮次保留、哪些轮次裁剪,而不是把整个聊天记录一股脑塞给模型,避免 Token 爆炸。
“熟客档案”不只是讨好顾客,更是让服务连续的基石。对话状态管理让 Agent 不再是“金鱼记忆”,也让“再要一份”这类人类式表达成为可能,同时你始终掌控着记忆的长度与成本。
- 用户消息 u_i第 i 轮用户输入,可能是明确的指令,也可能是对上一轮的追问。
- 助手回应 a_i第 i 轮 Agent 生成的回复,包含结构化输出或工具调用结果。
- 历史序列 H_t按时间排列的消息对集合,构成模型理解当前需求的上下文。
依赖注入模式
后厨不能只在开业时备一次料——每天的食材库存、当班厨师、特殊要求都得动态更新。依赖注入解决的就是这个“运行时备料”问题。Pydantic AI 允许你定义一个 deps_type,然后在每次运行 Agent 时传入一个实际对象,工具函数通过 RunContext 参数随时取用。
这和系统提示词有本质区别:提示词是静态文字,写死就难改;依赖是运行时数据,每次调用可以不同。比如一个订单解析 Agent,你可以传入当前菜单、用户会员等级、库存快照。工具函数不仅能调 API,还能查这份“实时备料单”,并且验证代码和业务代码解耦,测试时传入假数据即可。
依赖注入像店里的“今日备料板”——写菜谱时不关心今天有什么菜,真到做菜时再看板子。这让 Agent 在保持结构不变的前提下,灵活应对每一天的真实世界。
- 显式入参 xLLM 根据 Schema 生成并传进来的参数,如菜品名。
- 运行时依赖 d开发者注入的数据对象,如菜单、库存、用户信息。
- 执行结果 y函数结合参数与依赖计算得出的返回值,反馈给 Agent 继续处理。
企业级应用实践
深夜小馆的流程跑顺之后,最该关心的是“翻台率”和“差评率”。企业里部署 AI Agent,同样需要量化指标来评估这套类型安全机制是否真正生效。最直观的指标就是类型校验通过率:一段时间内,Agent 输出通过校验的比例。
这个指标直接反映了整体可靠性。从体验上看,通过率会随 Prompt 质量、任务复杂度、模型能力波动;而 Pydantic AI 的重试机制像一个稳定器,把单次的高失败率压缩到极低的最终失败率。这也是为什么它特别适合订单解析、信息抽取、表单自动填充这些“出错代价高”的场景。
回头再看那家深夜小馆:标准点菜单让流程可复制,重试机制兜住随机性,熟客档案提升体验,备料板和流程指标让经营有数。这一整套组合拳,就是 Pydantic AI 给企业级 Agent 开发带来的范式升级。
- 总调用数 N_total统计周期内 Agent 被调用的总次数,含重试。
- 通过校验数 N_valid最终返回的结果成功通过 Pydantic 校验的次数。
- 通过率 ACC两者之比,越接近 1 说明链路越稳定,可以作为上线监控的核心指标。
AI 应用场景
场景一:电商订单自动录入
阶段:结构化输出解析 + 工具调用校验
解决的问题:传统客服订单来自自然语言,人工录入慢且易错;无类型保障的 LLM 方案会随机输出不同格式的 JSON,录入系统无从解析。Pydantic AI 用 result_type 强制规定订单结构,字段校验不过就自动重试,保证录入系统的永远是合法数据。
具体案例:某电商售后机器人接收用户消息“我要退一件 L 码蓝色卫衣,订单号 20240115”,Agent 用 RefundRequest 模型解析出订单号、商品、尺码,直接写入工单系统。即使模型第一次漏了尺码,重试也会补齐。
场景二:合同关键信息抽取
阶段:输出验证与重试
解决的问题:法律合同长短不一、格式多样,人工逐份提取甲方、金额、合同期限效率极低。类型安全的 Agent 让提取结果永远符合固定 Schema,非法日期格式如“下个月”直接触发重试,最终输出可直接入库。
具体案例:金融科技公司的合同审查系统,用 Pydantic AI 从 PDF 合同中抽取 PartyA、PartyB、Amount、SignDate 等字段,抽取完成后自动导入合规数据库,准确率达到人工水平的 95% 以上。
场景三:企业知识库问答 Agent
阶段:对话状态管理 + 依赖注入
解决的问题:单纯 RAG 问答常“答非所问”,因为上下文和外部数据无法按需注入。Pydantic AI 把用户身份、权限等级作为依赖注入,把工具调用的返回结果校验纳入流程,让问答系统不仅懂知识,更懂约束。
具体案例:某制造企业的内部运维助手,员工问“产线 A 的稼动率”,Agent 调用数据查询工具,先用 QueryParam 校验参数,再返回量化指标;不同权限用户依赖不同,数据边界清晰。
Python 演示
# 用 Pydantic AI 搭建一个带类型安全的餐厅点餐助手from typing import Listfrom pydantic import BaseModel, Field, ValidationErrorfrom pydantic_ai import Agent, RunContextfrom pydantic_ai.models.openai import OpenAIModel class OrderItem(BaseModel): """一道菜的结构化约束""" dish_name: str = Field(description="菜品名称,必须存在于今日菜单") spicy_level: int = Field(ge=0, le=3, description="辣度 0-3,不能超出") quantity: int = Field(ge=1, le=20, description="份数,1 到 20") class ParsedOrder(BaseModel): """整笔订单的结构化输出""" customer_name: str = Field(description="客户称呼") items: List[OrderItem] = Field(min_length=1, description="菜品列表") note: str = Field(default="", description="备注,可为空") class OrderContext(BaseModel): """依赖注入:今日菜单与库存快照""" menu: List[str] stock: dict def check_stock(ctx: RunContext[OrderContext], dish: str) -> str: """工具函数:根据依赖数据检查菜品是否有货""" if dish in ctx.deps.stock and ctx.deps.stock[dish] > 0: return f"{dish} 有货,剩余 {ctx.deps.stock[dish]} 份" return f"{dish} 缺货或不在菜单,请推荐替代" agent = Agent( model=OpenAIModel("gpt-4o-mini"), deps_type=OrderContext, result_type=ParsedOrder, system_prompt="你是餐厅点单助手,只能从今日菜单中选菜,输出严格符合订单结构。", retries=3,)agent.tool(check_stock) def validate_only_demo() -> None: """离线演示:不调用 LLM,只看 Pydantic 类型校验如何拦截非法数据""" ok = ParsedOrder( customer_name="小明", items=[OrderItem(dish_name="宫保鸡丁", spicy_level=1, quantity=2)], ) print("合法订单通过:", ok.model_dump_json()) try: ParsedOrder( # 辣度 5 超出约束,应被拦截 customer_name="小明", items=[OrderItem(dish_name="宫保鸡丁", spicy_level=5, quantity=2)], ) except ValidationError as exc: print("非法订单被拦截:", exc.errors()[0]["loc"], exc.errors()[0]["ctx"]) async def main() -> None: validate_only_demo() live_ctx = OrderContext( menu=["宫保鸡丁", "麻婆豆腐", "清炒时蔬"], stock={"宫保鸡丁": 5, "麻婆豆腐": 0, "清炒时蔬": 8}, ) result = await agent.run("我姓张,要一份宫保鸡丁,微辣,两碗,再来个清炒时蔬", deps=live_ctx) print(result.output.model_dump_json(indent=2)) if __name__ == "__main__": import asyncio asyncio.run(main())
class ParsedOrder(BaseModel)声明订单结构,min_length=1强制至少一道菜。spicy_level: int = Field(ge=0, le=3)字段级范围约束,Pydantic 运行时强制执行。agent = Agent(...)声明模型、依赖类型、结果类型和重试上限。agent.tool(check_stock)把普通函数注册为工具,类型注解自动转参数 Schema。ctx: RunContext[OrderContext]工具函数读取框架注入的依赖数据。await agent.run(..., deps=live_ctx)运行时传入真实依赖,Agent 全程按 Schema 执行。validate_only_demo()用ValidationError展示非法数据如何在到达 LLM 之前就被拦截。
常见误区
误区我本来就会在代码里写函数签名,Pydantic AI 只不过是把类型标注存下来,真正执行时模型照样自由发挥,注解可有可无。
正解Pydantic 的核心原则是“类型注解即运行时校验”。你写 spicy_level: int = Field(ge=0, le=3),框架在返回数据时真的会检查整数类型和范围,不满足直接抛错并触发重试。它比静态类型检查更严格,因为它在每个请求的运行时强制执行,而不是在 IDE 里画红线。
为何常见:多数人接触的类型系统都停留在“编译期检查”印象里,没想到类型还能被用来在运行时校验来自 LLM 的不确定性数据。
误区第一次输出辣度 5 失败,重试三次是不是再把辣度 5 原样返回?那不是白费 Token 吗?
正解每次重试都会让模型重新生成答案。框架会带上校验失败的错误信息,相当于服务员把菜单退回时说“辣度只能填0到3”,模型看到反馈后会修正自己。重试是新一轮 LLM 调用,不是原样拷贝。
为何常见:把重试理解成了数据库的重放机制,忽略了 LLM 调用是带反馈的迭代过程。
误区`result_type` 只能填一个 `BaseModel` 子类,想返回不同结构需要额外写分支逻辑。
正解result_type 支持联合类型和容器类型,比如 Union[OrderItem, RefundRequest] 或 List[OrderItem]。Pydantic 会尝试用每个子模型解析并选择匹配的那个,灵活性和安全性可以兼得。
为何常见:初次接触容易把“结构化”误解为“单一样式”,忽略了类型系统本身具备组合和选择能力。
误区反正数据都会传给模型,我把库存信息写进 `system_prompt` 不也一样能查到吗,为什么要费劲搞 `deps_type`?
正解系统提示词是静态字符串,编译时写死;依赖注入是运行时对象,每次调用可以不同。库存是每天早上变化的,提示词没法一天改一次。依赖还让你能传入任意 Python 对象——工具函数直接操作对象属性,校验、测试替身、权限控制全都建立在对象之上。
为何常见:没遇到“同结构不同数据”的真实需求时,很容易低估运行时数据与静态文本的差异。
面试题
Q1. Pydantic AI 的底层核心技术是什么?
- A.正则表达式匹配
- B.Pydantic 类型校验← 正确答案
- C.模板字符串替换
- D.静态代码分析
答案:B。Pydantic AI 的根基是 Pydantic 的类型系统,它把 Python 类型注解转化为运行时校验规则,用来约束 LLM 的输出和工具调用。正则和模板做不了字段级校验,静态分析不参与运行时。
Q2. 在 Pydantic AI 中,声明 Agent 返回结果结构的参数是哪个?
- A.result_type 参数← 正确答案
- B.prompt 参数
- C.deps_type 参数
- D.retries 参数
答案:A。result_type 用来声明输出结构,比如一个继承 BaseModel 的订单类;deps_type 声明依赖类型,retries 声明重试次数,prompt 是系统提示词。前者直接决定 Agent 返回什么对象。
Q3. Pydantic AI 中,工具函数的入参校验规则主要由什么决定?
- A.JSON 外部配置文件
- B.函数类型注解与 Pydantic 字段← 正确答案
- C.运行时反射机制
- D.正则表达式匹配
答案:B。工具函数的参数类型、默认值和 Field 约束会被 Pydantic 自动解析为 Schema,并在真正执行函数前校验 LLM 传来的参数。外部 JSON 不是必要配置,反射只是内部实现细节而非规则来源。
Q4. Pydantic AI 为 Agent 提供了哪些类型安全保障?
- A.输出结构强制校验← 正确答案
- B.工具入参 schema 校验← 正确答案
- C.失败后自动重试← 正确答案
- D.模型权重文件加密
答案:ABC。输出结构校验保证返回对象合规,工具入参校验保证函数调用不越界,自动重试兜住随机失败,这三者构成“类型安全”闭环。模型权重加密属于部署安全,与类型保障无关。
Q5. 某 Agent 单次输出失败率 p=0.2,重试上限 k=3,最终失败率是多少?
- A.0.2
- B.0.6
- C.0.008← 正确答案
- D.0.992
答案:C。根据公式 $P_{fail}^{(k)} = p^k$,计算得 0.2³ = 0.008,即最终拿不到合法结果的概率仅有 0.8%。0.6 是错误相加,0.992 是成功率而不是失败率。重试机制让整体可靠性显著提升。
Q6. 对话历史在 Pydantic AI Agent 中的主要作用是什么?
- A.让多轮对话保持上下文连贯← 正确答案
- B.加速模型推理速度
- C.减少模型训练数据
- D.自动加密用户隐私
答案:A。历史消息按轮次传给模型,帮 Agent 理解“再加一份”这类指代。它和推理加速、训练数据、隐私加密都没有关系,本质是上下文管理。
Q7. 关于 Pydantic AI 的依赖注入,下列说法正确的是?
- A.工具函数可通过 RunContext 获取运行时数据← 正确答案
- B.测试时可以用替身对象隔离外部依赖← 正确答案
- C.依赖注入和系统提示词作用完全相同
- D.依赖对象可以是自定义数据类实例← 正确答案
答案:ABD。RunContext 携带依赖对象,工具函数直接取用;依赖在运行时传入,测试时可以替换为 mock 实现隔离效果;依赖类型支持 BaseModel 子类与 dataclass。系统提示词是静态文本,与运行时数据有本质区别。
Q8. 工具函数注册到 Agent 后,LLM 调用它之前发生了什么?
- A.直接执行无需检查
- B.按 Schema 校验参数合法性← 正确答案
- C.自动关闭类型检查
- D.强制重新编译函数源码
答案:B。LLM 生成的调用参数会先经过 Pydantic 按工具函数 Schema 校验,类型不对、范围越界都会被拦截,校验通过后函数体才真正执行。类型安全正是在这一步落地。
Q9. 以下哪些场景适合用 Pydantic AI 来重构?
- A.客服对话中提取订单信息并自动录单← 正确答案
- B.合同文档关键字段抽取导入数据库← 正确答案
- C.视频流实时帧压缩加速
- D.将自然语言查询转成报表参数← 正确答案
答案:ABD。订单提取、合同字段抽取、自然语言转报表参数都依赖结构化输出和类型校验,是典型受益场景。视频帧压缩是纯计算任务,与 LLM 结构化输出无关。
Q10. 一个订单 Agent 在生产依赖和测试依赖间切换,主要依赖哪项机制?
- A.模型路由切换
- B.依赖注入的可替换性← 正确答案
- C.提示词注入防护
- D.输出缓存策略
答案:B。依赖注入让 Agent 每次运行时传入不同的依赖对象,生产环境传真实库存、测试环境传 mock 库存,工具函数代码不用改动。这就是依赖注入带来的可测试性隔离,与模型路由、缓存和提示词安全无直接关系。