深入学LangChain 官方文档(四):Structured Output 结构化输出

发布时间:2026/7/19 3:53:17
深入学LangChain 官方文档(四):Structured Output 结构化输出 深入学LangChain官方文档四Structured Output 结构化输出本篇对应的官方文档LangChain Structured Output支撑 Agent 场景中的response_format、两种执行策略、schema 校验、structured_response和错误处理主线。Models - Structured Output用于划清模型级with_structured_output()与本篇 Agent 结构化输出的调用边界。Alibaba Cloud Model Studio - Function Calling支撑qwen3.7-plus使用ToolStrategy所依赖的 Function Calling 能力。本篇讲解范围本篇集中讲清 Agent 如何接收 schema、选择或显式指定结构化输出策略、校验模型结果并把业务对象写入最终 state 的structured_response。完整 Agent loop 留给第 05 篇通用工具注册与执行留给第 06 篇模型级with_structured_output()只说明边界不并列展开第二套教程。前面的Model篇和Message 篇分别解决了两个基础问题Model 是推理引擎Message 是模型上下文的基本单位。模型现在能收到角色明确的输入也能返回包含程序字段的消息但业务系统仍然缺少一道关键保证模型给出的内容能不能直接交给下一段代码。而本篇讨论的 Structured Output就是这道从“模型回答”到“业务对象”的交接。它不是单纯让输出看起来像 JSON而是先定义 schema再让 Agent 按确定的策略生成、捕获和校验结果最后把通过校验的数据放进structured_response。客服工单很适合暴露这层差异。用户可能说“订单 A1024 的耳机右侧没有声音我明天出差希望尽快换货。”客服页面可以直接展示这句话路由程序却需要订单号、问题分类、紧急程度和下一步动作。如果字段名偶尔变化、优先级有时写“紧急”有时写“high”后续判断就会变成一串脆弱的字符串分支。字段契约路径保留了自然语言输入但交给业务路由的结果必须经过字段和类型校验。自由文本路径的问题不在文字写得差而在它没有给下游一个稳定接口。因此本篇会沿着同一个客服输入走完一条完整路径先看 JSON 文本为什么仍不可靠再用 Pydantic schema 写出业务契约随后解释response_format、ProviderStrategy、ToolStrategy和structured_response的职责最后把校验失败与重试放回这条执行链。读完后我们回学到结果不只是保留自然语言给人看还要进入工单路由、数据库写入或接口调用。具体来说就是先把字段契约说清楚再选择结构化输出策略。1. JSON 文本为什么还不是业务对象结构化输出的第一次尝试是在 prompt 末尾加一句“请以 JSON 返回”。模型通常会照做甚至会给出看起来很规整的代码块{order_id:A1024,category:hardware,priority:紧急,next_action:尽快处理}这段内容能被 JSON 解析器读取却不一定能被业务程序稳定使用。假设路由规则只接受low、medium、high三种优先级那么“紧急”虽然语义正确却已经越过接口边界。next_action也过于宽泛系统无法据此判断应该创建换货单、退款单还是人工复核任务。有效 JSON 只证明括号、引号和逗号符合语法。业务对象还要回答四类问题字段是否完整例如没有订单号时能否使用null还是必须拒绝本次抽取类型是否稳定例如优先级必须是字符串商品数量必须是整数值域是否受控例如问题分类只能来自预先定义的枚举字段含义是否清楚例如next_action表示给用户的回复还是后台要执行的动作。如果这些约束只写在自然语言 prompt 中模型每次都要重新解释描述应用也只能在返回后自行补校验。随着字段增加prompt、解析器和业务判断会分别维护一份规则三者很容易逐渐不一致。业务程序依赖的不是一对大括号而是一份稳定契约。Structured Output 的核心价值是把字段结构从“希望模型遵守的描述”提升为 Agent 可以执行、校验并返回的 schema。2. schema 把字段、类型和值域写在同一个地方LangChain 的 Agent 结构化输出支持 Pydantic model、dataclass、TypedDict 和 JSON Schema。它们都能描述输出形状但运行时能力并不完全相同。Pydantic 可以把字段说明、类型、默认值和校验规则集中在一个 Python 类型里并在成功时返回经过验证的模型实例适合本篇的工单场景。fromtypingimportLiteralfrompydanticimportBaseModel,FieldclassSupportTicket(BaseModel):从用户投诉中抽取的客服工单。order_id:str|NoneField(defaultNone,description订单号原文没有提供时返回 null,)category:Literal[product,shipping,billing,service]Field(description问题分类,)priority:Literal[low,medium,high]Field(description处理优先级,)next_action:strField(description客服系统下一步应执行的动作,)这里真正重要的不是BaseModel这个类名而是业务规则有了唯一落点。order_id明确允许缺失分类和优先级被限制在有限集合中next_action也说明了它属于后台动作而不是面向用户的安抚话术。字段描述仍然会提供给模型用来解释每个字段应表达什么Python 类型和值域则让校验器判断结果能否进入下游。自然语言说明和程序约束因此不再散落在不同文件中。选择其他 schema 形式时要知道自己放弃或获得了什么。dataclass 和 TypedDict 更轻量JSON Schema 便于跨语言共享如果需要 Python 侧的字段验证、嵌套对象和清晰的错误信息Pydantic 通常更直接。本篇不把四种写法逐一改写成相同示例因为真正需要掌握的是 schema 的职责而不是记住四套语法。四种载体真正拉开差距的不是定义时 少写几行代码而是运行时校验、Python 类型语义和跨语言交换由谁承担。把这三条能力轴放在一起schema 的选择就从语法偏好变成了工程边界。Pydantic 同时保留 Python 类型语义并提供直接的运行时验证适合在应用内部尽早拦住不合格对象JSON Schema 更强调语言无关的契约交换通常要结合具体执行方或校验器落地。dataclass 与 TypedDict 位于两者之间更适合表达 Python 内部结构但不能因为类型写得清楚就默认所有运行时数据已经完成同等强度的验证。schema 也不能替业务做全部判断。它可以约束priority只能取三个值却不能凭空知道某家公司的 SLA 如何定义“high”。值域与字段说明仍然必须来自真实业务规则Structured Output 负责执行这些规则不负责发明规则。3.response_format是契约进入 Agent 的入口定义好SupportTicket后下一步不是把它拼进 prompt而是通过create_agent的response_format交给 Agent。官方接口允许这里接收四类值ToolStrategy、ProviderStrategy、一个 schema type或者None。这四种写法表达的是控制选择None表示没有显式请求结构化输出直接传SupportTicket由 LangChain 根据模型能力选择策略ProviderStrategy(SupportTicket)明确要求走供应商原生结构化输出路径ToolStrategy(SupportTicket)明确借助 tool calling 生成并校验结构化结果。直接传 schema 和显式传入策略对象的区别最终落在“由框架判断路径”还是“由工程配置固定路径”。如果只把它们理解为三种等价写法部署环境变化时就很难解释实际走了哪条链路。直接传 schema 会把策略选择交给 LangChain 的能力识别换来更短的配置显式构造ProviderStrategy或ToolStrategy则把执行路径写进应用配置。三条路径使用的仍是同一份 schema 契约差别只在约束由谁执行、能力判断由谁承担。直接传 schema 的写法最短但“自动”不代表不需要理解边界。LangChain 在当前版本中会读取模型 profile模型与供应商支持原生结构化输出时选择ProviderStrategy其他情况选择ToolStrategy。如果 profile 信息不可用或者应用希望不同环境始终走相同路径就应显式指定策略。这也是本篇代码显式使用ToolStrategy的原因。Alibaba Cloud 官方能力表确认qwen3.7-plus支持 Function Calling因此工具调用策略有明确依据至于某个 OpenAI-compatible 连接能否被 LangChain profile 自动识别为供应商原生结构化输出不在正文中擅自假设。response_format保存的是“怎样取得结构化结果”的配置不是结构化结果本身。它在 Agent 创建时进入执行图真正的数据要等一次运行完成后从最终 state 中读取。response_format位于执行链的输入侧负责把业务 schema 交给 Agentstructured_response位于输出侧只在模型结果经过策略处理和 schema 校验后出现。把这两个字段分开后面的策略差异才不会和结果读取混在一起。4.ProviderStrategy与ToolStrategy约束输出的位置不同两种策略服务同一个 schema但它们把约束放在不同位置。ProviderStrategy使用模型供应商 API 提供的原生结构化输出能力。schema 会进入供应商支持的结构化参数由供应商在生成层执行约束。官方文档把它视为可用时更可靠的方式因为数据形状由模型服务直接保证而不是在普通文本生成后再猜测。这条路径仍然有边界。具体模型是否支持原生结构化输出、是否支持strict、能否同时使用普通工具和结构化输出都取决于供应商与模型能力。代码不能因为某个供应商的另一款模型支持就把当前模型也写成必然支持。ToolStrategy则把 schema 表达成一次结构化输出工具调用。模型按照 tool schema 生成参数LangChain 捕获参数并转换为目标对象。它适用于支持 tool calling、但没有原生结构化输出接口或者应用希望显式固定工具调用路径的模型。这里容易出现一个误解结构化输出工具不是普通业务工具。天气工具会真的查询天气库存工具会访问数据库ToolStrategy里的“工具”主要承担输出协议让模型把结果放进一组受约束的参数。完整的工具注册、执行和结果回传会在第 06 篇展开。两种策略最终都要回答同一个问题生成结果是否符合 schema。差别在于约束由供应商原生接口执行还是由 tool calling 与 LangChain 校验链完成。工程选择应该依据实际模型能力、错误处理需要和跨环境一致性而不是根据类名长短决定。两条路径不会在一次运行中并行执行。ProviderStrategy把约束交给供应商原生能力ToolStrategy把 schema 转成 Function Calling 路径并由 LangChain 校验选中的路径不同最终都要把合格对象写进structured_response。如果代码同时给 Agent 配置普通工具官方文档还要求模型支持工具与结构化输出同时使用。结构化抽取单独能运行不代表加入搜索、数据库等工具后仍能沿用完全相同的供应商能力组合这类组合必须按具体模型验证。5.structured_response才是业务代码的读取位置Agent 的输入仍然是熟悉的messagesstate。客服投诉作为 user message 进入运行模型和策略完成结构化输出后invoke返回最终 state。这个 state 不只包含最后一句回答还包含过程消息以及结构化结果。官方文档把通过捕获与校验的数据放在ticketresult[structured_response]如果 schema 使用 Pydanticticket是对应的 Pydantic 实例业务代码可以访问ticket.order_id、ticket.category和ticket.priority也可以通过model_dump()转成字典。它不需要从 Markdown 代码块中截取 JSON也不需要在多个候选字段名之间做兼容。result[messages]仍然有价值。它保存用户输入、模型调用以及 ToolStrategy 可能产生的工具消息适合排查模型为什么给出某个结果。structured_response则面向下游业务适合路由、持久化或接口调用。两者一个保存过程一个提供结果不能因为都在最终 state 中就混成同一用途。Agent state 同时保留两类信息messages支撑调试、追踪和继续对话structured_response支撑工单路由等业务动作。下游代码先判断自己需要过程还是结果再选择读取字段而不是默认取消息列表的最后一项。这个区分还会改变错误排查顺序。页面上没有自然语言回答不一定表示调用失败如果本次任务的目标是抽取工单首先应该检查最终 state 是否存在structured_response。反过来字段结果不对时也不应只打印最后一条消息而要检查 schema、策略和校验反馈。结构化输出的完成标志不是“模型说完了”而是业务对象通过校验并进入最终 state。这让 Agent 的输出可以成为程序接口而不仅是聊天内容。这里的“通过校验”仍然只是结构层结论。order_idA1024符合字符串类型不代表数据库里一定存在这个订单next_actioncreate_replacement形状正确也不代表当前用户拥有换货权限。结构化结果进入业务代码后仍要继续执行订单查询、身份校验、权限判断和幂等控制。schema 负责拦住字段层错误业务服务负责判断事实与动作是否合法两层不能互相替代。第一道闸门只回答“数据形状能否被程序接收”字段是否完整、类型是否正确、枚举是否落在允许值域。第二道闸门才回答“这个动作能否在当前业务中执行”订单是否存在、调用者是否有权限、状态是否允许继续流转任何一层失败都应停在自己的职责边界处理而不是把问题重新丢给模型猜测。接下来聚焦第一道闸门内部即使都属于结构化输出失败字段没有通过 schema 与一次返回多个候选结果触发的修复路径也不相同。6. 校验失败不是一种错误而是两条不同路径模型仍可能犯错。Structured Output 提供的不是“模型永远不会输出错”而是让错误在进入业务代码之前暴露并为修复建立明确路径。第一类是 schema validation error。假设工单 schema 只允许low、medium、high模型却生成urgentPydantic 校验会拒绝这个值。使用ToolStrategy且保持默认handle_errorsTrue时LangChain 会把具体错误反馈给模型请它重新生成符合约束的参数。第二类是 multiple structured outputs error。某些场景会用 Union schema 允许模型在多个结构类型中选择一个如果模型一次返回多个结构化输出而当前运行只期望一个结果问题不在字段值而在返回数量。LangChain 同样可以把错误送回模型要求它只保留最合适的结构。把两者都叫“JSON 解析失败”会丢掉真正的定位信息JSON 解析关注语法是否有效schema validation 关注字段、类型和值域是否符合契约multiple structured outputs 关注一次运行返回了多少个互斥结果。ToolStrategy.handle_errors决定错误怎样离开校验点。默认True会捕获错误、生成反馈并重试传入字符串可以给所有错误使用固定反馈传入异常类型或处理函数可以缩小捕获范围设为False则不重试让异常直接交给调用方。handle_errors控制的是校验失败后的流向启用处理时错误反馈会进入消息链并推动模型重新生成关闭处理时失败不会绕过 schema 进入结果字段而是作为异常交给调用方。重试由配置建立不是模型自行决定“再试一次”。重试也不是越多越好。如果 schema 本身互相矛盾或者字段描述缺少必要业务信息模型反复生成只会增加延迟与成本。看到同一字段连续失败时应该回头检查 schema 和输入是否足够而不是把所有问题都归因于模型“不听话”。生产系统还需要决定失败后的业务动作返回人工审核、保存原始文本等待补录还是终止整个请求。Structured Output 能把错误定位到契约层但无法替应用决定风险策略。7. 用qwen3.7-plus跑通客服工单抽取现在把前面的对象接成一次完整调用。代码重点只有四处Pydantic schema 定义业务契约ToolStrategy固定执行路径response_format把策略交给 Agent业务结果从structured_response读取。importosfromtypingimportLiteralfromlangchain.agentsimportcreate_agentfromlangchain.agents.structured_outputimportToolStrategyfromlangchain_openaiimportChatOpenAIfrompydanticimportBaseModel,FieldclassSupportTicket(BaseModel):从用户投诉中抽取的客服工单。order_id:str|NoneField(defaultNone,description订单号原文没有提供时返回 null,)category:Literal[product,shipping,billing,service]Field(description问题分类,)priority:Literal[low,medium,high]Field(description处理优先级,)next_action:strField(description客服系统下一步应执行的动作,)modelChatOpenAI(modelqwen3.7-plus,api_keyos.environ[DASHSCOPE_API_KEY],base_urlos.environ[DASHSCOPE_BASE_URL],)agentcreate_agent(modelmodel,tools[],response_formatToolStrategy(schemaSupportTicket,handle_errors(请严格按照工单 schema 返回一个结果category 和 priority 必须使用允许的枚举值。),),)resultagent.invoke({messages:[{role:user,content:(订单 A1024 的耳机右侧没有声音。我明天要出差希望尽快换货。),}]})ticket:SupportTicketresult[structured_response]print(ticket.model_dump())输入从messages进入 Agent而不是直接传给 Pydantic。ToolStrategy把SupportTicket转成模型可使用的结构化输出约束模型生成参数后LangChain 负责捕获并交给 Pydantic 校验。只有通过校验的对象才会成为result[structured_response]。这段代码没有把tools[]理解成“完全没有工具机制”。它表示没有额外的业务工具ToolStrategy仍会使用结构化输出所需的 tool calling 路径。以后加入订单查询或库存检查工具时才需要进一步验证模型能否同时完成普通工具调用和结构化输出。handle_errors中的文字只负责告诉模型怎样修正格式不应该承载全部业务规则。允许的分类和值域已经写在 schema 中错误反馈只强调如何回到契约。这样修改字段时不必同时维护一份长错误 prompt。示例没有写死区域地址而是从DASHSCOPE_BASE_URL读取。Alibaba Cloud 不同区域使用不同 endpoint 和 API key二者必须属于同一部署范围这属于模型连接前提不是 Structured Output 机制本身。如果运行后没有得到预期对象可以按执行链从前向后排查模型调用是否成功认证、endpoint 和模型名是否属于同一区域qwen3.7-plus是否实际收到结构化输出工具 schema模型返回的是一个还是多个结构化输出调用Pydantic 具体拒绝了哪个字段、类型或值域handle_errors是反馈重试还是已经关闭并向外抛出异常业务代码是否从structured_response读取而不是继续解析最后一条消息。六个检查点对应六个不同的故障层能力不支持时改 prompt 没有用策略选错时重写 schema 也解决不了问题。只有当前一层确认通过排查才进入下一层一旦失败就在模型能力、策略、契约、错误反馈、结果读取或业务规则中定位对应对象。这个顺序比反复修改 prompt 更可靠因为每一步都对应一个明确对象连接、策略、模型调用、schema 校验、错误处理和结果读取。总结把模型回答变成业务接口Structured Output 解决的不是输出排版而是模型结果能否跨过程序边界。客服工单从一段自然语言开始schema 先定义字段、类型和值域response_format把契约交给 Agent策略决定约束在哪一层执行校验拦住不合格结果最终对象再进入structured_response。选择时可以抓住两条线只需要给人展示一段回答自然语言通常更合适后续程序依赖固定字段时再引入结构化输出。模型供应商原生支持且能力边界明确时可以使用ProviderStrategy需要依靠 Function Calling 或希望固定执行路径时使用ToolStrategy。结构化输出也没有消除模型的不确定性。它做的是把不确定性限制在可检查的位置字段不合规时产生 validation error返回多个结果时产生数量错误业务代码只接收通过契约的对象。到这里Model、Message 和 Structured Output 已经分别回答了“谁负责推理”“上下文怎样传递”“结果怎样成为程序对象”。下一篇进入 Agent 核心循环继续追踪模型、消息和工具如何在一次运行中反复交接直到满足终止条件。