LangChain 03:结构化输出
前言
直接问模型,得到的是一段自然语言;而下游 API、表单或数据库写入要的是固定字段。
靠正则或关键词从回复里抠字段非常脆弱,模型换个措辞解析就失败。
结构化输出让模型按预定义 schema 返回数据,字段名、类型与含义都由代码约定。
LangChain 1.x 提供两条主要路径:Agent 场景把 Pydantic 模型传给 create_agent 的 response_format;单次调用场景用模型的 with_structured_output。
本文由同系列《LangChain 03:1.x Agent 教程》的结构化输出一节展开,配置与其保持一致:通过 OpenAI 兼容协议对接 火山方舟 Coding Plan。
环境安装与 init_chat_model 入门见同系列《LangChain 入门教程》,本文不再重复。
下文需要 Python 3.12+,依赖用 uv 管理;每个 Python 示例都是完整可运行脚本,复制到项目根目录执行 uv run python xxx.py 即可。
依赖
在项目目录声明 1.x 主包与 OpenAI 兼容集成(版本请按项目实际调整)。
1 | uv add "langchain>=1.0,<2.0" langchain-openai langgraph python-dotenv rich |
rich 用于在终端里更清楚地查看结构化结果:彩色高亮、自动缩进嵌套 dict / list / 模型对象,比内置 print 更适合调试。
在项目根目录创建 .env,写入 Coding Plan 的 API Key 与专用 Base URL,不要把 .env 提交进 Git。
1 | OPENAI_API_KEY=你的火山方舟 API Key |
示例脚本开头用 load_dotenv() 加载该文件。langchain-openai 仍读取 OPENAI_* 环境变量;值必须来自 Coding Plan,请勿使用普通方舟 .../api/v3。
下文模型统一写 openai:ark-code-latest,也可在控制台改成具体模型名后同步替换代码。
响应模型
结构化输出的核心是先用 Pydantic 定义一个「响应模型」:继承 BaseModel,每个字段带类型注解,并用 Field(description=...) 说明含义。
字段描述会进入模型可见的 schema,写清楚「是什么、什么格式」,模型才能填得准。
基本字段
最常见的响应模型就是几个标量字段,下面以天气摘要为例。
1 | from pydantic import BaseModel, Field |
int 类型能顺带约束模型不要输出「25 度左右」这类模糊表述。
需要枚举取值时,用 Literal 把字段限制在固定选项内。
1 | from typing import Literal |
嵌套与列表
真实业务的字段常有层级,Pydantic 模型可以直接嵌套,列表字段写 list[...]。
1 | from pydantic import BaseModel, Field |
解析后仍是类型化对象,brief.days[0].celsius 直接可读,不需要手工转换。
Agent 输出
response_format
create_agent 接受 response_format 参数,传入响应模型后,Agent 会在结束前把最终答案落成该结构的实例。
下面示例沿用天气工具:模型先调工具拿数据,再填进 WeatherBrief。
1 | import os |
读取结果
结构化结果不在 messages 里,而是挂在返回值的 structured_response 键上,类型就是传入的响应模型。brief.model_dump() 转成 dict 后可直接序列化;也可以直接访问属性,比如 brief.celsius。
下游 API、表单或数据库写入应优先读 structured_response,而不是再解析自然语言。
校验与重试
模型返回的数据会先过一遍 Pydantic 校验,类型不符或缺少必填字段时不会静默通过。
校验失败时框架会按策略重试,成功结果才写入 structured_response。
降低失败率的关键仍在响应模型本身:字段描述写清楚,取值范围用类型与约束表达。
例如气温字段可以加上范围约束,超出范围直接判为不合法。
1 | from pydantic import BaseModel, Field |
ge / le 是 Pydantic 的标准约束参数,分别表示大于等于与小于等于;阈值请按业务实际调整。
模型直调
with_structured_output
不是所有场景都需要 Agent 循环,单纯做分类、抽取时直接在模型上调用 with_structured_output 更轻。
它返回一个包装后的模型,invoke 的结果直接就是响应模型实例。
1 | import os |
场景选择
两条路径如何选,看任务是否需要工具与多步推理。
- 单次分类、抽取、打标签:用
with_structured_output,一次模型调用直接拿结果。 - 需要查数据、调接口再汇总:用 Agent +
response_format,工具结果由模型整理进固定字段。 - 在 LangGraph 图内做路由或规划:同样用
with_structured_output包一层,输出交给条件边分发,见同系列《LangGraph 15:工作流模式》。
验证
按下面顺序确认示例可用。
- 执行
uv run python -c "import importlib.metadata as m; print(m.version('langchain'))",主版本应为 1.x。 - 确认
.env中OPENAI_BASE_URL为https://ark.cn-beijing.volces.com/api/coding/v3。 - 跑「response_format」脚本,打印的 dict 应含
city/summary/celsius字段。 - 把
rprint(brief.model_dump())改成rprint(brief.celsius),应直接输出整数。 - 跑「with_structured_output」脚本,
result.label应是三个枚举值之一。 - 临时删掉
response_format参数再运行 Agent 示例,返回值将不再含structured_response键,可对照体会该参数的作用。
总结
- 先用 Pydantic 定义响应模型,字段类型与
Field(description=...)描述共同构成模型可见的 schema。 - Agent 场景把响应模型传给
create_agent的response_format,结果从result["structured_response"]读取。 - 校验失败时框架按策略重试;字段描述写清楚、必要时加范围约束,能降低失败率。
- 单次分类或抽取用
with_structured_output更轻,不必为拿固定字段起一个完整 Agent。 - 下游系统一律消费结构化结果,不要回头解析自然语言。