🔧 输出解析器
模型输出的是 AIMessage(内容是字符串),但我们的业务通常需要结构化数据——Python 对象、列表、JSON。输出解析器(Output Parser)负责这道"最后一公里"的转换。
本章目标
- 理解
BaseOutputParser的抽象,以及它为何是 Runnable - 掌握
StrOutputParser(最常用)、JsonOutputParser、PydanticOutputParser - 学会用解析器给模型提供"格式指令"(format instructions)
- 为 LC5 的 LCEL 管道(
prompt | model | parser)准备好最后一块积木
为什么需要解析器
看模型"裸输出"的痛点:
# 模型返回的是 AIMessage 对象,内容是字符串
resp = model.invoke("用JSON描述一个人")
print(type(resp)) # → AIMessage
print(type(resp.content))# → str,是 '```json\n{"name":"张三"}\n```' 这种
# 想拿到 dict?得自己 strip 掉 markdown、json.loads、处理异常...很繁琐
解析器把这套繁琐封装成可复用的 Runnable。每个解析器都实现一个核心方法 parse,把字符串变成目标类型。
BaseOutputParser
class BaseOutputParser(BaseLLMOutputParser, RunnableSerializable):
"""所有输出解析器的基类。
本身是个 Runnable —— 输入 AIMessage/str,输出解析后的目标类型。
所以可以直接放在管道末端:prompt | model | parser
"""
def parse(self, text: str) -> Any:
"""核心方法:把字符串解析成目标类型。子类必须实现。"""
raise NotImplementedError
def parse_result(self, result, *, partial=False):
"""解析 GenerationResult,默认实现是调 parse。"""
return self.parse(result.generations[0].text)
def get_format_instructions(self) -> str:
"""返回给模型的"格式说明",告诉模型该怎么输出。
例如 PydanticOutputParser 会生成 schema 描述。"""
return ""
解析器继承 RunnableSerializable,所以它本身可以放在管道里:chain = prompt | model | parser。这正是 LCEL 的核心模式(LC5 详解)。parser 接收 model 的输出,吐出最终结果。
常用解析器
| 解析器 | 源码 | 输入 | 输出 | 典型场景 |
|---|---|---|---|---|
StrOutputParser |
output_parsers/string.py | AIMessage | str | 最常用,直接取 .content |
JsonOutputParser |
output_parsers/json.py | AIMessage | dict | 把模型输出的 JSON 字符串解析成字典 |
PydanticOutputParser |
output_parsers/pydantic.py:19 | AIMessage | Pydantic 对象 | 解析成带类型校验的对象 |
CommaSeparatedListOutputParser |
output_parsers/list.py | AIMessage | list[str] | 逗号分隔转列表 |
XMLOutputParser |
output_parsers/xml.py | AIMessage | dict | 解析 XML(Claude 系偏好) |
StrOutputParser:最常用
90% 的简单场景用这个就够了——它就是把 AIMessage 的 .content 取出来。源码极简:
class StrOutputParser(BaseTransformOutputParser):
"""解析器,把 AIMessage/Generation 的文本取出来。"""
def parse(self, text: str) -> str:
return text
# 配合 _transform 方法,从 AIMessageChunk 流中提取 .content
PydanticOutputParser:带格式指令
更强大的是 PydanticOutputParser——它不仅解析,还能告诉模型应该输出什么格式。这个 get_format_instructions() 是它的杀手锏:
# pip install langchain
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
class Recipe(BaseModel):
name: str = Field(description="菜名")
ingredients: list[str] = Field(description="所需食材")
steps: list[str] = Field(description="制作步骤")
parser = PydanticOutputParser(pydantic_object=Recipe)
# 关键:生成给模型的格式说明
instructions = parser.get_format_instructions()
print(instructions)
# 输出(节选):
# "The output should be formatted as a JSON instance that conforms
# to the JSON schema below.
# {"properties": {"name": {...}, "ingredients": {...}, "steps": {...}},
# "required": [...], "title": "Recipe"}
# ..."
# 把这段说明塞进 prompt 的变量里,模型就会按格式输出
# 然后 parser.parse() 把字符串变成 Recipe 对象
回忆 LC3 的 with_structured_output——它内部其实就是"绑工具 + 用 PydanticToolsParser 解析"的封装。现代开发优先用 with_structured_output(更简洁、利用模型原生工具能力),但理解解析器原理对调试和自定义仍有价值。
可运行代码
① 纯本地:用伪造数据演示解析逻辑(无需 API)。② 真实调用:接模型看完整效果。先看本地版。
# pip install langchain
from langchain_core.output_parsers import (
StrOutputParser, JsonOutputParser, PydanticOutputParser,
CommaSeparatedListOutputParser,
)
from langchain_core.messages import AIMessage
from pydantic import BaseModel, Field
# ============ ① StrOutputParser:取 .content ============
str_parser = StrOutputParser()
result = str_parser.invoke(AIMessage(content="你好"))
print("Str:", repr(result)) # → '你好' (是 str,不是 AIMessage)
# ============ ② JsonOutputParser:解析 JSON 字符串 ============
json_parser = JsonOutputParser()
# 模型常返回带 markdown 代码块的 JSON,解析器会处理
result = json_parser.invoke(AIMessage(content='```json\n{"a": 1, "b": 2}\n```'))
print("Json:", result, type(result)) # → {'a': 1, 'b': 2} <class 'dict'>
# ============ ③ 列表解析器 + 格式指令 ============
list_parser = CommaSeparatedListOutputParser()
print("指令:", list_parser.get_format_instructions())
# → "Your response should be a list of comma separated values..."
result = list_parser.invoke(AIMessage(content="苹果, 香蕉, 橘子"))
print("List:", result) # → ['苹果', '香蕉', '橘子']
# ============ ④ PydanticOutputParser 完整流程 ============
class Movie(BaseModel):
title: str = Field(description="电影名")
rating: float = Field(description="评分 0-10")
pyd_parser = PydanticOutputParser(pydantic_object=Movie)
# 看 format_instructions
print("\n格式指令:", pyd_parser.get_format_instructions()[:80], "...")
# 模拟模型按格式输出
fake_output = AIMessage(content='{"title": "盗梦空间", "rating": 9.3}')
movie = pyd_parser.invoke(fake_output)
print("Movie:", movie, type(movie)) # → Movie(title='盗梦空间', rating=9.3)
# ============ ⑤ 管道模式(LC5 预告)============
# pip install langchain-openai
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
model = ChatOpenAI(model="gpt-4o-mini")
prompt = ChatPromptTemplate.from_messages([
("system", "你是电影专家。{format_instructions}"),
("human", "推荐一部{genre}电影"),
])
# prompt | model | parser —— 完整链(LC5 详解这个 | 语法)
chain = prompt | model | pyd_parser
result = chain.invoke({
"genre": "科幻",
"format_instructions": pyd_parser.get_format_instructions(),
})
print("管道结果:", result, type(result)) # 直接拿到 Movie 对象
|prompt | model | parser 这种写法就是 LCEL(LangChain Expression Language)。现在你已经集齐了三块积木(prompt、model、parser),它们都能用 | 串起来。下一章 LC5 就是把这件事彻底讲透——为什么 | 能用、背后是什么、能玩出多少花样。
与生产实践对照
| LangChain 概念 | OpenCode 对应 | |
|---|---|---|
| 解析 AIMessage 成结构化数据 | → | 无独立解析层(直接消费流事件,processor.ts 实时落库) |
| PydanticOutputParser schema 校验 | → | 工具参数 Schema 校验(tool.ts 的 wrap,InvalidArgumentsError) |
| 结构化输出 | → | createStructuredOutputTool 动态注入 JSON 工具 |
小结
- 解析器把模型的字符串输出变成目标类型(str / dict / Pydantic / list)。
StrOutputParser最常用(取 .content);PydanticOutputParser既能解析又能给模型发格式指令。- 解析器本身是 Runnable,能放在管道末端——这就引出了下一章的 LCEL。
下一章 LC5 · LCEL 核心原理——本教程的核心转折章!我们已经集齐了消息、提示、模型、解析器四块积木,LCEL 就是把它们用 | 管道串联、统一调度的"语法糖"。理解了 LCEL,你就掌握了 LangChain 的灵魂。