LangChain - 输出解析器 (Output Parser)
目录+
一句话理解
大模型只会"说人话"——它返回的永远是一段文本字符串。但程序需要的是能直接用的数据(列表、字典、日期、对象)。输出解析器就是这两者之间的翻译官。
看个最直白的例子。你问模型"列出三个机器学习框架":
模型返回(字符串): "TensorFlow, PyTorch, scikit-learn"
这段文本人一看就懂,但对程序来说它只是一坨字符:不能遍历、取不出第二个元素。你得自己写 split(",")、再逐个 strip() 去空格……
解析器帮你把这步自动做掉:
解析后(Python 列表): ['TensorFlow', 'PyTorch', 'scikit-learn']
现在 result[0]、len(result)、for 循环都能直接用了。
它的两个职责:一前一后
解析器承担两个职责,放到调用模型的时间线上看就顺了:
①【调用模型之前】 get_format_instructions()
在提示词里加一句:"请用逗号分隔来回答"
目的:让模型按规定格式输出,否则它可能写成 "1. xx 2. xx"
↓ 模型回答(仍是字符串)
②【拿到回答之后】 parse()
把字符串翻译成 list / dict / datetime / 对象
关键在于:"约束格式(①)"是为了让"解析(②)"能成功——先告诉模型按什么格式说话,它说了,你再按那个格式翻译。这就是后文说的"职责闭环"。
在 LCEL 链式写法中,解析器放在最后,chain.invoke() 的返回值就直接是解析后的结果,无需手动调用 parse():
chain = prompt | llm | parser
并不是所有解析器都需要职责 ①:
- 只解析、不约束格式:如
StrOutputParser、JsonOutputParser、CommaSeparatedListOutputParser,可以直接接在链尾。- 必须先约束格式:如
DatetimeOutputParser、PydanticOutputParser,格式很严格,不先把get_format_instructions()注入 Prompt,模型就输出不出可被正确解析的内容。
StrOutputParser
最简单的解析器:从 AIMessage 中提取纯文本的 .content,返回字符串。用于"只要回答文本"的场景,也能让链的输出统一为 str 而不是 AIMessage 对象。
将 LangChain - 提示模板LangChain - 提示模板
为什么需要提示模板
直接给模型写死一段提示词有两个问题:① 提示词固定,无法根据不同输入灵活变化;② 复制粘贴拼字符串,难以复用和维护。
提示模板(Prompt Template) 就是带 {变量} 占位符的提示字符串。它接收原始用户输入,把变量填进去,生成一段「准备好发给模型」的提示词。好处是:清晰可读、可复用、易维护、参数化生成。
四种常见模板
| 模板 | 适用场景 |
| --- | --- |
| PromptTemplate | 单条字符串提示,最基础 |
| ChatPromptTemplate | 多角色对话提示(system / human / ai),聊天模型首选 |
| FewShotPromptTemplate | 给几个示例「教」模型怎么答 |
| 部分填充 partial | 先固定一部分变量,其余后续再填(不是独立模板,是通用能力) |
一、PromptTemplate
最基础的字符串模板:一段含 {变量} 的文本,填充后得到完整提示词。
1. 两种用法对比
模板本身只负责「把变量填进字符串」。填好之后既可以手动调用模型,也 中的测试代码改为使用 StrOutputParser:
template = ChatPromptTemplate.from_template("请将{text}翻译成英文")
message = template.invoke({"text": "我是程序员"})
# 真正调用大模型,返回 AI 的回答(AIMessage)
aimessage = llm.invoke(message)
# 用解析器取出纯文本
parser = StrOutputParser()
result = parser.invoke(aimessage)
print(result)
等价的链式写法:
chain = template | llm | StrOutputParser()
print(chain.invoke({"text": "我是程序员"}))
CommaSeparatedListOutputParser
把模型返回的"逗号分隔文本"自动解析成 Python 的 list(列表),这正是开头那个例子。
基本用法
from langchain.output_parsers import CommaSeparatedListOutputParser
template = ChatPromptTemplate.from_messages(
[("system", "你是一名专业的程序员"), ("user", "{input}")]
)
parser = CommaSeparatedListOutputParser()
chain = template | llm | parser
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()},
)
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)