Deep Read

输出解析器 (Output Parser)

概述

LLM 的原始输出永远是一段字符串(AIMessage)。输出解析器(Output Parser)负责把这段字符串解析为程序可直接使用的结构化数据(字符串、列表、字典、日期、对象等)。

它通常承担两个职责:

  1. 格式约束:通过 get_format_instructions() 生成一段格式说明,拼进 Prompt 告诉模型"按什么格式输出"。
  2. 解析输出:通过 parse() / invoke() 把模型返回的文本转成目标数据类型。

在 LCEL 链式写法中,解析器放在最后,chain.invoke() 的返回值就直接是解析后的结果,无需手动调用 parse():

chain = prompt | llm | parser

常见类型:StrOutputParserCommaSeparatedListOutputParserJsonOutputParserDatetimeOutputParserXMLOutputParserPydanticOutputParser 等。

两类解析器的区别

  • 只解析、不约束格式:如 StrOutputParserJsonOutputParserCommaSeparatedListOutputParser,可以直接接在链尾。
  • 需要先约束格式:如 DatetimeOutputParserPydanticOutputParser,必须先把 get_format_instructions() 注入 Prompt,模型才能输出可被正确解析的内容。

StrOutputParser

最简单的解析器:从 AIMessage 中提取出纯文本的 .content,返回字符串。常用于"只要回答文本"的场景,也能让链的输出统一为 str 而不是 AIMessage 对象。

ChatPromptTemplateChatPromptTemplateChatPromptTemplate 创建聊天消息列表,模板带有对应角色。常用的有 AIMessagePromptTemplate、SystemMessagePromptTemplate、HumanMessagePromptTemplate。 from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate from langchain_core.output_parsers import StrOutputParser from models import DEEPSEEK_URL, DEEPSEEK_V4_PRO import os api_key = os.getenv("MY_DEEPSEEK_API_KEY") base_url = DEEPSEEK_URL model = DEEPSEEK_V4_PRO llm = ChatOpenAI(api_key=api_key, base_u 中的测试代码更改为使用 StrOutputParser 解析器:

# 第3种格式
template = ChatPromptTemplate.from_template("请以下将{text} 翻译成英文")

message = template.invoke({"text": "我是程序员"})
# 真正调用大模型,返回 AI 的回答
aimessage = llm.invoke(message)

# 创建解析器
parser = StrOutputParser()
result = parser.invoke(aimessage)
print(result)

等价的链式写法:

chain = template | llm | StrOutputParser()
print(chain.invoke({"text": "我是程序员"}))

CommaSeparatedListOutputParser

把模型返回的"逗号分隔文本"自动解析成 Python 的 list(列表)

核心作用

LLM 返回的永远是一段字符串。这个解析器帮你做两件事:

  1. 给提示词加格式说明:告诉模型"请用逗号分隔输出"。
  2. 解析输出:把 "苹果, 香蕉, 橙子" 这样的字符串转成 ['苹果', '香蕉', '橙子'](自动 strip 每个元素的首尾空格)。

基本用法

from langchain.output_parsers import CommaSeparatedListOutputParser

template = ChatPromptTemplate.from_messages(
    [("system", "你是一名专业的程序员"), ("user", "{input}")]
)

parser = CommaSeparatedListOutputParser()
chain = template | llm | parser

# print(chain.invoke({"input": "列出Python的三个主要版本, 用逗号分隔"}))
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"),会被错误拆分。这种结构化需求建议改用 JsonOutputParserPydanticOutputParser


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()},
)
print(prompt)
print("-" * 30)
print(output_parser.get_format_instructions())
print("-" * 30)
# 链式调用
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)