最终回复也能结构化:OpenAI、DeepSeek 与 LangChain
结构化输出既能约束工具调用参数,也能规定模型最终回复的格式。本文通过同一个联系人案例,梳理 OpenAI、DeepSeek 的接口能力,以及 LangChain 的策略选择、解析校验与实际调用路径。
以前,我知道 Function Calling 可以严格规定函数参数,却以为模型最后回复的 text,只能通过提示词要求它“尽量输出 JSON”。
后来我发现,最终回复本身也可以按照指定的结构生成。 从“请按 JSON 回答”到“按 Schema 交付结果”,接口设计和数据处理方式都会随之改变。
要理解这项能力,需要依次回答三个问题:
- OpenAI: 如何原生约束工具参数和最终回复?
- DeepSeek: 对应能力在哪些接口里,如何开启?
- LangChain: 如何把这些能力封装起来,实际选择哪条路径?
前两家提供模型 API,LangChain 提供应用框架。我们需要同时理解厂商的能力,以及框架实际调用这些能力的方式。
下面以联系人提取为例。输入“张伟,邮箱 zhang.wei@example.com”,业务需要得到:
{
"name": "张伟",
"email": "zhang.wei@example.com"
}
业务需要固定的 name 和 email 字段,因此可以先写一份 JSON Schema,也就是数据格式说明书:
contact_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
},
"required": ["name", "email"],
"additionalProperties": False,
}
这份 Schema 规定:两个字段都是字符串,两个字段都必须出现,不允许增加额外字段。后面的代码复用这份定义,展示各接口的核心配置。
一、OpenAI:工具参数和最终回复,都可以受 Schema 约束
先区分两个容易混淆的目标:输出合法 JSON,以及输出符合业务结构的 JSON。
例如 {"contact": "张伟,zhang.wei@example.com"} 是合法 JSON,却没有业务需要的 name 和 email 字段。
OpenAI 的 JSON mode 约束 JSON 语法,严格 Structured Outputs 进一步约束指定的 Schema。两者都是 API 功能;只在提示词里写“请输出 JSON”,则依赖模型遵循指令。OpenAI 结构化输出文档
严格结构化输出有两个入口:
Function Calling + strict: true
Schema 约束哪里:模型填写的函数参数
联系人案例:填写联系人信息,交给程序执行保存
最终回复的 json_schema 格式 + strict: true
Schema 约束哪里:模型回复的正文
联系人案例:直接返回提取好的联系人数据

图 1:先确定数据是交给函数执行,还是作为最终结果交给业务。
Function Calling:约束交给程序的任务单
如果用户说“把这个联系人保存到通讯录”,应用可以提供 save_contact 函数,并把 contact_schema 用作它的参数定义。模型填写姓名和邮箱,应用再执行真正的保存操作。
在 Responses API 中,工具配置的关键部分是:
tools = [{
"type": "function",
"name": "save_contact",
"description": "将联系人保存到通讯录",
"parameters": contact_schema,
"strict": True,
}]
这时结构化数据在工具调用的 arguments 中。严格模式约束参数,实际保存由应用负责。如果需要模型根据保存结果继续回答,就把执行结果传回去。
OpenAI 明确说明,严格 Function Calling 底层使用的也是 Structured Outputs 能力。strict 管参数结构,是否必须调用工具则由 tool_choice 等配置控制。OpenAI Function Calling 文档
最终回复:直接交付结构化结果
如果需求只是提取联系人,可以直接把 Schema 放进最终回复的格式配置。以下代码沿用前面的 contact_schema,展示核心调用:
from openai import OpenAI
client = OpenAI() # 从环境变量读取 OPENAI_API_KEY
response = client.responses.create(
model="gpt-6-astra",
instructions="提取联系人信息。缺失字段用空字符串,不得编造。",
input="张伟,邮箱 zhang.wei@example.com",
text={
"format": {
"type": "json_schema",
"name": "contact_info",
"schema": contact_schema,
"strict": True,
}
},
)
这里约束的是模型最终生成的正文。在响应完整且没有拒答的正常路径下,SDK 的 response.output_text 中就是联系人 JSON 字符串。
output_text 中的“text”表示文本载体,里面可以装 JSON。应用使用 json.loads() 将它解析成字典即可。模型在生成回复时已经按结构输出,应用不需要再调用一次模型来重新整理格式。
OpenAI 两种 API 的参数位置略有差别:Responses API 使用 text.format,Chat Completions API 使用 response_format。Python SDK 还提供 responses.parse(text_format=某个Pydantic类),帮助转换 Schema、解析结果。OpenAI 接口与 SDK 示例
两种用途也能组合:在支持同时使用工具和结构化回复的模型与接口上,Agent 可以先调用工具,再按最终回复的 Schema 交付结果。工具阶段约束“交给函数什么参数”,收尾阶段约束“交给业务什么数据”。
为什么能约束格式:生成过程中限制候选内容
OpenAI 在 Structured Outputs 的技术说明中介绍了约束解码(constrained decoding):将 JSON Schema 转成语法规则,在生成每一个 token 时,根据已生成的内容筛选下一步合法的候选,屏蔽不符合规则的候选。这里的 token 可以理解为模型每一步生成的文本片段。OpenAI 技术原理说明
这解释了严格模式为何能在生成阶段约束格式。应用拿到 JSON 字符串后的解析,负责把文本变成程序对象;两件事发生在不同阶段。
OpenAI 的严格模式支持 JSON Schema 的一个子集。本文中的 required 和 additionalProperties: False 是重要约定;更复杂的嵌套、可空字段和数值约束,需要按所用模型的支持范围定义。
二、DeepSeek:把三个接口入口分开看
DeepSeek 的结构化能力分布在不同接口中。理解它,需要同时阅读 JSON Output、Tool Calls 和 Responses API 文档。
截至本文核对文档时,需要区分三个入口:
Chat Completions 的 JSON Output
配置方式:response_format: {"type": "json_object"}
约束对象:最终正文的 JSON 语法
Beta 严格工具调用
配置方式:Beta 地址 + function.strict: true
约束对象:工具调用参数的 Schema
Responses API 的结构化输出
配置方式:text.format.type: "json_schema",传入 name 和 schema
约束对象:最终正文的 Schema

图 2:三个入口并列存在,约束的输出位置与开启方式不同。
JSON Output:约束最终正文的 JSON 语法
官方教程使用 json_object,同时要求提示词包含 json 字样,并给出预期格式示例。
对于联系人提取,你可以在提示词中要求 name 和 email,再开启 JSON Output。但这个入口没有接收业务 Schema,不能把它当成严格的字段契约。应用仍需要校验字段和类型。
官方还提醒,输出可能被长度限制截断,也可能出现空的 content,因此解析前要检查响应。DeepSeek JSON Output 文档
Beta 严格工具调用:约束函数参数
DeepSeek 的 Tool Calls 文档明确提供 strict 模式。开启方式包括使用 https://api.deepseek.com/beta,并将所有函数工具的 strict 设为 true。服务端会检查 Schema 是否符合支持范围。DeepSeek 严格工具调用文档
沿用“保存联系人”的例子,关键配置如下。这里使用 Chat Completions 的工具结构,函数定义在 function 这一层里面:
import os
from openai import OpenAI
beta_client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com/beta",
)
beta_tools = [{
"type": "function",
"function": {
"name": "save_contact",
"description": "将联系人保存到通讯录",
"parameters": contact_schema,
"strict": True,
},
}]
把这份 beta_tools 作为 tools 传给 beta_client.chat.completions.create(),模型生成的调用参数就受严格模式约束。它仍然是一张交给程序执行的任务单。
Responses API:给最终回复传入 Schema
DeepSeek 当前的 Responses API 参考文档已经列出了 text.format 的三种类型:text、json_object 和 json_schema。其中 json_schema 要求传入 name 与 schema,文档声明输出符合给定的 JSON Schema。DeepSeek Responses API 文档
对于同一份联系人数据,核心请求可以写成:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
)
response = client.responses.create(
model="deepseek-v4-pro",
instructions="提取联系人信息。缺失字段用空字符串,不得编造。",
input="张伟,邮箱 zhang.wei@example.com",
text={
"format": {
"type": "json_schema",
"name": "contact_info",
"schema": contact_schema,
}
},
)
与前面的 OpenAI 示例相比,这里按 DeepSeek 已公开的参数定义传值,没有添加 strict。这个输出格式对象没有列出 strict 开关,不能据此推导出它没有 Schema 约束。 文档已经声明了符合 Schema 的行为。
同样,也不能据此断言两家的底层解码实现、Schema 支持范围和异常行为完全一致。能确认的是各自公开的接口契约;实际接入还需要验证所用模型与 Schema。
使用 DeepSeek 时,可以先定位具体接口,再判断约束对象:JSON Output 管正文语法,Beta 严格工具调用管函数参数,Responses 的 json_schema 管最终正文结构。
三、LangChain:统一描述结果,再选择具体输出路径
当应用同时接入 OpenAI 和 DeepSeek,业务代码通常希望用一份类型定义描述结果,再由框架处理请求格式与返回值的差异。
LangChain 就在这一层发挥作用。它的处理过程可以拆成三步:
定义结果
框架处理什么:接收 Python 类型或 JSON Schema,转换为底层接口需要的描述
选择路径
框架处理什么:使用厂商原生输出格式,或通过工具调用提交结构化结果
处理返回值
框架处理什么:解析为对象或字典,并按所用接口、类型和策略进行校验与错误处理
统一 Schema:用业务类型描述需要的结果
所谓“统一 Schema 输入”,就是接收不同形式的数据结构定义。例如,可以用 Pydantic 描述联系人:
from pydantic import BaseModel, ConfigDict
class ContactInfo(BaseModel):
"""从输入中提取的联系人信息。"""
model_config = ConfigDict(extra="forbid")
name: str
email: str
这里的类与前面的 contact_schema 描述同一组字段约定。框架可以将其转换成 Schema,再把输出解析成 ContactInfo 实例。业务代码随后就能使用 contact.name。
LangChain 的 Agent 结构化输出也接受 dataclass、TypedDict 和 JSON Schema 等形式;其中 JSON Schema 字典需要用显式策略包装。具体返回形式与校验能力取决于接口和输入类型。使用 Pydantic,可以获得经过类型定义校验的对象。LangChain 结构化输出文档
Agent 层:选择原生回复或工具调用
在 create_agent 中,两种输出路径对应两个策略。
ProviderStrategy:使用厂商原生结构化输出。
比如配置 ProviderStrategy(ContactInfo, strict=True),接入支持该配置的 OpenAI 模型,框架就会把结果结构交给原生接口约束。
ToolStrategy:通过工具调用提交结构化结果。
框架把 ContactInfo 定义成一个工具,让模型填写姓名和邮箱。收到调用后,框架将参数作为结果收下。这个结果工具不需要执行“保存通讯录”这样的业务动作;它的作用是提交数据。
因此,两条路径都可以交付联系人对象,但底层数据所在的位置不同:原生策略读取结构化回复,工具策略读取调用参数。
下面展示两种显式配置。ProviderStrategy 的 strict 参数要求 langchain>=1.2,并安装对应供应商的集成包:
from langchain.agents import create_agent
from langchain.agents.structured_output import ProviderStrategy, ToolStrategy
openai_agent = create_agent(
model="openai:gpt-6-astra",
tools=[],
response_format=ProviderStrategy(ContactInfo, strict=True),
)
deepseek_agent = create_agent(
model="deepseek:deepseek-v4-pro",
tools=[],
response_format=ToolStrategy(ContactInfo),
)
上面的 DeepSeek Agent 显式使用工具通道提交结果。ToolStrategy 本身不等于开启 Beta 严格模式;是否使用严格约束,还需要检查模型集成与实际请求配置。
如果直接写 response_format=ContactInfo,LangChain 会根据模型能力选择策略;从 langchain>=1.1 起,原生结构化能力会从模型的 profile 数据中动态读取。如果同时提供业务工具,还需要模型支持工具与结构化输出一起使用。成功取得的 Agent 结果放在 result["structured_response"] 中。LangChain 策略选择说明

图 3:Agent 根据能力选择输出路径,工具策略还可在校验失败后反馈重试。
模型层:相同的方法名,可能对应不同接口
厂商 API 提供的能力,与集成类实际调用的能力,需要分别确认。
除了 create_agent,LangChain 还提供模型层的 with_structured_output()。对于单次提取任务,可以直接这样封装:
from langchain_openai import ChatOpenAI
from langchain_deepseek import ChatDeepSeek
openai_extractor = ChatOpenAI(
model="gpt-6-astra"
).with_structured_output(
ContactInfo, method="json_schema", strict=True
)
deepseek_extractor = ChatDeepSeek(
model="deepseek-v4-pro"
).with_structured_output(
ContactInfo, method="function_calling", strict=True
)
前者显式选择 OpenAI 的原生 Schema 输出;后者显式选择 DeepSeek 的严格工具调用。在当前 ChatDeepSeek 实现中,默认服务地址配合 strict=True 会切换到 Beta 地址。
截至 2026 年 9 月 15 日核对的源码,ChatDeepSeek.with_structured_output() 还包含以下转换:
if method == "json_schema":
method = "function_calling"
因此,给这个集成方法传入 json_schema,当前实现仍会转成工具调用。 它与直接调用 DeepSeek Responses API 的 Schema 输出是不同路径;后续集成版本可能调整这一行为。ChatDeepSeek 源码

图 4:框架方法名相同,实际发送的 API 请求仍可能不同。
这也解释了为什么同样叫“结构化输出”,换一个集成类,实际路径就可能改变。最直接的核实方式是查看最终请求:发往哪个地址,调用哪个 API,Schema 放在 tools 里还是回复格式里。
返回结果:解析、校验与错误反馈
例如,普通工具调用漏掉了邮箱,校验器能够发现 email 缺失。Agent 的 ToolStrategy 可以通过 handle_errors 将错误反馈给模型,让它尝试修正;重试仍然可能失败。LangChain 错误处理说明
模型层的 with_structured_output() 与 Agent 层的错误反馈循环也要分清:给返回值增加解析器,并不自动等于建立了“出错后让模型重填”的循环。
在联系人案例中,OpenAI 和 DeepSeek 提供结构化能力;LangChain 接收联系人类型,选择调用路径,并将返回值解析为业务对象。三者在数据流中的职责各不相同。
实际接入时,我会先确定数据属于工具参数还是最终结果,再检查厂商接口与框架请求是否对应。收到输出后,还需要检查完成状态、空内容、拒答或解析错误,并核对关键字段是否来自输入。
Schema 能规定邮箱字段怎样出现,但邮箱属于谁,还需要内容本身有依据。把生成约束、框架适配和业务校验放在各自的位置,结构化输出才算接进了应用。
文档与源码核对日期:2026 年 9 月 15 日。文中的框架行为以该日期核对的实现为准。
更多推荐

所有评论(0)