输出解析器 (Output Parser)
目录+
概述
LLM 的原始输出永远是一段字符串(AIMessage)。输出解析器(Output Parser)负责把这段字符串解析为程序可直接使用的结构化数据(字符串、列表、字典、日期、对象等)。
它通常承担两个职责:
- 格式约束:通过
get_format_instructions()生成一段格式说明,拼进 Prompt 告诉模型"按什么格式输出"。 - 解析输出:通过
parse()/invoke()把模型返回的文本转成目标数据类型。
在 LCEL 链式写法中,解析器放在最后,chain.invoke() 的返回值就直接是解析后的结果,无需手动调用 parse():
chain = prompt | llm | parser
常见类型:StrOutputParser、CommaSeparatedListOutputParser、JsonOutputParser、DatetimeOutputParser、XMLOutputParser、PydanticOutputParser 等。
两类解析器的区别
- 只解析、不约束格式:如
StrOutputParser、JsonOutputParser、CommaSeparatedListOutputParser,可以直接接在链尾。- 需要先约束格式:如
DatetimeOutputParser、PydanticOutputParser,必须先把get_format_instructions()注入 Prompt,模型才能输出可被正确解析的内容。
StrOutputParser
最简单的解析器:从 AIMessage 中提取出纯文本的 .content,返回字符串。常用于"只要回答文本"的场景,也能让链的输出统一为 str 而不是 AIMessage 对象。
将 ChatPromptTemplateChatPromptTemplateChatPromptTemplate 创建聊天消息列表,模板带有对应角色。常用的有 AIMessagePromptTemplate、SystemMessagePromptTemplate、HumanMessagePromptTemplate。
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate
from langchain_core.output_parsers import StrOutputParser
from models import DEEPSEEK_URL, DEEPSEEK_V4_PRO
import os
api_key = os.getenv("MY_DEEPSEEK_API_KEY")
base_url = DEEPSEEK_URL
model = DEEPSEEK_V4_PRO
llm = ChatOpenAI(api_key=api_key, base_u 中的测试代码更改为使用 StrOutputParser 解析器:
# 第3种格式
template = ChatPromptTemplate.from_template("请以下将{text} 翻译成英文")
message = template.invoke({"text": "我是程序员"})
# 真正调用大模型,返回 AI 的回答
aimessage = llm.invoke(message)
# 创建解析器
parser = StrOutputParser()
result = parser.invoke(aimessage)
print(result)
等价的链式写法:
chain = template | llm | StrOutputParser()
print(chain.invoke({"text": "我是程序员"}))
CommaSeparatedListOutputParser
把模型返回的"逗号分隔文本"自动解析成 Python 的 list(列表)。
核心作用
LLM 返回的永远是一段字符串。这个解析器帮你做两件事:
- 给提示词加格式说明:告诉模型"请用逗号分隔输出"。
- 解析输出:把
"苹果, 香蕉, 橙子"这样的字符串转成['苹果', '香蕉', '橙子'](自动 strip 每个元素的首尾空格)。
基本用法
from langchain.output_parsers import CommaSeparatedListOutputParser
template = ChatPromptTemplate.from_messages(
[("system", "你是一名专业的程序员"), ("user", "{input}")]
)
parser = CommaSeparatedListOutputParser()
chain = template | llm | parser
# print(chain.invoke({"input": "列出Python的三个主要版本, 用逗号分隔"}))
print(chain.invoke({"input": "列举三个常见的机器学习框架, 用逗号分隔"}))
# 输出: ['TensorFlow', 'PyTorch', 'scikit-learn']
规范做法:注入格式说明
上面靠在 prompt 里手写"用逗号分隔"并不稳妥。更可靠的方式是用 get_format_instructions() 把官方格式说明注入 Prompt:
parser = CommaSeparatedListOutputParser()
print(parser.get_format_instructions())
# Your response should be a list of comma separated values, eg: `foo, bar, baz`
template = ChatPromptTemplate.from_messages([
("system", "你是一个助手。\n{format_instructions}"),
("user", "{input}"),
]).partial(format_instructions=parser.get_format_instructions())
chain = template | llm | parser
局限:它只是按逗号
split。若某个元素本身含逗号(如"1,000"),会被错误拆分。这种结构化需求建议改用JsonOutputParser或PydanticOutputParser。
JsonOutputParser
把模型返回的 JSON 文本解析为 Python 的 dict / list。是最常用的结构化输出方式。
from langchain_core.output_parsers import JsonOutputParser
template = ChatPromptTemplate.from_messages(
[("system", "你是一名专业的程序员"), ("user", "{input}")]
)
parser = JsonOutputParser()
chain = template | llm | parser
result = chain.invoke(
{"input": "langchain是什么? 问题用question 回答用ans 返回一个JSON格式"}
)
print(result) # {'question': '...', 'ans': '...'}
print(type(result)) # <class 'dict'>
要点:
- 它能容错地从模型输出里提取 JSON(即使被
```json代码块包裹也能解析)。 - 支持流式输出:在 streaming 时会逐步返回正在累积的、合法的部分 JSON。
- 想约束字段结构时,可配合 Pydantic 模型:
JsonOutputParser(pydantic_object=MyModel),再把get_format_instructions()注入 Prompt。
DatetimeOutputParser
把模型返回的时间字符串解析为 Python 的 datetime 对象。必须先注入格式说明,模型才知道按什么日期格式回答。
# 定义模板格式
template = """
回答用户的问题:{question}
{format_instructions}
"""
# 使用日期时间解析器
output_parser = DatetimeOutputParser()
prompt = PromptTemplate.from_template(
template,
# partial 表示提前预置变量的值
partial_variables={"format_instructions": output_parser.get_format_instructions()},
)
print(prompt)
print("-" * 30)
print(output_parser.get_format_instructions())
print("-" * 30)
# 链式调用
chain = prompt | client | output_parser
output = chain.invoke({"question": "新中国是什么时候成立的?"})
# 打印输出
print(output) # 1949-10-01 00:00:00
print(type(output)) # <class 'datetime.datetime'>
要点:get_format_instructions() 会生成一段说明,要求模型严格按 %Y-%m-%dT%H:%M:%S.%fZ 这类格式输出;若不注入,模型可能返回"1949年10月1日"导致解析失败。
PydanticOutputParser
把模型输出解析为一个强类型的 Pydantic 对象,带字段校验。需要严格 schema、字段类型/约束时的首选。
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
class Book(BaseModel):
title: str = Field(description="书名")
author: str = Field(description="作者")
year: int = Field(description="出版年份")
parser = PydanticOutputParser(pydantic_object=Book)
prompt = PromptTemplate.from_template(
"提取书籍信息:{query}\n{format_instructions}",
partial_variables={"format_instructions": parser.get_format_instructions()},
)
chain = prompt | llm | parser
book = chain.invoke({"query": "《活着》是余华1993年的作品"})
print(book.title, book.author, book.year) # 活着 余华 1993
print(type(book)) # <class '__main__.Book'>
要点:解析失败时(模型没按 schema 输出)会抛异常,可配合 OutputFixingParser 自动让模型修正。
解析器对比
| 解析器 | 返回类型 | 是否需注入格式说明 | 适用场景 |
|---|---|---|---|
StrOutputParser |
str |
否 | 只要纯文本回答 |
CommaSeparatedListOutputParser |
list[str] |
建议 | 简单列表 |
JsonOutputParser |
dict / list |
可选 | 结构化数据、流式输出 |
DatetimeOutputParser |
datetime |
是 | 日期时间 |
PydanticOutputParser |
Pydantic 对象 | 是 | 强类型 + 字段校验 |
XMLOutputParser |
dict |
可选 | XML 格式输出 |
get_format_instructions() 统一机制
绝大多数解析器都实现了 get_format_instructions(),这是 LangChain 的统一约定:
- 它返回一段给模型看的格式说明文本;
- 把它通过
partial_variables或.partial()注入 Prompt; - 模型据此输出符合格式的文本,再由同一个解析器
parse()还原成数据。
prompt = PromptTemplate.from_template(
template,
partial_variables={"format_instructions": parser.get_format_instructions()},
)
这种"格式说明 + 解析"成对出现的设计,使得解析器既约束输入又负责输出,职责闭环。
content_blocks:跨模型的标准化内容块
content_blocks 在 LangChain 层将所有模型提供商异构的 API 响应,统一转换成标准化的内容块。让上层应用代码与底层具体模型实现解耦,无需为每个模型编写定制化的解析代码——也适用于工具调用结果。
from langchain_community.chat_models import ChatTongyi
from models import get_ali_model_client
qwen_model = ChatTongyi(model_name="qwen-max")
deepseek_model = get_ali_model_client()
qwen_response = qwen_model.invoke("你是谁")
deepseek_response = deepseek_model.invoke("你是谁")
# 不同模型,统一的解析方式
print(qwen_response.content_blocks)
print(deepseek_response.content_blocks)