前言
在前面的学习中,我们已经能够通过 LangChain 调用大模型,也学会了使用 Prompt Template 组织提示词。不过,大模型默认返回的依然是一段文本------即使它看起来像列表或者 JSON,对 Python 程序来说,它通常仍然只是一个字符串。
例如,让模型列出 5 个中国汽车品牌,模型可能返回:
text
比亚迪, 吉利, 长城, 红旗, 奇瑞
人可以直接看懂,但程序如果要继续遍历、筛选或者保存,更希望得到真正的 Python 列表:
python
["比亚迪", "吉利", "长城", "红旗", "奇瑞"]
同样,如果要求模型从电影简介中提取电影名、导演和题材,程序真正需要的也不是一段随意组织的说明,而是一份字段固定、类型明确的数据。
这正是 Output Parser(输出解析器)要解决的问题。
本文基于课堂中的两个 Notebook 进行讲解:
04 Output Parser _List.ipynb:把模型输出解析成 Python 列表05 Output Parser _ JSON.ipynb:把电影信息解析成 Pydantic 结构化对象
通过本文,你将掌握:
- Output Parser 的核心工作方式
- 使用
CommaSeparatedListOutputParser解析列表 - 使用
PydanticOutputParser解析为结构化对象 - 两种解析器的适用场景与对比
- 常见问题与调试方法
项目效果展示

模型返回逗号分隔的字符串后,解析器将其转换为 Python 列表:
text
模型原始输出:比亚迪, 吉利, 长城, 红旗, 奇瑞
解析后结果:['比亚迪', '吉利', '长城', '红旗', '奇瑞']
结果类型:<class 'list'>

模型返回 JSON 字符串后,解析器将其转换为 FilmInfo 对象:
text
模型原始输出:{"film_name": "复仇者联盟4:终局之战", "author_name": "安东尼·罗素和乔·罗素", "genres": ["动作", "科幻"]}
解析后对象:FilmInfo(film_name='复仇者联盟4:终局之战', author_name='安东尼·罗素和乔·罗素', genres=['动作', '科幻'])
直接访问属性:复仇者联盟4:终局之战
完整工程代码
以下为两个案例的完整可运行代码,可直接复制执行。
案例一:列表解析完整代码
python
import os
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import CommaSeparatedListOutputParser
from langchain_core.prompts import ChatPromptTemplate
# 1. 创建列表解析器
output_parser = CommaSeparatedListOutputParser()
parser_instructions = output_parser.get_format_instructions()
# 2. 构建提示词模板
prompt_template = ChatPromptTemplate.from_messages([
("system", "{parser_instructions}"),
("human", "列出5个{subject}国家的汽车品牌。")
])
# 3. 构造最终提示词
final_prompt = prompt_template.invoke({
"subject": "中国",
"parser_instructions": parser_instructions
})
# 4. 初始化模型
model = ChatOpenAI(
model="qwen-plus",
openai_api_key=os.getenv("DASHSCOPE_API_KEY"),
openai_api_base=os.getenv(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1"
)
)
# 5. 调用模型并解析
response = model.invoke(final_prompt)
result = output_parser.invoke(response)
print("模型原始文本:", response.content)
print("解析后的列表:", result)
print("结果类型:", type(result))
案例二:JSON 解析完整代码
python
import os
from typing import List
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic.v1 import BaseModel, Field
# 1. 定义数据模型
class FilmInfo(BaseModel):
film_name: str = Field(
description="电影的名字",
example="拯救大兵瑞恩"
)
author_name: str = Field(
description="电影的导演",
example="斯皮尔伯格"
)
genres: List[str] = Field(
description="电影的题材",
example=["历史", "战争"]
)
# 2. 创建解析器
output_parser = PydanticOutputParser(pydantic_object=FilmInfo)
# 3. 构建提示词模板
prompt_template = ChatPromptTemplate.from_messages([
("system", "{parser_instructions} 你输出的结果请使用中文。"),
(
"human",
"请你帮我从电影概述中,提取电影名、导演,以及电影的体裁。"
"电影概述会被三个#符号包围。\n###{film_introduction}###"
)
])
# 4. 准备电影简介
film_introduction = """
《复仇者联盟4:终局之战》是由安东尼·罗素和乔·罗素
联合执导,小罗伯特·唐尼、克里斯·埃文斯等主演的动作科幻片。
该片改编自漫威漫画,讲述复仇者联盟剩余成员再次集结,
利用时间装置穿越时空、重新创造希望的故事。
"""
# 5. 构造最终提示词
final_prompt = prompt_template.invoke({
"film_introduction": film_introduction,
"parser_instructions": output_parser.get_format_instructions()
})
# 6. 初始化模型
model = ChatOpenAI(
model="qwen-plus",
openai_api_key=os.getenv("DASHSCOPE_API_KEY"),
openai_api_base=os.getenv(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1"
)
)
# 7. 调用模型并解析
response = model.invoke(final_prompt)
result = output_parser.invoke(response)
print("模型原始文本:")
print(response.content)
print("\n解析后的对象:")
print(result)
print("\n电影名称:", result.film_name)
print("电影题材:", result.genres)
一、为什么大模型输出还需要解析
1. 从文本到数据
大模型默认返回的是一段文本。即使它看起来像列表或者 JSON,对 Python 程序来说,它通常仍然只是一个字符串。
核心问题:人可以直接看懂,但程序如果要继续遍历、筛选或者保存,更希望得到真正的 Python 数据结构。
2. Output Parser 的工作方式
Output Parser 通常承担两项工作:
第一项工作 ,是告诉模型应该按照什么格式回答。解析器会通过 get_format_instructions() 生成格式要求,然后我们把这段要求放进提示词。
第二项工作,是在模型回答之后,把文本转换成 Python 数据,并检查它是否符合预期结构。
完整流程如下:

二、实战一:把模型回答解析成 Python 列表
1. 导入所需组件
功能说明
导入模型客户端、列表解析器和提示词模板。
完整代码
python
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import CommaSeparatedListOutputParser
from langchain_core.prompts import ChatPromptTemplate
核心实现逻辑
三个组件的职责分别是:
ChatOpenAI:通过 OpenAI 兼容接口调用通义千问CommaSeparatedListOutputParser:处理以逗号分隔的文本ChatPromptTemplate:构建包含变量的聊天提示词
2. 创建列表解析器并获取格式说明
完整代码
python
output_parser = CommaSeparatedListOutputParser()
parser_instructions = output_parser.get_format_instructions()
print(parser_instructions)
输出
text
Your response should be a list of comma separated values,
eg: `foo, bar, baz` or `foo,bar,baz`
关键理解 :parser_instructions 并不是最终结果,而是一段要发送给模型的格式说明。解析器通过这种方式"约束"模型按预期格式回答。
3. 把格式说明加入提示词
完整代码
python
prompt_template = ChatPromptTemplate.from_messages([
("system", "{parser_instructions}"),
("human", "列出5个{subject}国家的汽车品牌。")
])
其中包含两个变量:
parser_instructions:解析器生成的格式要求subject:要查询的国家
构造最终提示词
python
final_prompt = prompt_template.invoke({
"subject": "中国",
"parser_instructions": parser_instructions
})
最终指令可以理解为:请列出 5 个中国汽车品牌,并且只用逗号分隔这些品牌。
4. 调用模型
完整代码
python
import os
model = ChatOpenAI(
model="qwen-plus",
openai_api_key=os.getenv("DASHSCOPE_API_KEY"),
openai_api_base=os.getenv(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1"
)
)
response = model.invoke(final_prompt)
print(response.content)
输出
text
比亚迪, 吉利, 长城, 红旗, 奇瑞
关键点 :此时结果虽然长得像列表,但 response.content 仍然是字符串:
python
print(type(response.content))
text
<class 'str'>
5. 将字符串解析成列表
完整代码
python
result = output_parser.invoke(response)
print(result)
输出
python
['比亚迪', '吉利', '长城', '红旗', '奇瑞']
现在的 result 才是真正的 Python 列表,可以继续遍历或按索引访问:
python
for brand in result:
print(brand)
6. 为什么不直接使用 split
对于这个简单案例,我们当然也可以写:
python
brands = response.content.split(",")
但是,这种方式只负责字符串切割,不负责指导模型如何回答。只要模型改用中文逗号、换行或者增加解释性文字,结果就可能需要额外清理。
Output Parser 的优势在于,它把"要求模型按格式回答"和"程序解析回答"放在同一套规则中。项目越复杂,这种统一约束越重要。
三、实战二:提取电影信息并生成结构化对象
1. 为什么列表已经不够用了
汽车品牌案例中的每个元素都是同一类数据,用列表表示很合适。
电影信息则包含多个含义不同的字段:
- 电影名称是字符串
- 导演名称是字符串
- 电影题材可能有多个,因此应当是字符串列表
如果仍然只返回一个普通列表,我们很难知道每个位置究竟代表什么。因此,我们需要使用 PydanticOutputParser 定义结构化输出。
2. 导入相关组件
完整代码
python
from typing import List
import os
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from pydantic.v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
3. 使用 Pydantic 编写数据说明书
完整代码
python
class FilmInfo(BaseModel):
film_name: str = Field(
description="电影的名字",
example="拯救大兵瑞恩"
)
author_name: str = Field(
description="电影的导演",
example="斯皮尔伯格"
)
genres: List[str] = Field(
description="电影的题材",
example=["历史", "战争"]
)
核心实现逻辑
可以把 BaseModel 理解成一份数据说明书 ,而 Field 用来补充每个字段的含义和示例。
其中的类型标注非常重要:
| 字段 | 类型 | 含义 |
|---|---|---|
film_name |
str |
电影名必须是字符串 |
author_name |
str |
导演必须是字符串 |
genres |
List[str] |
题材必须是字符串列表 |
4. 创建 Pydantic 输出解析器
完整代码
python
output_parser = PydanticOutputParser(pydantic_object=FilmInfo)
参数 pydantic_object=FilmInfo 表示:后续返回结果必须符合 FilmInfo 定义的结构。
查看解析器生成的格式说明
python
print(output_parser.get_format_instructions())
核心输出为 JSON Schema:
json
{
"properties": {
"film_name": {
"description": "电影的名字",
"type": "string"
},
"author_name": {
"description": "电影的导演",
"type": "string"
},
"genres": {
"description": "电影的题材",
"type": "array",
"items": {"type": "string"}
}
},
"required": ["film_name", "author_name", "genres"]
}
这份 Schema 同时说明了字段名称、字段类型和必填字段。我们不需要手写整段格式要求,解析器会根据 Pydantic 模型自动生成。
5. 构建电影信息提取模板
完整代码
python
prompt_template = ChatPromptTemplate.from_messages([
(
"system",
"{parser_instructions} 你输出的结果请使用中文。"
),
(
"human",
"请你帮我从电影概述中,提取电影名、导演,以及电影的体裁。"
"电影概述会被三个#符号包围。\n###{film_introduction}###"
)
])
这次系统消息中既包含结构要求,也要求使用中文输出。
人类消息中的 ### 是一种提示词分隔符,用来帮助模型区分"任务说明"和"待处理的电影简介"。
6. 填充电影简介并构造提示词
完整代码
python
film_introduction = """
《复仇者联盟4:终局之战》是由安东尼·罗素和乔·罗素
联合执导,小罗伯特·唐尼、克里斯·埃文斯等主演的动作科幻片。
该片改编自漫威漫画,讲述复仇者联盟剩余成员再次集结,
利用时间装置穿越时空、重新创造希望的故事。
"""
final_prompt = prompt_template.invoke({
"film_introduction": film_introduction,
"parser_instructions": output_parser.get_format_instructions()
})
7. 调用模型获得 JSON 文本
完整代码
python
model = ChatOpenAI(
model="qwen-plus",
openai_api_key=os.getenv("DASHSCOPE_API_KEY"),
openai_api_base=os.getenv(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1"
)
)
response = model.invoke(final_prompt)
print(response.content)
输出
json
{
"film_name": "复仇者联盟4:终局之战",
"author_name": "安东尼·罗素和乔·罗素",
"genres": ["动作", "科幻"]
}
关键认知 :这段输出已经符合 JSON 外观,但 response.content 依然只是字符串。
一个常见误区是:看到大括号就认为结果已经是 Python 字典。实际上,"长得像 JSON 的字符串"和"程序中的结构化对象"并不是一回事。
8. 解析并校验为 FilmInfo 对象
完整代码
python
result = output_parser.invoke(response)
print(result)
输出
text
FilmInfo(
film_name='复仇者联盟4:终局之战',
author_name='安东尼·罗素和乔·罗素',
genres=['动作', '科幻']
)
此时 result 已经是 FilmInfo 对象,可以直接通过属性访问数据:
python
print(result.film_name)
print(result.genres)
输出:
text
复仇者联盟4:终局之战
['动作', '科幻']
与手动处理 JSON 字符串相比,这种方式不但完成了解析,还检查了字段是否存在、数据类型是否符合定义。
四、两种解析器对比
对比总览
| 对比项 | 列表解析器 | Pydantic 解析器 |
|---|---|---|
| 核心类 | CommaSeparatedListOutputParser |
PydanticOutputParser |
| 结果结构 | 一组同类元素 | 多个命名字段 |
| 类型约束 | 较弱 | 较强(由 Pydantic 模型定义) |
| 使用难度 | 简单 | 稍高 |
| 典型结果类型 | list[str] |
BaseModel 子类对象 |
| 适合场景 | 列举、推荐、标签 | 信息抽取、接口数据、入库 |
适用场景说明
CommaSeparatedListOutputParser 适合结构简单、每个元素含义相同的结果,例如:
- 城市名称列表
- 商品关键词列表
- 推荐书单
- 标签列表
PydanticOutputParser 适合字段含义不同、类型明确的业务数据,例如:
- 电影信息(名称、导演、题材)
- 商品数据(名称、价格、库存)
- 简历字段(姓名、经验、技能列表)
- 文章元数据(标题、作者、标签、发布时间)
五、常见问题与解决方法
问题1:为什么模型已经输出 JSON,解析器仍然报错
常见原因:
- 模型在 JSON 前后增加了解释文字
- 使用了 Markdown 代码块包围 JSON(如
json ...) - 缺少必填字段
- 字段名与 Pydantic 模型不一致
- 字段类型错误,例如把列表写成普通字符串
解决方法:
先查看模型原始回答:
python
print(response.content)
不要一上来只检查解析器。很多解析失败,本质上是模型没有严格遵守输出格式。
问题2:如何提高解析成功率
改进方向:
- 把
get_format_instructions()放入系统消息 - 明确要求不要添加解释、前言和 Markdown 代码块
- 减少互相矛盾的提示词
- 为字段提供清楚的描述和示例
- 对失败请求增加重试或修复机制
例如,可以在系统提示中增加:
text
请严格按照指定结构输出,不要添加任何额外解释。
问题3:为什么不能把 API Key 直接写进代码
课堂实践中我们已经把密钥保存到环境变量,因此代码只负责读取:
python
api_key = os.getenv("DASHSCOPE_API_KEY")
这样做可以避免把密钥上传到 GitHub、CSDN 或共享项目中。
问题4:公共地址和专属地址应该使用哪个
如果使用普通的阿里云百炼 OpenAI 兼容接口,可以使用公共地址:
text
https://dashscope.aliyuncs.com/compatible-mode/v1
如果账号页面提供了业务空间专属域名,则应使用自己账号对应的完整地址,不要复制他人的专属地址。
六、从解析器走向 Chain
当前代码的调用方式
本文为了讲清每一步,把提示词、模型调用和输出解析分开执行:
text
prompt_template.invoke()
↓
model.invoke()
↓
output_parser.invoke()
Chain 方式的简化写法
LangChain 还可以使用管道符把它们连接起来:
python
chain = prompt_template | model | output_parser
之后只需要调用一次:
python
result = chain.invoke({
"subject": "中国",
"parser_instructions": parser_instructions
})
这种写法会把多个可运行组件组合成一条 Chain。它正是后续要讲解的重点。
新版 LangChain 的结构化输出
本文采用课堂锁定环境中的 PydanticOutputParser,便于理解结构化输出的底层流程。
在新版 LangChain 中,部分模型还支持 with_structured_output(),Agent 也可以根据模型能力采用原生结构化输出或工具调用策略。但不管接口如何变化,核心思想仍然相同:先定义数据结构,再让模型按照结构返回,最后对结果进行验证。
七、总结
本文完成了两个由浅入深的课堂案例:
第一个案例 使用 CommaSeparatedListOutputParser,将模型返回的逗号分隔字符串转换成 Python 列表。
第二个案例 使用 Pydantic 定义 FilmInfo 数据结构,再通过 PydanticOutputParser 提取并验证电影名称、导演和题材。
最值得记住的是下面这条完整思路:
text
定义目标结构
↓
生成格式说明(get_format_instructions)
↓
将格式说明加入 Prompt
↓
调用大模型
↓
解析并验证输出(output_parser.invoke)
↓
交给后续 Python 程序使用
学会 Output Parser 后,大模型不再只是返回一段供人阅读的文本,而是可以稳定地向程序提供列表、对象和字段明确的数据。这也是构建 RAG、Agent、信息抽取和自动化工作流的重要基础。
参考资料
版权说明
本文为原创技术文章,转载请注明出处。
文中代码均经过实际测试,可根据项目需求进行修改与扩展。
如文章存在疏漏或错误,欢迎在评论区交流讨论。