LangChain 06 - 结构化输出学习导航与实战
LangChain 06 - 结构化输出学习导航与实战
模块 1:你只需要记住这些
1. 什么是结构化输出
结构化输出指的是:要求模型最终返回一个符合预定义结构的数据对象(如固定字段的 JSON、Pydantic 模型、TypedDict),而不再是无格式的自然语言文本。
比如不让模型输出:
盗梦空间在2010年上映,导演是克里斯托弗·诺兰,评分9.3。
而是让它输出:
{"title": "盗梦空间", "year": 2010, "director": "克里斯托弗·诺兰", "rating": 9.3}
💡 核心目标:
把"自然语言回答"变成"程序可以稳定消费的数据"。
价值三点:
- 更容易被代码处理:下游系统可以直接读字段,不用再从自然语言里做解析
- 结果更稳定:减少"模型说法变了但意思差不多"导致的解析失败
- 更适合工程化:适用于表单抽取、分类、路由、工具参数生成、工作流状态传递等场景
2. 传统方式 vs 结构化输出
传统方式(繁琐、不推荐):
- 提示词里苦苦要求模型"请返回 JSON,不要带任何解释"
- 手动
json.loads(response.content) - 手动验证类型
if not isinstance(data['age'], int): raise ... - 手动创建对象
person = Person(**data)
结构化输出(简洁):
structured_llm = model.with_structured_output(Person)
person = structured_llm.invoke("张三是一名 30 岁的软件工程师")
一步到位,自动解析、验证、创建对象。
为什么结构化输出这么受欢迎:
- Prompt 变干净了:字段的 description 直接充当了 Prompt 的一部分
- 类型安全:编辑器能自动补全,代码运行前就能做类型检查
- 极其稳定:依托大模型厂商底层的 JSON 模式,输出错误率降到极低
3. 四种 Schema 定义方式
LangChain 1.x 支持多种 Schema 与结构化输出方式:
| 方式 | 返回类型 | 运行时校验 | 推荐度 |
|---|---|---|---|
| Pydantic | Schema 类实例 | ✅ 字段不匹配抛异常 | 生产首选 |
| TypedDict | 字典 | ❌ 不校验 | 轻量场景 |
| JSON Schema | 字典 | ❌ 不校验 | 跨语言接口 |
| @dataclass | 字典 | ❌ 不校验 | 简单场景 |
理解口诀:
Pydantic 返回对象且校验,其余三种返回字典不校验。
4. Pydantic 是重中之重
四种方式里,Pydantic 功能最丰富,是生产场景首选。它的核心要素:
- 所有模型必须继承
BaseModel - 使用类型提示:
str、int、float、List[xxx]、Optional[xxx]等 - 使用
Field()添加字段描述,帮助 LLM 理解字段含义
高级特性速查:
- 可选字段:
Optional[int]— LLM 未填充时返回None - 默认值:
Field(default="默认值", description="描述") - 枚举类型:
Enum类或Literal["低", "中", "高"] - 列表提取:
List[Person] - 嵌套结构:模型里包含模型(建议 ≤ 3 层)
- 限制条件:
min_length、max_length、ge(>=)、le(<=)、gt(>)
5. with_structured_output 的工作流程
第1步:定义结构(Pydantic 模型)
↓
第2步:协议转换(LangChain 自动把 Pydantic 转成 JSON Schema)
↓
第3步:模型交互(JSON Schema 作为 Tools 传入,模型底层语法约束保证格式)
↓
第4步:自动解析与验证(Pydantic 解析字符串 → 字典 → 验证类型 → 返回对象)
关键理解:
你写的 Python 类 → 自动变成 JSON Schema → 约束模型输出 → 自动解析回来
模块 2:带教式理解
1. 为什么"没有描述,LLM 可能格式错误"
Field(description="...") 不只是给开发者看的注释,它会被 LangChain 转换成 JSON Schema 里的 description 字段,直接传给模型。
所以:
- 有 description → 模型知道这个字段要填什么
- 没 description → 模型只能靠字段名猜,容易出错
这也是为什么 Pydantic 模式比裸 JSON Schema 更好用:你只需要写 Python,description 自动就进了 Schema。
2. Optional 和 default 的区别容易混淆
很多初学者分不清这两种写法:
# 写法 1:Optional,没默认值
age: Optional[int] = Field(description="年龄")
# → LLM 没提到年龄时,返回 None
# 写法 2:有默认值
age: int = Field(default=1, description="年龄")
# → LLM 没提到年龄时,返回 1
直觉:
Optional= “这个字段可以不存在”default= “这个字段不存在时用这个值兜底”
⚠️ 注意:不同模型提供商对 default 字段的支持是不同的,同样代码在不同平台结果可能不一样。
3. Enum vs Literal 怎么选
两种写法都能限制字段可选值:
# 方式 1:Enum 类
class Priority(str, Enum):
LOW = "低"
MEDIUM = "中"
HIGH = "高"
urgency: Priority = Field(description="紧急程度")
# 方式 2:Literal(更简洁,**不想单独**定义类时用)
urgency: Literal["低", "中", "高"] = Field(description="紧急程度")
选择直觉:
- 值少、不复用 →
Literal - 值多、多处复用、需要
.value访问 →Enum
4. 嵌套结构为什么要控制在 3 层以内
# 能跑,但容易出错
class Bad(BaseModel):
user: User
company: Company
address: Address
country: Country # 4 层嵌套
LLM 能力有限,嵌套层级越深,模型越容易在某一层丢字段或填错。
💡 建议:
- 嵌套层级 ≤ 3 层
- 使用清晰的 description
- 必要时拆分成多次调用
5. 为什么只有 Pydantic 会抛异常
PDF 里用了一个 fake server 做实验:故意返回字段名不匹配的数据(title1 而不是 title)。
结果:
- Pydantic:抛
ValidationError,告诉你哪个字段 missing - TypedDict:直接返回
{'title1': '盗梦空间', ...},不报错 - JSON Schema:同上,不报错
- @dataclass:同上,不报错
因为只有 Pydantic 在运行时做了类型校验,其余三种只是"类型声明",不是"运行时强校验器"。
这个区别在生产环境很关键:
如果你需要"模型输出格式不对就立刻报错"的保障,选 Pydantic。
6. include_raw=True 什么时候有用
正常调用 .with_structured_output() 只返回解析后的对象,你拿不到原始的 AIMessage。
但有时你需要:
- 看 token 用量(
response_metadata.token_usage) - 看 tool_calls 原始结构
- 排查解析错误
这时传入 include_raw=True:
model_with_structure = model.with_structured_output(Movie, include_raw=True)
resp = model_with_structure.invoke("...")
# resp 是一个字典,包含三个字段:
# 'raw': 原始 AIMessage
# 'parsed': 解析后的对象
# 'parsing_error': 解析错误(None 表示成功)
7. 输出解析器(JsonOutputParser)为什么不推荐
传统方式的流程是:
提示词指导(引导生成指定格式) → 模型生成文本 → 解析器转换
问题:
- 需要在 Prompt 里手动写"必须输出 JSON"
- 需要自己创建
JsonOutputParser(pydantic_object=Movie) - 需要用
chain = prompt_template | model | parser拼链 - 稳定性不如
with_structured_output()
所以:
**了解**即可,实际开发用 with_structured_output()。
这一章结束后,你应该能自己回答
1、结构化输出解决的核心问题是什么?
它解决的是"模型输出如何被程序稳定消费"的问题。通过预定义 Schema,让模型返回固定字段的数据对象,而不是无格式的自然语言文本,下游系统可以直接读字段,不用再从文本里做解析。
2、四种 Schema 方式中,哪种会做运行时校验?
只有 Pydantic 会做运行时校验。当模型返回的数据字段不匹配 Schema 时,Pydantic 会抛出 ValidationError。TypedDict、JSON Schema、@dataclass 都只返回未校验的字典,不会报错。
3、为什么 Field(description="...") 很重要?
因为 description 会被 LangChain 自动转换成 JSON Schema 里的字段描述,直接传给模型。没有描述,模型只能靠字段名猜,容易格式错误。有描述,模型就知道每个字段该填什么。
4、Optional 和 default 有什么区别?
Optional 表示"这个字段可以不存在",LLM 没提及时返回 None。default 表示"这个字段不存在时用这个默认值兜底"。注意不同模型提供商对 default 的支持不同,同样代码在不同平台结果可能不一样。
5、include_raw=True 有什么用?
正常调用只返回解析后的对象,拿不到原始 AIMessage。传入 include_raw=True 后返回一个字典,包含 raw(原始 AIMessage,含 token 用量等元数据)、parsed(解析后的对象)、parsing_error(解析错误信息),适合排查问题。
小测试
1. 传统方式获取结构化数据需要几步?
至少四步:① 提示词要求输出 JSON ② 手动 json.loads 解析 ③ 手动验证类型 ④ 手动创建对象。而 with_structured_output() 一步到位。
2. TypedDict 和 Pydantic 最关键的区别是什么?
TypedDict 返回字典且不做运行时校验,Pydantic 返回类实例且做运行时校验(字段不匹配会抛异常)。
3. 嵌套结构建议控制在几层以内?
💡 建议 ≤ 3 层。层级越深,模型越容易在某一层丢字段或填错。
4. 如果需要限制字段只能是几个固定值,有哪两种写法?
用 Enum 类或 Literal["值1", "值2"]。值少且不复用选 Literal,值多或多处复用选 Enum。
5. DeepSeek 模型支持 method="json_schema" 吗?
不支持。DeepSeek 模型服务不支持 json_schema 模式。是否可用依赖于模型供应商及 LangChain 适配器的具体实现。








