使用 LangChain 开发应用时,模型最终返回的通常是一段自然语言。

如果只是聊天,这种结果没有问题。但当模型输出还要交给程序继续处理,例如保存数据库、调用接口、渲染页面或进入下一步工作流,自然语言就会变得麻烦。

例如,我们希望模型从一段客户消息中提取姓名、邮箱和电话。与其让模型回答:

代码片段Text
这个人的姓名是张三,邮箱是...

更希望直接得到:

代码片段JSON
{
  "name": "张三",
  "email": "zhangsan@example.com",
  "phone": "13800138000"
}

这就是 LangChain 中的结构化输出(Structured Output)。本文主要介绍它解决什么问题、Agent 和 Model 中分别怎样使用,以及 ProviderStrategyToolStrategy 应该如何选择。

一、结构化输出的作用

结构化输出并不是简单地要求模型“返回 JSON”。

真正需要解决的问题,是让模型输出符合一个事先确定的数据结构,并让程序可以直接使用这个结果,而不是再从一段文本中做字符串截取、正则匹配或 JSON 修复。

针对本文开头提到的示例,我们可以先定义一个 Schema,例如:

代码片段Python
from pydantic import BaseModel, Field
 
 
class ContactInfo(BaseModel):
    name: str = Field(description="联系人姓名")
    email: str = Field(description="联系人邮箱")
    phone: str = Field(description="联系人电话号码")

这里的 ContactInfo 就是一份输出契约:最终结果需要包含哪些字段,每个字段是什么类型,各自表达什么含义。

当前 LangChain 支持使用 Pydantic、Dataclass、TypedDict 和 JSON Schema 等方式描述结构。使用 Pydantic 时,还可以直接获得字段校验、类型约束和嵌套结构等能力。

结构化输出比较典型的场景包括:

  • 从邮件、合同、日志中提取字段;
  • 对文本进行分类、打标签;
  • 生成后续 API 所需参数;
  • 返回前端需要的卡片、表格数据;
  • 将 Agent 的结果交给下一段程序继续处理。

如果结果最终只是展示给人阅读,例如解释一个技术问题或回答一次普通问答,就没有必要为了“看起来规范”强行使用结构化输出。

二、Agent 中的基本使用

通过 create_agent() 创建 Agent 时,结构化输出主要由 response_format 参数控制。

最简单的方式,是直接把 Schema 传进去。LangChain 会根据模型能力选择合适的结构化输出策略,最终结果放在 Agent State 的 structured_response 中。

先看一个完整例子:

代码片段Python
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",
    response_format=ContactInfo,
)
 
result = agent.invoke({
    "messages": [
        {
            "role": "user",
            "content": (
                "提取联系人信息:"
                "张三,邮箱 zhangsan@example.com,"
                "电话 13800138000"
            ),
        }
    ]
})
 
print(result["structured_response"])

运行结果

模型生成内容可能略有不同,下面展示一次典型结果:

代码片段Text
ContactInfo(
    name='张三',
    email='zhangsan@example.com',
    phone='13800138000'
)

这里有两个地方需要注意。

一个是我们没有要求模型“请返回 JSON”,真正决定输出格式的是 ContactInfo

另一个是最终获取结构化结果时,不需要再解析最后一条 AIMessage,而是直接读取:

代码片段Python
result["structured_response"]

这让结构化输出真正成为 Agent 运行结果的一部分,而不是藏在自然语言中的一段 JSON。

三、两种输出策略

虽然上面的代码只传入了一个 Schema,但 LangChain 在内部仍然需要决定:用什么方式约束模型产生这个结构。

当前主要有两种策略:

策略实现方式适用情况特点
ProviderStrategy使用模型提供商原生结构化输出Provider 和模型原生支持通常可靠性更高
ToolStrategy利用 Tool Calling 生成结构化数据模型支持工具调用兼容范围更广

ProviderStrategy

部分模型提供商的 API 本身支持 Schema 约束。此时可以使用:

代码片段Python
from langchain.agents.structured_output import ProviderStrategy
 
agent = create_agent(
    model="openai:gpt-5.5",
    response_format=ProviderStrategy(ContactInfo),
)

运行结果

代码片段Text
ContactInfo(
    name='张三',
    email='zhangsan@example.com',
    phone='13800138000'
)

这种情况下,Schema 会直接交给模型 Provider 的结构化输出能力处理,由 Provider 对结果格式进行约束。

实际开发中通常不需要手动写 ProviderStrategy

直接使用:

代码片段Python
response_format=ContactInfo

LangChain 会结合模型的 Profile 判断其结构化输出能力:模型支持原生结构化输出时选择 ProviderStrategy,否则使用 ToolStrategy

需要注意一个细节:如果传入的是普通 JSON Schema 字典,不能直接依赖自动策略选择,需要显式包装成 ProviderStrategyToolStrategy

ToolStrategy

如果模型没有原生结构化输出能力,但支持 Tool Calling,可以显式使用:

代码片段Python
from langchain.agents.structured_output import ToolStrategy
 
agent = create_agent(
    model="openai:gpt-5.5",
    response_format=ToolStrategy(ContactInfo),
)

运行结果

代码片段Text
ContactInfo(
    name='张三',
    email='zhangsan@example.com',
    phone='13800138000'
)

这里模型实际上会按照类似“调用一个具有固定参数 Schema 的工具”的方式提交数据,然后 LangChain 再验证并转换成目标对象。

因此,Tool Calling 在这里并不是为了真正访问数据库或调用外部 API,而是被当成一种约束模型输出格式的协议

四、模型级结构化输出

response_format 主要解决 Agent 最终结果的问题。

但很多场景并不需要 Agent。

例如,只是让模型从一段文本中提取电影信息,没有工具调用、循环执行或 Agent State,此时直接使用 Model 的 with_structured_output() 更简单。

代码片段Python
from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
 
 
class Movie(BaseModel):
    title: str = Field(description="电影名称")
    year: int = Field(description="上映年份")
    director: str = Field(description="导演")
 
 
model = init_chat_model("gpt-5.5")
 
structured_model = model.with_structured_output(Movie)
 
result = structured_model.invoke(
    "电影《盗梦空间》于 2010 年上映,导演是 Christopher Nolan。"
)
 
print(result)

运行结果

模型生成内容可能略有不同,下面展示一次典型结果:

代码片段Text
Movie(
    title='盗梦空间',
    year=2010,
    director='Christopher Nolan'
)

这里的 structured_model 仍然是一个可调用的 Model,只是它已经绑定了输出 Schema。

所以两种方式解决的问题并不相同:

代码片段Text
单次模型处理
Model

with_structured_output()

结构化数据

而 Agent 更接近:

代码片段Text
用户任务

Agent

Model ↔ Tool

response_format

最终结构化结果

如果只是抽取、分类、判断等单次模型任务,通常优先使用 with_structured_output();如果模型还需要调用工具并经过 Agent 循环,再使用 create_agent(response_format=...)

五、Schema 的设计方式

结构化输出能否稳定工作,很大程度上取决于 Schema 是否定义清楚。

Pydantic 通常是 Python 项目中比较合适的默认选择,因为字段类型、说明和校验规则可以放在同一个模型里。LangChain 的模型接口也明确区分了这一点:Pydantic 可以进行运行时验证,而 TypedDict 更轻量,JSON Schema 则更适合跨语言和已有接口规范的场景。

例如下面这个 Schema:

代码片段Python
from typing import Literal
from pydantic import BaseModel, Field
 
 
class ProductReview(BaseModel):
    rating: int = Field(
        description="商品评分",
        ge=1,
        le=5,
    )
 
    sentiment: Literal[
        "positive",
        "negative",
        "neutral"
    ] = Field(
        description="评价情感"
    )
 
    summary: str = Field(
        description="评价内容的简短摘要"
    )

运行结果

对于:

代码片段Text
东西不错,物流也快,就是价格稍微有点贵,给 4 分。

一次典型输出可能是:

代码片段Text
ProductReview(
    rating=4,
    sentiment='positive',
    summary='商品体验较好,物流较快,但价格偏高'
)

这里的 ge=1le=5 不只是给模型看的说明,还参与 Pydantic 的结果验证;Literal 则把情感分类限制在预先定义的几个值中。

实际项目中,Schema 应尽量表达业务真正需要的约束,而不是只写一堆 str

例如状态字段适合使用 Literal,评分适合增加数值范围,不一定存在的信息则应该允许 None。字段的 description 也应说明业务含义,而不是简单重复字段名。

六、错误处理与结果校验

结构化输出降低了解析结果的不确定性,但不意味着模型永远不会产生错误。

尤其使用 ToolStrategy 时,模型可能返回不符合 Schema 的参数,或者在多个可选 Schema 中一次生成多个结构化结果。

LangChain 会对这类结果进行验证。ToolStrategy 默认启用错误处理,在验证失败时可以把错误信息作为 ToolMessage 返回给模型,让模型重新生成。handle_errors 还可以配置自定义错误信息、指定异常类型,或者关闭自动重试。

例如:

代码片段Python
agent = create_agent(
    model="openai:gpt-5.5",
    response_format=ToolStrategy(
        ProductReview,
        handle_errors="评分必须在 1 到 5 之间,请重新生成。"
    ),
)

运行结果

如果模型第一次生成:

代码片段Text
rating=10

Schema 校验失败后,模型会收到错误反馈并重新生成,例如:

代码片段Text
ProductReview(
    rating=5,
    sentiment='positive',
    summary='用户对商品评价很好'
)

这也是使用 Schema 和单纯要求“返回 JSON”的重要区别。

JSON 只能说明数据能够被解析,却不能说明:

代码片段Text
rating = 100

是否符合业务规则。

还要区分另一件事:格式正确并不等于内容正确。

结构化输出可以保证字段、类型和部分约束符合要求,却不能证明模型提取出的姓名、金额或分类一定符合事实。涉及数据库写入、资金操作或其他重要业务时,仍然需要业务层校验。

七、实际项目的选择

理解了前面的机制之后,实际使用时不需要把所有策略都配置一遍。

多数情况下,可以按照下面的顺序判断:

如果只是一次模型调用,例如抽取、分类、生成固定对象:

代码片段Python
model.with_structured_output(Schema)

如果已经使用 create_agent(),并且只关心 Agent 最终返回什么:

代码片段Python
create_agent(
    ...,
    response_format=Schema,
)

通常先直接传 Schema,让 LangChain根据模型能力选择策略。只有在需要明确控制输出机制、错误重试或兼容某些模型时,再显式使用:

代码片段Python
ProviderStrategy(Schema)

或:

代码片段Python
ToolStrategy(Schema)

Schema 本身也不要设计得过度复杂。

如果一个对象包含大量深层嵌套、几十个可选字段以及复杂条件,模型理解和生成的难度都会增加。更实际的做法,是让一次结构化输出只承担一个明确的数据任务。

总结

结构化输出解决的是模型结果如何可靠地被程序读取并使用

当输出只是给人阅读,自然语言已经足够;当结果还要进入数据库、接口、UI 或下一段工作流,就应该把 Schema 看成模型与程序之间的一份数据契约。

本文介绍的几种方式可以归结为一个简单的判断:单次模型任务使用 with_structured_output();Agent 的最终结果使用 response_format;模型支持原生结构化输出时优先交给 Provider,否则可以通过 Tool Calling 完成。真正落地时,比选择哪一个 API 更重要的是把 Schema 定义清楚,让字段、类型和业务约束能够被程序直接理解和验证。