这个话题偏工程细节,我先去找原文和 LangChain 官方文档里的结构化输出机制。
官方文档机制已经拿到,我再找一下这篇中文原文的具体出处和示例。
官方文档里的策略、校验重试机制和 v1 迁移变更我都核对完了。以下是成稿。
LangChain 里让 Agent 吐出结构化数据,现在只有一条主线:建 Agent 时给 create_agent 传 response_format,结果从返回 state 的 structured_response 键里取,拿到的是 Pydantic 对象、dataclass 或字典,而不是一段需要自己正则去抠的自然语言。底下有两条路:ToolStrategy 把 schema 伪装成一个工具,让模型通过 tool calling 把字段 "传" 出来,所有支持工具调用的模型都能用,是通用兜底;ProviderStrategy 直接用 OpenAI、Anthropic、xAI、Gemini 这类服务商原生的结构化输出能力,由服务端按 schema 约束生成,是目前最可靠的方式。直接传一个 Pydantic 类给 response_format,LangChain 会按模型能力自动选 —— 这也是大多数场景该用的写法。LangChain真正容易翻车的不是写法,而是 "以为模型一定听话":字段超范围、字段编造、误调多个结构化工具,这些都要靠 handle_errors 把校验错误回灌成一条 ToolMessage 让模型自己改。
一、最小可跑的样子
from pydantic import BaseModel, Field
from langchain.agents import create_agent
class ContactInfo(BaseModel):
"""联系人信息"""
name: str = Field(description="姓名")
email: str = Field(description="邮箱")
phone: str = Field(description="电话")
agent = create_agent(
model="openai:gpt-5.5",
tools=[],
response_format=ContactInfo, 直接传 schema,自动选策略
)
result = agent.invoke({
"messages": [{"role": "user",
"content": "小明的电话是 12874384973,邮箱是 12347923@qq.com"}]
})
print(result["structured_response"]) ContactInfo(name='小明', ...)
三个位置要记住:response_format 在创建时定,structured_response 在 state 里取,tools 可以为空 —— 结构化输出不需要真有工具。
二、三条策略,选错会白折腾
| 策略 | 干活的方式 | 适用条件 | 特点 |
|---|---|---|---|
| ToolStrategy | 把 schema 包成一个 "假工具",走 tool calling | 任何支持工具调用的模型 | 支持校验错误反馈与自动重试,通用兜底 |
| ProviderStrategy | 用服务商原生的结构化输出接口 | 模型与服务商支持原生 schema 约束 | 最可靠,服务端强约束 |
| 直接传 schema(AutoStrategy) | 按模型 profile 能力自动选 | 常规场景 | 省心,推荐默认用法 |
LangChain菜鸟教程自动选择的具体规则:模型支持原生结构化(OpenAI、Anthropic、xAI、Gemini 等)就走 ProviderStrategy,其余一律 ToolStrategy。这个判断依赖模型的 profile 数据,langchain>=1.1 起会动态读取;读不到时可以手动补:
custom_profile = {"structured_output": True} 其余字段按需补
model = init_chat_model("...", profile=custom_profile)
三、ToolStrategy 到底做了什么
它不是在 "提示模型输出 JSON",而是走了一次完整的工具调用,只是这次调用不会真的执行你的函数:
| 步 | 发生什么 | 谁在做 |
|---|---|---|
| 1 | 把 Pydantic schema 转成一个工具定义注册给模型 | LangChain |
| 2 | 模型生成 tool_calls,name 就是 schema 名,args 就是字段值 | 模型 |
| 3 | 拦截这次调用,不执行任何真实函数 | LangChain |
| 4 | 把 args 解析、校验后写入 structured_response | LangChain |
| 5 | 追加一条伪 ToolMessage,告诉模型 "结构化输出成功" | LangChain |
稀土掘金理解第 3 步很关键:这里的 "工具" 只负责传数据 schema,不等于你写了一个真实的课程查询函数。所以它不需要副作用,也不会因为你的工具实现有 bug 而导致结构化输出失败。
四、ProviderStrategy 与 strict 的边界
| 项 | 说明 |
|---|---|
| 支持方 | OpenAI、Anthropic(Claude)、xAI(Grok)、Gemini 等原生支持结构化输出的服务商 |
| strict 参数 | 请求服务端严格按 schema 强制,需 langchain>=1.2 |
| JSON Schema 字典 | 必须显式包进 ProviderStrategy 或 ToolStrategy,不会被自动识别 |
| 与工具共存 | 若同时传了 tools,模型必须支持 "工具调用 + 结构化输出" 并行 |
LangChainOpenAI 的 strict 模式有个硬约束,值得单独记:schema 必须落在 strict 子集里 —— 每个 object 都要 additionalProperties: false,所有属性都得进 required,可选字段写成 anyOf 加 null(比如 int | None),并且不支持自由的 dict[str, Any]。LangChain Forum很多人遇到 "参数被静默丢掉",根源就是某个类型写不进 strict 子集,被服务端悄悄裁掉了。
五、schema 用什么类型,决定你拿回什么
| schema 类型 | 返回形态 | 校验 | 备注 |
|---|---|---|---|
| Pydantic BaseModel | Pydantic 对象 | 自动校验,支持字段范围与自定义 validator | 功能最全,首选 |
| TypedDict | 字典 | 需手动校验 | 轻量,无运行时约束 |
| dataclass | 对象 | 需手动校验 | 写法简单 |
| JSON Schema 字典 | 字典 | 需手动校验 | 必须显式包进策略 |
| Union 联合类型 | 视策略而定 | — | 只有 ToolStrategy 支持 |
LangChainCSDN博客一个常被忽略的细节:字段的 description 不是注释,是给模型的指令。rating: int = Field(description="1 到 5 的评分", ge=1, le=5) 里,description 帮模型理解语义,ge/le 帮你在校验阶段拦住越界值,两件事都要做。
六、校验失败不是异常,是一次对话
这是结构化输出最 "Agent" 的地方:校验不通过时,错误会被写成一条 ToolMessage 回灌给模型,让它自己改。以评分越界为例:
| 轮次 | 模型输出 | 系统反馈 |
|---|---|---|
| 1 | {"rating": 10, "comment": "Amazing product"} | 校验失败:rating 应 ≤ 5,附 "Please fix your mistakes." |
| 2 | {"rating": 5, "comment": "Amazing product"} | 校验通过,写入 structured_response |
LangChainToolStrategy 的 handle_errors 默认就是 True,也就是默认会重试。可以按需收紧:
response_format=ToolStrategy(ProductRating, handle_errors=True) 默认
response_format=ToolStrategy(ProductRating, handle_errors=False) 出错直接抛
还有一种错误同样会被回灌:模型一次调用了多个结构化输出工具。LangChain 会在 ToolMessage 里指出这个问题并要求重试。
七、v1 相对 v0,四个变化要知道
| 变化 | v0 的做法 | v1 的做法 |
|---|---|---|
| 生成位置 | 结构化输出在独立节点里生成 | 在主循环里生成,降成本、降延迟 |
| 策略 | 默认只有工具调用策略 | ToolStrategy / ProviderStrategy 二选一 |
| 提示词输出 | 支持 response_format=("prompt", Schema) | 已移除,官方认为不够可靠 |
| 预绑定模型 | 可以传 bind_tools 过的模型 | 不再支持,会与结构化输出冲突 |
LangChain另外 state_schema 在 v1 里只接受 TypedDict,不再支持 Pydantic 模型和 dataclass;需要校验请放到中间件钩子里做。LangChain
八、七个真实会踩的坑
| 坑 | 现象 | 解法 |
|---|---|---|
| 传了预绑定模型 | 报错或结构化输出失效 | 传原始模型,工具走 tools= 参数 |
| JSON Schema 直接传 | 不生效、退回默认行为 | 显式包 ProviderStrategy / ToolStrategy |
| strict 子集不满足 | 参数被静默丢弃 | 消除自由 dict,可选字段改 anyOf + null |
| 用 Union 配 ProviderStrategy | 不支持 | 换 ToolStrategy |
| 字段没写 description | 模型乱填、语义错位 | 每个字段都给语义描述 + 约束 |
| 流式里立刻取结果 | structured_response 还是空的 | 等工具调用完成,流式下它会延迟就绪 |
| 模型拒答被当成解析错 | 报的是奇怪的校验错误 | 捕获 ModelRefusalError,Agent 需 ModelRefusalMiddleware |
LangChainCSDN博客LangChain拒答这条要补一句版本要求:with_structured_output 抛出 ModelRefusalError 需要 langchain-core>=1.7.5,Gemini 还需 langchain-google-genai>=4.4.3;低于这些版本会退化成普通 ValueError,排查时很容易误判。
九、生产环境值得加的四件事
| 加什么 | 怎么写 | 为什么 |
|---|---|---|
| 保留原始消息 | include_raw=True | 能同时拿到 raw / parsed / parsing_error,便于算 token 和排错 |
| 拆小 schema | 一个 schema 别塞十个嵌套对象 | 字段越多,模型出错的概率越高 |
| 给默认值与可空 | 不确定的字段设 None 默认 | 避免模型为了 "填满" 而编造 |
| 埋校验失败率 | 记录 parsing_error 与重试次数 | 这是判断模型是否适配该 schema 的最直接指标 |
include_raw=True 的返回结构是固定的三键字典:raw 是原始 AIMessage,parsed 是解析后的对象(失败时为 None),parsing_error 是异常对象。这套东西在 Agent 场景里同样有价值 —— 出问题时你能看到模型到底说了什么。
一句能落地的判断:结构化输出在 LangChain 里已经是 "配置项" 而不是 "工程量",选策略只占一成,剩下九成在 schema 设计 —— 字段少一点、描述清楚一点、约束写严一点,重试率自然就下来了。
【常见问题】
问题 1:LangChain Agent 结构化输出最简写法是什么?
回答 1:建 Agent 时传 response_format,结果从 state 的 structured_response 取。最省事的写法是直接把 Pydantic 类传给 response_format,LangChain 会按模型能力自动选策略:支持原生结构化就走 ProviderStrategy,否则走 ToolStrategy。调用后 result["structured_response"] 就是校验通过的对象,不需要再写正则或 JSON 解析。
问题 2:LangChain Agent 结构化输出里 ToolStrategy 和 ProviderStrategy 有什么区别?
回答 2:ToolStrategy 把 schema 伪装成一个工具,让模型通过 tool calling 把字段传出来,LangChain 拦截这次调用、不执行真实函数,把参数转成结构化结果,所有支持工具调用的模型都能用。ProviderStrategy 用服务商原生的结构化输出接口,由服务端按 schema 强约束,OpenAI、Anthropic、xAI、Gemini 这类模型支持,是最可靠的方式。前者通用,后者更稳。
问题 3:LangChain Agent 结构化输出校验失败会报错吗?
回答 3:默认不会直接抛异常,而是走一次重试。ToolStrategy 的 handle_errors 默认为 True,校验不通过时 LangChain 会把具体错误(比如 "rating 应小于等于 5")写成一条 ToolMessage 回灌给模型,让它改正后重新输出。只有把 handle_errors=False 才会直接抛出。这也意味着生产环境要监控重试次数,重试率高说明 schema 设计或模型选型有问题。
问题 4:LangChain Agent 结构化输出为什么有时拿不到结果?
回答 4:常见四种原因。一是流式场景下 structured_response 会延迟就绪,工具调用没完成时取到的是空值;二是传了 bind_tools 过的预绑定模型,v1 明确不支持;三是 JSON Schema 字典没显式包进 ProviderStrategy 或 ToolStrategy,不会被自动识别;四是 strict 模式下 schema 不符合子集要求,参数被服务端静默裁掉,看起来像 "模型没填"。
问题 5:LangChain Agent 结构化输出该用 Pydantic 还是 JSON Schema?
回答 5:优先 Pydantic。Pydantic 模型支持自动校验、字段范围约束、嵌套结构和自定义 validator,返回的是强类型对象,出错时错误信息也最具体;TypedDict、dataclass 和裸 JSON Schema 都需要你手动校验,返回的是字典或弱类型对象。唯一的例外是 schema 需要动态生成、来自外部配置时,才用 JSON Schema 字典,但记得显式包进策略。
问题 6:LangChain Agent 结构化输出在 v1 里有哪些行为变了?
回答 6:四个关键变化。结构化输出从独立节点挪进主循环生成,成本与延迟都降了;新增 ToolStrategy 和 ProviderStrategy 两种策略,取代 v0 默认只有工具调用的做法;提示词输出(prompted output)被移除,官方认为不够可靠;create_agent 不再接受预绑定模型,state_schema 也只支持 TypedDict。从 v0 迁移时这四点最容易导致 "代码能跑但结果不对"。
【参考资料】
[2026-11-14](Structured output)LangChain 官方文档(查阅日期)
[2026-11-14](LangChain v1 migration guide)LangChain 官方文档(查阅日期)
[2026-11-05](第四章 结构化输出)CSDN 博客
[2026-07-25](LangChain Agent 高级玩法:命名、结构化输出与流式模式)稀土掘金
[2026-07-24](Make a llm.with_structured_output call a tool)LangChain Forum
[2026-07-24](Models - Structured output)LangChain 官方文档(查阅日期)
[2026-07-24](LangChain 输出策略)菜鸟教程(查阅日期)
免责声明:本文依据 2026 年 11 月 14 日前可访问的 LangChain 官方文档、迁移指南、社区教程与开发者问答整理,属于技术实现层面的解读,不构成对任何框架版本行为的承诺。LangChain v1 仍在快速迭代,response_format 支持的策略、strict 参数与拒答异常所需的版本门槛(如 langchain>=1.2、langchain-core>=1.7.5、langchain-google-genai>=4.4.3)均可能随版本变化,实际请以所安装版本的官方文档与源码为准。文中代码为示意,模型名称(如 gpt-5.5)仅为示例占位,请替换为当前可用的模型标识。不同服务商对 strict 子集的限制不同,"参数被静默丢弃" 等行为的根因需结合实际请求排查。官方文档条目未标注发布日期,参考资料中的日期为本次查阅日期。生产环境使用前请自行完成错误处理、重试上限与成本评估。
