4、输出解析器(Output Parsers):PydanticOutputParser、CommaSeparatedListOutputParser、自定义解析器
聊完了 Prompt 和 LLM 的调用,接下来我们得面对一个很现实的问题:大模型吐出来的东西,怎么变成我们程序能用的数据?
你想想看,LLM 本质上就是个文本生成器。你问它「给我三个 Python 库的名字」,它可能回你一句:「好的,以下是三个常用的 Python 库:NumPy、Pandas、Matplotlib。」
这句子写得挺通顺,但对程序来说,这就是一堆字符串。你想把它塞进一个列表里?还得自己写正则去匹配「:」后面的内容,再按顿号切分。我刚开始做 LangChain 项目时,就吃过这个亏。那时候图省事,直接让 LLM 返回 JSON 字符串,然后 json.loads() 一把梭。结果呢?模型偶尔多打了个逗号,或者把 key 的引号写成了中文全角,整个程序就崩了。
所以,输出解析器(Output Parser) 就是来解决这个问题的。它的职责很明确:把 LLM 输出的原始文本,转换成我们指定的数据结构。LangChain 内置了几种好用的解析器,咱们一个一个来看。
4.1 PydanticOutputParser:结构化数据的首选
我个人习惯,只要是需要从 LLM 输出中提取多个字段的场景,比如信息抽取、实体识别、结构化问答,我都会优先考虑 PydanticOutputParser。它基于 Pydantic 模型,能帮你定义好输出的 schema,并且自带校验逻辑。
举个例子,假设我们要让 LLM 提取一条新闻的标题、作者和发布时间:
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import Optional
# 1. 定义你的输出结构
class NewsArticle(BaseModel):
title: str = Field(description="新闻的标题")
author: Optional[str] = Field(description="新闻的作者,如果未知则为空")
publish_date: Optional[str] = Field(description="新闻的发布日期,格式为 YYYY-MM-DD")
# 2. 创建解析器
parser = PydanticOutputParser(pydantic_object=NewsArticle)
# 3. 获取格式指令(这个很重要!)
format_instructions = parser.get_format_instructions()
print(format_instructions)
这里有个关键点:get_format_instructions() 方法会自动生成一段提示词,告诉 LLM 应该以什么格式返回数据。你只需要把这个指令拼到你的 Prompt 里就行。
核心思路: 解析器不直接修改 LLM 的输出,而是通过「格式指令」引导 LLM 输出符合预期的文本,然后再由解析器进行解析和校验。
我曾经在一个知识库问答项目中,需要从用户问题中提取「实体」和「意图」两个字段。一开始我手动写 Prompt 让 LLM 返回 JSON,结果总是有 5% 左右的请求因为格式问题解析失败。换成 PydanticOutputParser 后,失败率直接降到了 0.5% 以下。为什么?因为格式指令里包含了详细的示例和约束,LLM 更容易理解你的要求。
小技巧: 如果你的 Pydantic 模型字段有复杂的校验逻辑(比如正则匹配、数值范围),解析器会在解析时自动校验。如果 LLM 返回的数据不合法,它会抛出 OutputParserException,方便你进行重试或降级处理。
4.2 CommaSeparatedListOutputParser:简单场景的利器
有些场景其实没那么复杂。比如你只是想让 LLM 生成一个关键词列表,或者列举几个选项。这时候用 PydanticOutputParser 就有点「杀鸡用牛刀」了。
CommaSeparatedListOutputParser 就是为这种场景准备的。它期望 LLM 返回一个用逗号分隔的列表,然后帮你转成 Python 的 List[str]。
from langchain.output_parsers import CommaSeparatedListOutputParser
from langchain.prompts import PromptTemplate
parser = CommaSeparatedListOutputParser()
format_instructions = parser.get_format_instructions()
prompt = PromptTemplate(
template="列出 5 个常用的数据可视化库。\n{format_instructions}",
input_variables=[],
partial_variables={"format_instructions": format_instructions}
)
# 假设 LLM 返回了:"Matplotlib, Seaborn, Plotly, Bokeh, Altair"
result = parser.parse("Matplotlib, Seaborn, Plotly, Bokeh, Altair")
print(result) # ['Matplotlib', 'Seaborn', 'Plotly', 'Bokeh', 'Altair']
嗯,这里要注意一点:LLM 有时候会不听话。比如它可能返回带编号的列表:「1. Matplotlib, 2. Seaborn...」。这时候解析器会直接报错。我的建议是,在 Prompt 里明确强调「只返回逗号分隔的文本,不要编号,不要额外说明」。如果还是不行,可以考虑用 StructuredOutputParser 或者干脆写个自定义解析器。
4.3 自定义解析器:掌控一切
内置解析器虽然方便,但总有覆盖不到的场景。比如你希望 LLM 返回一个 Markdown 表格,然后解析成 Pandas DataFrame;或者你需要处理一些特殊的格式,像 XML、YAML。
这时候就得自己动手了。LangChain 的 BaseOutputParser 抽象类,只需要你实现两个方法:
parse(self, text: str) -> T:核心方法,把文本解析成你想要的类型。get_format_instructions(self) -> str:返回格式指令,告诉 LLM 怎么输出。
来看一个实际例子。我之前做过一个项目,需要 LLM 输出「关键词-权重」的键值对,每行一个。比如:
Python: 0.95
机器学习: 0.88
深度学习: 0.82
内置解析器搞不定这个,于是我写了个自定义的:
from langchain.schema import BaseOutputParser
from typing import Dict
class KeyValueOutputParser(BaseOutputParser[Dict[str, float]]):
def parse(self, text: str) -> Dict[str, float]:
lines = text.strip().split('\n')
result = {}
for line in lines:
if ':' not in line:
continue
key, value = line.split(':', 1)
key = key.strip()
try:
value = float(value.strip())
except ValueError:
# 如果转换失败,就跳过这一行
continue
result[key] = value
return result
def get_format_instructions(self) -> str:
return "请按以下格式输出:每行一个键值对,键和值用英文冒号分隔。例如:\n关键词1: 0.95\n关键词2: 0.88"
避坑指南: 我曾经在自定义解析器里忘了处理 LLM 输出中的空行和前后空格,结果解析出来的字典里全是空字符串 key。后来我加上了 .strip() 和 continue 逻辑才搞定。所以,写解析器时一定要对 LLM 的输出做「防御性编程」——假设它可能给你任何乱七八糟的东西。
4.4 如何选择合适的解析器?
说了这么多,到底该用哪个?我整理了一个简单的对照表:
| 场景 | 推荐解析器 | 说明 |
|---|---|---|
| 需要提取多个字段,且字段有类型约束 | PydanticOutputParser | 最推荐,自带校验和错误提示 |
| 只需要一个简单的列表 | CommaSeparatedListOutputParser | 轻量级,但 LLM 容易跑偏 |
| 需要返回固定结构的 JSON | StructuredOutputParser | 比 Pydantic 轻量,但校验弱一些 |
| 格式特殊,或需要后处理 | 自定义解析器 | 灵活度最高,但需要自己写逻辑 |
最后说一句,输出解析器不是银弹。它解决的是「格式规范化」的问题,但解决不了「内容质量」的问题。如果 LLM 本身理解错了你的意图,解析器再强也救不了。所以,好的 Prompt 设计 + 合适的解析器,才是企业级应用的黄金搭档。