一、简介
LangChain 的输出解析器(Output Parser)负责将大语言模型(LLM)生成的非结构化文本转换为结构化数据,使得下游应用能够可靠地处理模型输出。无论是提取 JSON、填充 Pydantic 对象,还是处理列表、日期,输出解析器都提供了统一的接口和丰富的内置实现,极大简化了 LLM 应用的开发。
1.1 为什么需要输出解析器?
LLM 本质上是一个文本生成器,其输出是自由格式的字符串。但在实际应用中,应用程序通常需要:
- 结构化数据:JSON、XML、Pydantic 对象
- 特定格式:日期、数字、枚举值
- 错误处理:模型输出不符合预期时优雅降级
- 流式解析:实时处理部分输出
- 自愈能力:自动修正格式错误
输出解析器解决了这些问题,提供:
- 类型安全:将字符串转换为 Python 对象
- 验证机制:确保输出符合业务规则
- 可组合性:与提示词模板、模型链式集成
- 流式支持:增量解析流式输出
1.2 核心工作流程
bash
用户输入
↓
Prompt Template + Parser Instructions(自动加入格式要求)
↓
LLM 输出结构化文本(JSON/XML/列表等)
↓
Output Parser 解析
↓
结构化结果(dict/Pydantic/list...)
1.3 核心抽象
所有输出解析器都继承自 BaseOutputParser,核心方法包括:
| 方法 | 作用 |
|---|---|
| parse(text: str) -> Any | 将原始文本解析为目标数据结构。 |
| parse_with_prompt(text: str, prompt: PromptValue) -> Any | 可选方法,在解析时使用提示信息辅助(如用于错误恢复)。 |
| get_format_instructions() -> str | 返回格式说明,通常嵌入到提示模板中,告诉模型如何输出。 |
python
from langchain_core.output_parsers import BaseOutputParser
from typing import Any, List
class BaseOutputParser(ABC):
@abstractmethod
def parse(self, text: str) -> Any:
"""解析完整文本为结构化对象"""
pass
def parse_with_prompt(self, completion: str, prompt: PromptValue) -> Any:
"""带提示上下文的解析(用于错误恢复)"""
pass
def get_format_instructions(self) -> str:
"""返回格式说明,用于注入到提示词中"""
pass
二、内置输出解析器分类
2.1 字符串解析器
2.1.1 StrOutputParser - 最简单的解析器
最简单的解析器,直接返回字符串,不做任何处理。常用于链中提取模型输出的文本。
python
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
model = ChatOpenAI()
parser = StrOutputParser()
chain = model | parser
result = chain.invoke("Hello")
print(result) # 直接输出模型返回的字符串
2.1.2 CommaSeparatedListOutputParser - 逗号分隔列表
将模型输出的逗号分隔列表解析为 Python 列表。
python
from langchain.output_parsers import CommaSeparatedListOutputParser
parser = CommaSeparatedListOutputParser()
format_instructions = parser.get_format_instructions()
# 输出:"Your response should be a list of comma separated values, eg: `foo, bar, baz`"
# 解析示例
result = parser.parse("苹果, 香蕉, 橙子")
print(result) # ['苹果', '香蕉', '橙子']
2.2 结构化数据解析器
2.2.1 PydanticOutputParser - Pydantic 模型解析
最强大的解析器之一。通过定义 Pydantic 模型,解析器会指示模型生成符合该模型的 JSON,并自动解析为 Pydantic 对象。
python
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List
# 定义数据模型
class MovieReview(BaseModel):
title: str = Field(description="电影名称")
rating: float = Field(description="评分,1-10分")
pros: List[str] = Field(description="优点列表")
cons: List[str] = Field(description="缺点列表")
summary: str = Field(description="一句话总结")
# 创建解析器
parser = PydanticOutputParser(pydantic_object=MovieReview)
# 获取格式说明
format_instructions = parser.get_format_instructions()
print(format_instructions)
# 输出JSON格式说明
# 创建提示词
from langchain_core.prompts import PromptTemplate
prompt = PromptTemplate(
template="分析电影:{movie_name}\n{format_instructions}\n",
input_variables=["movie_name"],
partial_variables={"format_instructions": format_instructions}
)
# 使用
model = ChatOpenAI()
chain = prompt | model | parser
result = chain.invoke({"movie_name": "肖申克的救赎"})
print(result.title) # 访问结构化数据
print(result.rating)
print(result.pros[0])
2.2.2 JsonOutputParser - JSON 解析器
比 PydanticOutputParser 更轻量,只要求输出合法的 JSON,返回 Python 字典。
python
from langchain.output_parsers import JsonOutputParser
parser = JsonOutputParser()
# 在提示词中使用
from langchain_core.prompts import PromptTemplate
prompt = PromptTemplate(
template="返回JSON格式:{{'name': '{{name}}', 'age': {{age}}}}\n{format_instructions}",
partial_variables={"format_instructions": parser.get_format_instructions()}
)
# 解析
result = parser.parse('{"name": "张三", "age": 30}')
print(result["name"]) # 张三
2.3 日期时间解析器
2.3.4 DatetimeOutputParser - 日期解析
将模型输出的日期时间字符串解析为 Python datetime 对象。
python
from langchain.output_parsers import DatetimeOutputParser
parser = DatetimeOutputParser()
# 解析
result = parser.parse("2024-03-28 15:30:00")
print(result) # datetime.datetime(2024, 3, 28, 15, 30)
2.4 枚举与选择解析器
2.4.1 EnumOutputParser - 枚举值解析
限制输出必须是预定义的枚举值。
python
from langchain.output_parsers import EnumOutputParser
from enum import Enum
class Sentiment(Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
NEUTRAL = "neutral"
parser = EnumOutputParser(enum=Sentiment)
result = parser.parse("positive")
print(result) # Sentiment.POSITIVE
2.5 高级解析器特性
2.5.1 OutputFixingParser - 自动修复格式错误
当基础解析器失败时,调用另一个 LLM 来修复输出。
python
from langchain.output_parsers import OutputFixingParser
from langchain_openai import ChatOpenAI
# 原始解析器
original_parser = PydanticOutputParser(pydantic_object=MovieReview)
# 带修复能力的解析器
fixing_parser = OutputFixingParser.from_llm(
parser=original_parser,
llm=ChatOpenAI(model="gpt-3.5-turbo") # 用于修复的模型
)
# 即使模型输出格式有误,也能尝试修复
try:
result = fixing_parser.parse("一些格式错误的输出")
except Exception as e:
print(f"修复失败: {e}")
2.5.2 RetryOutputParser - 自动重试
解析失败时,向模型发送更详细的指令要求重新生成。
python
from langchain.output_parsers import RetryOutputParser
from langchain_openai import ChatOpenAI
retry_parser = RetryOutputParser.from_llm(
parser=original_parser,
llm=ChatOpenAI(),
max_retries=3 # 最大重试次数
)
# 在链中使用
from langchain_core.runnables import RunnableLambda
chain = prompt | model | RunnableLambda(retry_parser.parse_with_prompt)
2.5.3 其他专用解析器
- PandasDataFrameOutputParser:将模型输出解析为 pandas DataFrame(通常用于分析任务)。
- XMLOutputParser:将输出解析为 XML 结构。
- MarkdownListOutputParser:解析 Markdown 列表(如 - item)为列表。
- MarkdownHeaderTextSplitter:虽然不是严格意义上的输出解析器,但常用于分割 Markdown。
三、自定义输出解析器
如果内置解析器不满足需求,可以继承 BaseOutputParser 并实现 parse 方法。为了获得最佳实践,可以同时提供 get_format_instructions。其核心要点主要有以下3点:
- 继承 BaseOutputParser[T]
- 实现 parse() 方法(处理字符串)
- 可选实现 get_format_instructions()(告诉模型输出格式)
3.1 最简单的自定义解析器(字符串处理)
功能:把 "姓名:张三,年龄:25" 解析成字典
python
from langchain.schema import BaseOutputParser
from typing import Dict
# 自定义解析器
class MyParser(BaseOutputParser[Dict]):
# 必须实现:解析逻辑
def parse(self, output: str) -> Dict:
# 在这里写任意清洗逻辑
output = output.strip()
name = output.split("姓名:")[1].split(",")[0].strip()
age = output.split("年龄:")[1].strip()
return {
"name": name,
"age": int(age)
}
# 可选:告诉模型输出格式
def get_format_instructions(self) -> str:
return "请按格式输出:姓名:xx,年龄:xx"
# 使用
parser = MyParser()
result = parser.parse("姓名:张三,年龄:25")
print(result) # {'name': '张三', 'age': 25}
3.2 最常用 → JSON 自定义解析器
自带的 JSON 解析器很容易报错,自定义一个容错超强的版本:
python
import json
import re
from langchain.schema import BaseOutputParser
class JsonParser(BaseOutputParser[dict]):
def parse(self, output: str) -> dict:
# 第一步:清理文本,提取 {} 内容
json_str = re.search(r"\{.*\}", output, re.DOTALL)
if not json_str:
raise ValueError("未找到JSON")
# 第二步:加载
return json.loads(json_str.group())
def get_format_instructions(self) -> str:
return """
请输出JSON格式:
{"name": "姓名", "age": 年龄}
"""
# 使用
parser = JsonParser()
res = parser.parse("好的,结果是:{\"name\":\"张三\",\"age\":25}")
print(res)
3.3 最强 → Pydantic 自定义解析器
直接解析成强类型对象,最稳定、最规范。
步骤 1:定义数据结构(Pydantic)
python
from pydantic import BaseModel, Field
class UserInfo(BaseModel):
name: str = Field(description="姓名")
age: int = Field(description="年龄")
job: str = Field(description="职业")
步骤 2:自定义解析器
python
from langchain.schema import BaseOutputParser
import json
import re
class PydanticParser(BaseOutputParser[UserInfo]):
def parse(self, output: str) -> UserInfo:
json_str = re.search(r"\{.*\}", output, re.DOTALL).group()
data = json.loads(json_str)
return UserInfo(**data)
def get_format_instructions(self) -> str:
return """
请输出JSON:
{"name":"姓名","age":年龄,"job":"职业"}
"""
步骤 3:使用
python
parser = PydanticParser()
user = parser.parse('{"name":"张三","age":25,"job":"工程师"}')
print(user.name) # 张三
print(user.age) # 25
print(user.job) # 工程师
3.4 自定义解析器 + Prompt + LLM 完整链路
python
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# 1. 定义 prompt
prompt = PromptTemplate(
template="从文本中提取信息。\n{format_instructions}\n文本:{text}",
input_variables=["text"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
# 2. 定义模型
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 3. 拼接链
chain = prompt | llm | parser
# 4. 运行(直接返回结构化对象!)
result = chain.invoke({
"text": "张三今年25岁,是一名软件工程师。"
})
print(result)
# 输出:UserInfo(name='张三', age=25, job='软件工程师')
四、与提示词模板集成
输出解析器的核心价值在于与提示词模板无缝集成,自动注入格式说明。
4.1 标准集成模式
python
from langchain.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
# 定义模型
class Product(BaseModel):
name: str = Field(description="产品名称")
price: float = Field(description="产品价格,单位元")
tags: List[str] = Field(description="产品标签,最多3个")
# 创建解析器
parser = PydanticOutputParser(pydantic_object=Product)
# 创建提示词模板
prompt = PromptTemplate(
template="""根据产品描述,提取产品信息。
产品描述:{description}
{format_instructions}
请直接返回JSON格式,不要包含其他内容。""",
input_variables=["description"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
# 构建链
model = ChatOpenAI(model="gpt-4")
chain = prompt | model | parser
# 使用
result = chain.invoke({
"description": "一款高性价比的无线蓝牙耳机,售价299元,支持降噪和长续航"
})
print(result.name) # 无线蓝牙耳机
print(result.price) # 299.0
print(result.tags) # ['无线', '降噪', '长续航']
4.2 对话模板集成
python
from langchain_core.prompts import ChatPromptTemplate
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个数据提取助手。\n{format_instructions}"),
("human", "{input}")
])
# 部分填充格式说明
chat_prompt = chat_prompt.partial(
format_instructions=parser.get_format_instructions()
)
chain = chat_prompt | model | parser
五、高级技巧
5.1 与提示模板配合使用
通过 get_format_instructions 将格式要求嵌入提示,可显著提高模型输出的准确性。
python
prompt = PromptTemplate(
template="Answer the user question.\n{format_instructions}\nQuestion: {question}",
input_variables=["question"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
5.2 解析器链式组合
可以将多个解析器组合,例如先使用 JsonOutputParser 解析 JSON,再对某个字段使用 PydanticOutputParser,但通常推荐直接使用一个解析器完成完整解析。
python
from langchain_core.output_parsers import StrOutputParser
import json
# 第一步:获取 JSON 字符串
str_parser = StrOutputParser()
# 第二步:解析为 Python 对象
json_parser = JsonOutputParser()
# 组合
chain = model | str_parser | json_parser
5.3 异常处理与重试
在复杂场景下,使用 RetryOutputParser 或 OutputFixingParser 增加鲁棒性。
python
retry_parser = RetryOutputParser.from_llm(parser=base_parser, llm=model, max_retries=2)
5.4 流式输出中的解析
对于流式输出,解析器通常不能实时工作(因为需要完整输出)。此时可以考虑在累积完整响应后再解析,或者使用支持流式的解析器(如 StrOutputParser 直接流式输出字符串)。
python
from langchain_core.output_parsers import JsonOutputParser
# 流式 JSON 解析器
parser = JsonOutputParser()
async for chunk in model.astream(prompt):
# 增量解析 JSON
partial = parser.parse_partial(chunk)
print(partial)
六、实际应用场景
6.1 信息抽取
python
class ResumeInfo(BaseModel):
name: str
skills: List[str]
experience_years: int
education: str
parser = PydanticOutputParser(pydantic_object=ResumeInfo)
chain = (
PromptTemplate.from_template("提取简历信息:{resume}\n{format}")
| model
| parser
)
6.2 分类与标签
python
from langchain.output_parsers import EnumOutputParser
class Category(Enum):
TECH = "technology"
SPORTS = "sports"
BUSINESS = "business"
ENTERTAINMENT = "entertainment"
parser = EnumOutputParser(enum=Category)
classifier = PromptTemplate.from_template(
"分类文章:{article}\n{format}"
) | model | parser
6.3 数据验证与清洗
python
class ValidatedData(BaseModel):
user_id: int = Field(gt=0, description="用户ID,必须大于0")
email: str = Field(pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$')
age: int = Field(ge=0, le=150)
parser = PydanticOutputParser(pydantic_object=ValidatedData)
# 自动验证数据合法性
七、性能优化与最佳实践
7.1 缓存解析器实例
python
# 解析器是无状态的,可以重用
shared_parser = PydanticOutputParser(pydantic_object=MyModel)
7.2 批量解析
python
def batch_parse(parser, texts: List[str]) -> List[Any]:
return [parser.parse(text) for text in texts]
7.3 异步解析
python
async def async_parse(parser, text: str) -> Any:
# 如果解析器支持异步
return await parser.aparse(text)
7.4 最佳实践总结
- 始终提供格式指令:将 get_format_instructions 放入提示中,模型输出的结构化程度会大幅提升。
- 使用 Pydantic 模型定义复杂结构:PydanticOutputParser 提供了类型安全、字段描述和验证,是处理复杂输出的首选。
- 处理解析失败:生产环境中应结合 RetryOutputParser 或 OutputFixingParser 增加容错。
- 避免过度复杂:如果只需要简单文本或列表,使用 StrOutputParser 或 CommaSeparatedListOutputParser 即可。
- 注意输出长度:JSON 格式可能会消耗更多 token,需在 prompt 中提示简洁性。
- 测试解析器:针对模型可能输出的边缘情况编写单元测试,确保解析器鲁棒。