Chapter 04

输出解析器

将 LLM 的非结构化文本输出转换为 Python 对象:StrOutputParser、Pydantic、JSON 与最新的 with_structured_output

为什么需要输出解析器?

LLM 的原始输出是一个 AIMessage 对象(包含 content 字符串)。在实际应用中,我们通常需要将这个字符串转换成结构化的 Python 数据——字典、列表、Pydantic 模型等——以便后续的业务逻辑处理。

输出解析器(Output Parser) 承担两个职责:

  1. 格式化指令注入:向 Prompt 中添加说明,告诉模型应该用什么格式输出(JSON、特定 schema 等)
  2. 输出转换:将模型的字符串输出解析为 Python 对象
2025 年最佳实践建议

对于支持 Function Calling / Tool Calling 的模型(GPT-4o、Claude 3.5+),优先使用 model.with_structured_output(schema) 而非手工解析 JSON 字符串。它通过原生 API 特性约束输出格式,可靠性更高,不会因为模型"多说了一句话"而解析失败。

StrOutputParser:最简单的解析器

AIMessage 中提取 content 字符串,是 99% 的 LCEL 链的收尾步骤:

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o-mini")
prompt = ChatPromptTemplate.from_template("解释:{concept}")

# 没有 StrOutputParser:返回 AIMessage 对象
chain_raw = prompt | model
result = chain_raw.invoke({"concept": "闭包"})
print(type(result))   # <class 'langchain_core.messages.AIMessage'>
print(result.content)

# 有 StrOutputParser:返回纯字符串
chain = prompt | model | StrOutputParser()
result = chain.invoke({"concept": "闭包"})
print(type(result))   # <class 'str'>
print(result)

JsonOutputParser:解析 JSON 输出

JsonOutputParser 要求模型输出合法的 JSON,并将其解析为 Python dict:

from langchain_core.output_parsers import JsonOutputParser

parser = JsonOutputParser()

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个数据提取助手,始终以 JSON 格式返回结果,不要有任何额外文字。"),
    ("human", """
从以下文本中提取信息,返回 JSON 格式:
{{"name": "姓名", "age": 年龄数字, "job": "职业"}}

文本:{text}
"""),
])

chain = prompt | model | parser

result = chain.invoke({
    "text": "张伟是一位35岁的软件工程师,在北京工作。"
})
print(result)         # {"name": "张伟", "age": 35, "job": "软件工程师"}
print(result["name"]) # 张伟

# JsonOutputParser 支持流式:边生成边解析(yield 部分 JSON)
for partial in chain.stream({"text": "李娜,28岁,设计师。"}):
    print(partial)  # 逐步构建的部分字典

PydanticOutputParser:类型安全的结构化输出

PydanticOutputParser 基于 Pydantic v2 模型定义输出 schema,自动生成格式化指令并在解析时做类型验证:

from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List

# 定义输出结构
class BookReview(BaseModel):
    title: str = Field(description="书名")
    rating: float = Field(description="评分,1到10", ge=1, le=10)
    pros: List[str] = Field(description="优点列表")
    cons: List[str] = Field(description="缺点列表")
    summary: str = Field(description="一句话总结")

parser = PydanticOutputParser(pydantic_object=BookReview)

# parser.get_format_instructions() 生成插入提示词的格式说明
print(parser.get_format_instructions()[:200])

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一位书评家。\n\n{format_instructions}"),
    ("human", "请为《{book_title}》写一篇书评。"),
]).partial(format_instructions=parser.get_format_instructions())

chain = prompt | model | parser

review = chain.invoke({"book_title": "三体"})
print(type(review))       # <class 'BookReview'> — Pydantic 对象!
print(review.title)       # 三体
print(review.rating)      # 9.5
print(review.pros)        # ['宏大的宇宙观', '独特的科学概念', ...]

with_structured_output:现代推荐方案

model.with_structured_output(schema) 是 LangChain 0.3.x 对支持原生工具调用的模型(GPT-4o、Claude 3.5+)提供的最可靠结构化输出方案。它通过模型原生的 Function Calling / Tool Calling 特性约束输出,不依赖文本解析,可靠性远高于 PydanticOutputParser。

from pydantic import BaseModel, Field
from typing import Literal

class SentimentResult(BaseModel):
    """情感分析结果"""
    sentiment: Literal["positive", "negative", "neutral"] = Field(
        description="情感倾向"
    )
    confidence: float = Field(description="置信度 0-1", ge=0, le=1)
    reason: str = Field(description="判断理由,一句话")

# 绑定结构化输出 schema
structured_model = model.with_structured_output(SentimentResult)

prompt = ChatPromptTemplate.from_template(
    "分析以下评论的情感:{review}"
)

chain = prompt | structured_model

result = chain.invoke({
    "review": "这家餐厅的菜很好吃,但服务有点慢。"
})
print(type(result))         # <class 'SentimentResult'>
print(result.sentiment)     # neutral
print(result.confidence)    # 0.7
print(result.reason)

with_structured_output 的 method 参数

# method="function_calling"(默认):通过工具调用约束输出
m1 = model.with_structured_output(SentimentResult, method="function_calling")

# method="json_mode":要求模型输出 JSON,再解析
# 适用于不支持工具调用的模型
m2 = model.with_structured_output(SentimentResult, method="json_mode")

# include_raw=True:同时返回原始 AIMessage 和解析结果
m3 = model.with_structured_output(SentimentResult, include_raw=True)
raw_result = m3.invoke("分析:产品质量不错,价格偏贵。")
print(raw_result["raw"])     # 原始 AIMessage
print(raw_result["parsed"]) # SentimentResult 对象

OutputFixingParser:解析失败自动修复

当模型偶尔输出格式不符合要求时,OutputFixingParser 会自动发起第二次 LLM 调用来修复输出:

from langchain.output_parsers import OutputFixingParser

base_parser = PydanticOutputParser(pydantic_object=BookReview)
fixing_parser = OutputFixingParser.from_llm(
    parser=base_parser,
    llm=model
)

# 即使模型输出有小错误(多余的注释、字段名错误等),也会自动修复
chain = prompt | model | fixing_parser
OutputFixingParser 的代价

OutputFixingParser 修复失败时会发起额外的 LLM 调用,增加延迟和成本。在生产环境中,优先选择 with_structured_output 从根本上避免解析失败,而不是依赖修复机制。

CommaSeparatedListOutputParser

解析逗号分隔的列表,是最轻量级的解析器之一:

from langchain_core.output_parsers import CommaSeparatedListOutputParser

parser = CommaSeparatedListOutputParser()
print(parser.get_format_instructions())
# "Your response should be a list of comma separated values, ..."

prompt = ChatPromptTemplate.from_messages([
    ("system", "{format_instructions}"),
    ("human", "列举5个关于{topic}的关键词"),
]).partial(format_instructions=parser.get_format_instructions())

chain = prompt | model | parser
result = chain.invoke({"topic": "机器学习"})
print(result)  # ['神经网络', '监督学习', '过拟合', '特征工程', '梯度下降']

各解析器对比总结

解析器输出类型可靠性适用场景
StrOutputParserstr极高所有需要纯文本输出的场景
JsonOutputParserdict简单 JSON,不需要严格 schema
PydanticOutputParserPydantic 对象需要类型验证,模型不支持工具调用
with_structured_outputPydantic/dict生产推荐,模型支持工具调用
CommaSeparatedListOutputParserlist[str]简单列表输出
OutputFixingParser包装其他对解析失败有容错需求

本章小结