结构化输出既能约束工具调用参数,也能规定模型最终回复的格式。本文通过同一个联系人案例,梳理 OpenAI、DeepSeek 的接口能力,以及 LangChain 的策略选择、解析校验与实际调用路径。

以前,我知道 Function Calling 可以严格规定函数参数,却以为模型最后回复的 text,只能通过提示词要求它“尽量输出 JSON”。

后来我发现,最终回复本身也可以按照指定的结构生成。 从“请按 JSON 回答”到“按 Schema 交付结果”,接口设计和数据处理方式都会随之改变。

要理解这项能力,需要依次回答三个问题:

  1. OpenAI: 如何原生约束工具参数和最终回复?
  2. DeepSeek: 对应能力在哪些接口里,如何开启?
  3. 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 的三种类型:textjson_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 日。文中的框架行为以该日期核对的实现为准。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐