使用 LangChain 开发应用时,模型最终返回的通常是一段自然语言。
如果只是聊天,这种结果没有问题。但当模型输出还要交给程序继续处理,例如保存数据库、调用接口、渲染页面或进入下一步工作流,自然语言就会变得麻烦。
例如,我们希望模型从一段客户消息中提取姓名、邮箱和电话。与其让模型回答:
这个人的姓名是张三,邮箱是...
更希望直接得到:
{
"name": "张三",
"email": "zhangsan@example.com",
"phone": "13800138000"
}
这就是 LangChain 中的结构化输出(Structured Output)。本文主要介绍它解决什么问题、Agent 和 Model 中分别怎样使用,以及 ProviderStrategy、ToolStrategy 应该如何选择。
一、结构化输出的作用
结构化输出并不是简单地要求模型“返回 JSON”。
真正需要解决的问题,是让模型输出符合一个事先确定的数据结构,并让程序可以直接使用这个结果,而不是再从一段文本中做字符串截取、正则匹配或 JSON 修复。
针对本文开头提到的示例,我们可以先定义一个 Schema,例如:
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 中。
先看一个完整例子:
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"])
运行结果
模型生成内容可能略有不同,下面展示一次典型结果:
ContactInfo(
name='张三',
email='zhangsan@example.com',
phone='13800138000'
)
这里有两个地方需要注意。
一个是我们没有要求模型“请返回 JSON”,真正决定输出格式的是 ContactInfo。
另一个是最终获取结构化结果时,不需要再解析最后一条 AIMessage,而是直接读取:
result["structured_response"]
这让结构化输出真正成为 Agent 运行结果的一部分,而不是藏在自然语言中的一段 JSON。
三、两种输出策略
虽然上面的代码只传入了一个 Schema,但 LangChain 在内部仍然需要决定:用什么方式约束模型产生这个结构。
当前主要有两种策略:
| 策略 | 实现方式 | 适用情况 | 特点 |
|---|---|---|---|
ProviderStrategy | 使用模型提供商原生结构化输出 | Provider 和模型原生支持 | 通常可靠性更高 |
ToolStrategy | 利用 Tool Calling 生成结构化数据 | 模型支持工具调用 | 兼容范围更广 |
ProviderStrategy
部分模型提供商的 API 本身支持 Schema 约束。此时可以使用:
from langchain.agents.structured_output import ProviderStrategy
agent = create_agent(
model="openai:gpt-5.5",
response_format=ProviderStrategy(ContactInfo),
)
运行结果
ContactInfo(
name='张三',
email='zhangsan@example.com',
phone='13800138000'
)
这种情况下,Schema 会直接交给模型 Provider 的结构化输出能力处理,由 Provider 对结果格式进行约束。
实际开发中通常不需要手动写 ProviderStrategy。
直接使用:
response_format=ContactInfo
LangChain 会结合模型的 Profile 判断其结构化输出能力:模型支持原生结构化输出时选择 ProviderStrategy,否则使用 ToolStrategy。
需要注意一个细节:如果传入的是普通 JSON Schema 字典,不能直接依赖自动策略选择,需要显式包装成 ProviderStrategy 或 ToolStrategy。
ToolStrategy
如果模型没有原生结构化输出能力,但支持 Tool Calling,可以显式使用:
from langchain.agents.structured_output import ToolStrategy
agent = create_agent(
model="openai:gpt-5.5",
response_format=ToolStrategy(ContactInfo),
)
运行结果
ContactInfo(
name='张三',
email='zhangsan@example.com',
phone='13800138000'
)
这里模型实际上会按照类似“调用一个具有固定参数 Schema 的工具”的方式提交数据,然后 LangChain 再验证并转换成目标对象。
因此,Tool Calling 在这里并不是为了真正访问数据库或调用外部 API,而是被当成一种约束模型输出格式的协议。
四、模型级结构化输出
response_format 主要解决 Agent 最终结果的问题。
但很多场景并不需要 Agent。
例如,只是让模型从一段文本中提取电影信息,没有工具调用、循环执行或 Agent State,此时直接使用 Model 的 with_structured_output() 更简单。
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)
运行结果
模型生成内容可能略有不同,下面展示一次典型结果:
Movie(
title='盗梦空间',
year=2010,
director='Christopher Nolan'
)
这里的 structured_model 仍然是一个可调用的 Model,只是它已经绑定了输出 Schema。
所以两种方式解决的问题并不相同:
单次模型处理
Model
↓
with_structured_output()
↓
结构化数据
而 Agent 更接近:
用户任务
↓
Agent
↓
Model ↔ Tool
↓
response_format
↓
最终结构化结果
如果只是抽取、分类、判断等单次模型任务,通常优先使用 with_structured_output();如果模型还需要调用工具并经过 Agent 循环,再使用 create_agent(response_format=...)。
五、Schema 的设计方式
结构化输出能否稳定工作,很大程度上取决于 Schema 是否定义清楚。
Pydantic 通常是 Python 项目中比较合适的默认选择,因为字段类型、说明和校验规则可以放在同一个模型里。LangChain 的模型接口也明确区分了这一点:Pydantic 可以进行运行时验证,而 TypedDict 更轻量,JSON Schema 则更适合跨语言和已有接口规范的场景。
例如下面这个 Schema:
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="评价内容的简短摘要"
)
运行结果
对于:
东西不错,物流也快,就是价格稍微有点贵,给 4 分。
一次典型输出可能是:
ProductReview(
rating=4,
sentiment='positive',
summary='商品体验较好,物流较快,但价格偏高'
)
这里的 ge=1、le=5 不只是给模型看的说明,还参与 Pydantic 的结果验证;Literal 则把情感分类限制在预先定义的几个值中。
实际项目中,Schema 应尽量表达业务真正需要的约束,而不是只写一堆 str。
例如状态字段适合使用 Literal,评分适合增加数值范围,不一定存在的信息则应该允许 None。字段的 description 也应说明业务含义,而不是简单重复字段名。
六、错误处理与结果校验
结构化输出降低了解析结果的不确定性,但不意味着模型永远不会产生错误。
尤其使用 ToolStrategy 时,模型可能返回不符合 Schema 的参数,或者在多个可选 Schema 中一次生成多个结构化结果。
LangChain 会对这类结果进行验证。ToolStrategy 默认启用错误处理,在验证失败时可以把错误信息作为 ToolMessage 返回给模型,让模型重新生成。handle_errors 还可以配置自定义错误信息、指定异常类型,或者关闭自动重试。
例如:
agent = create_agent(
model="openai:gpt-5.5",
response_format=ToolStrategy(
ProductReview,
handle_errors="评分必须在 1 到 5 之间,请重新生成。"
),
)
运行结果
如果模型第一次生成:
rating=10
Schema 校验失败后,模型会收到错误反馈并重新生成,例如:
ProductReview(
rating=5,
sentiment='positive',
summary='用户对商品评价很好'
)
这也是使用 Schema 和单纯要求“返回 JSON”的重要区别。
JSON 只能说明数据能够被解析,却不能说明:
rating = 100
是否符合业务规则。
还要区分另一件事:格式正确并不等于内容正确。
结构化输出可以保证字段、类型和部分约束符合要求,却不能证明模型提取出的姓名、金额或分类一定符合事实。涉及数据库写入、资金操作或其他重要业务时,仍然需要业务层校验。
七、实际项目的选择
理解了前面的机制之后,实际使用时不需要把所有策略都配置一遍。
多数情况下,可以按照下面的顺序判断:
如果只是一次模型调用,例如抽取、分类、生成固定对象:
model.with_structured_output(Schema)
如果已经使用 create_agent(),并且只关心 Agent 最终返回什么:
create_agent(
...,
response_format=Schema,
)
通常先直接传 Schema,让 LangChain根据模型能力选择策略。只有在需要明确控制输出机制、错误重试或兼容某些模型时,再显式使用:
ProviderStrategy(Schema)
或:
ToolStrategy(Schema)
Schema 本身也不要设计得过度复杂。
如果一个对象包含大量深层嵌套、几十个可选字段以及复杂条件,模型理解和生成的难度都会增加。更实际的做法,是让一次结构化输出只承担一个明确的数据任务。
总结
结构化输出解决的是模型结果如何可靠地被程序读取并使用。
当输出只是给人阅读,自然语言已经足够;当结果还要进入数据库、接口、UI 或下一段工作流,就应该把 Schema 看成模型与程序之间的一份数据契约。
本文介绍的几种方式可以归结为一个简单的判断:单次模型任务使用 with_structured_output();Agent 的最终结果使用 response_format;模型支持原生结构化输出时优先交给 Provider,否则可以通过 Tool Calling 完成。真正落地时,比选择哪一个 API 更重要的是把 Schema 定义清楚,让字段、类型和业务约束能够被程序直接理解和验证。
社区讨论
参与讨论
有问题或想法?欢迎继续讨论。