🔧 输出解析器

模型输出的是 AIMessage(内容是字符串),但我们的业务通常需要结构化数据——Python 对象、列表、JSON。输出解析器(Output Parser)负责这道"最后一公里"的转换。

本章目标

  • 理解 BaseOutputParser 的抽象,以及它为何是 Runnable
  • 掌握 StrOutputParser(最常用)、JsonOutputParserPydanticOutputParser
  • 学会用解析器给模型提供"格式指令"(format instructions)
  • 为 LC5 的 LCEL 管道(prompt | model | parser)准备好最后一块积木

为什么需要解析器

看模型"裸输出"的痛点:

📄 痛点:拿不到想要的数据形态 python
# 模型返回的是 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

📄 langchain_core/output_parsers/base.py:140 · 解析器基类 python
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 ""
💡 关键认知:parser 是 Runnable

解析器继承 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 取出来。源码极简:

📄 langchain_core/output_parsers/string.py · StrOutputParser python
class StrOutputParser(BaseTransformOutputParser):
    """解析器,把 AIMessage/Generation 的文本取出来。"""

    def parse(self, text: str) -> str:
        return text
    # 配合 _transform 方法,从 AIMessageChunk 流中提取 .content

PydanticOutputParser:带格式指令

更强大的是 PydanticOutputParser——它不仅解析,还能告诉模型应该输出什么格式。这个 get_format_instructions() 是它的杀手锏:

📄 lc4_parsers.py · PydanticOutputParser 的格式指令 python
# 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 对象
parser get_format_ instructions() prompt 注入格式说明 model 按格式输出 parser.parse() → Pydantic 对象 ① 先生成格式 ② 喂给模板 ③ 模型照做 ④ 解析回来
图 LC4.1 · PydanticOutputParser 既是"出题人"又是"阅卷人"
📌 与 with_structured_output 的关系

回忆 LC3 的 with_structured_output——它内部其实就是"绑工具 + 用 PydanticToolsParser 解析"的封装。现代开发优先用 with_structured_output(更简洁、利用模型原生工具能力),但理解解析器原理对调试和自定义仍有价值。

可运行代码

🚀 两种模式

① 纯本地:用伪造数据演示解析逻辑(无需 API)。② 真实调用:接模型看完整效果。先看本地版。

📄 lc4_parsers.py · 解析器实操(本地版 + 管道版) python
# 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 工具

小结

下一章 · 核心 ★

下一章 LC5 · LCEL 核心原理——本教程的核心转折章!我们已经集齐了消息、提示、模型、解析器四块积木,LCEL 就是把它们用 | 管道串联、统一调度的"语法糖"。理解了 LCEL,你就掌握了 LangChain 的灵魂。