输出解析器 (Output Parsers)
输出解析器是LangChain的重要组件,用于将语言模型的原始文本输出转换为结构化数据。本文档详细介绍了输出解析器的类型、功能和最佳实践。
概述
语言模型通常输出自然语言文本,但在应用开发中,我们经常需要将这些文本转换为结构化的数据格式,如列表、字典或对象。输出解析器实现了这一关键功能,提供了:
结构化数据提取:将自然语言转换为特定数据结构
格式一致性保证:确保输出符合预期格式
容错处理:处理模型输出不符合要求的情况
模型指导:向模型提供正确输出格式的指导
基础解析器
1. 字符串解析器 (StrOutputParser)
最简单的解析器,用于提取模型返回的原始文本:
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 创建简单链
model = ChatOpenAI()
prompt = ChatPromptTemplate.from_template("解释{topic}是什么?")
parser = StrOutputParser()
# 组合组件
chain = prompt | model | parser
# 调用链
result = chain.invoke({"topic": "量子计算"})
# result 是一个字符串,包含模型对量子计算的解释
2. 列表解析器 (CommaSeparatedListOutputParser)
将逗号分隔的文本转换为Python列表:
from langchain_core.output_parsers import CommaSeparatedListOutputParser
# 创建列表解析器
list_parser = CommaSeparatedListOutputParser()
# 创建带格式说明的提示模板
format_instructions = list_parser.get_format_instructions()
list_prompt = ChatPromptTemplate.from_template(
"列出{topic}的五个最重要特点。\n{format_instructions}"
)
# 组合组件
list_chain = list_prompt | model | list_parser
# 调用链
topic_features = list_chain.invoke({
"topic": "人工智能",
"format_instructions": format_instructions
})
# 结果是一个Python列表: ['特点1', '特点2', '特点3', '特点4', '特点5']
3. JSON解析器 (JsonOutputParser)
将JSON格式文本转换为Python字典或列表:
from langchain_core.output_parsers import JsonOutputParser
# 创建JSON解析器
json_parser = JsonOutputParser()
# 创建带格式说明的提示模板
json_format_instructions = json_parser.get_format_instructions()
json_prompt = ChatPromptTemplate.from_template(
"生成一个包含{person}基本信息的JSON。应包括姓名、职业、年龄和技能列表。\n{format_instructions}"
)
# 组合组件
json_chain = json_prompt | model | json_parser
# 调用链
person_info = json_chain.invoke({
"person": "爱因斯坦",
"format_instructions": json_format_instructions
})
# 结果是一个Python字典,包含爱因斯坦的信息
高级解析器
1. Pydantic解析器 (PydanticOutputParser)
使用Pydantic模型定义输出结构:
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field, validator
from typing import List
# 定义Pydantic模型
class Movie(BaseModel):
title: str = Field(description="电影标题")
director: str = Field(description="导演姓名")
year: int = Field(description="上映年份")
genre: List[str] = Field(description="电影类型")
rating: float = Field(description="评分(1-10)")
@validator("rating")
def rating_must_be_valid(cls, v):
if v < 1 or v > 10:
raise ValueError("评分必须在1到10之间")
return v
# 创建Pydantic解析器
pydantic_parser = PydanticOutputParser(pydantic_object=Movie)
# 创建带格式说明的提示模板
format_instructions = pydantic_parser.get_format_instructions()
pydantic_prompt = ChatPromptTemplate.from_template(
"生成一部{genre}电影的信息。\n{format_instructions}"
)
# 组合组件
pydantic_chain = pydantic_prompt | model | pydantic_parser
# 调用链
movie_data = pydantic_chain.invoke({
"genre": "科幻",
"format_instructions": format_instructions
})
# 结果是一个Movie对象,包含电影信息
2. XML解析器 (XMLOutputParser)
解析XML格式的输出:
from langchain_core.output_parsers import XMLOutputParser
# 创建XML解析器
xml_parser = XMLOutputParser()
# 创建带格式说明的提示模板
xml_format = """<analysis>
<sentiment>positive or negative</sentiment>
<language>detected language</language>
<summary>brief summary</summary>
</analysis>"""
xml_prompt = ChatPromptTemplate.from_template(
"分析以下文本的情感、语言和内容:\n{text}\n\n以下面的XML格式输出:\n{xml_format}"
)
# 组合组件
xml_chain = xml_prompt | model | xml_parser
# 调用链
xml_result = xml_chain.invoke({
"text": "人工智能正在迅速发展,为各行各业带来革命性变化。",
"xml_format": xml_format
})
# 结果是一个包含解析XML数据的字典
3. 自定义解析器
创建自定义输出解析器:
from langchain_core.output_parsers import BaseOutputParser
from typing import Dict, Any
class CustomKeyValueParser(BaseOutputParser[Dict[str, Any]]):
"""解析形如'key: value'的文本"""
def parse(self, text: str) -> Dict[str, Any]:
"""从文本中解析键值对"""
result = {}
lines = text.strip().split('\n')
for line in lines:
if ':' in line:
key, value = line.split(':', 1)
result[key.strip()] = value.strip()
return result
def get_format_instructions(self) -> str:
"""提供格式指导给模型"""
return """请以'键: 值'的格式返回信息,每行一个键值对。
例如:
名称: 爱因斯坦
职业: 物理学家
贡献: 相对论"""
# 使用自定义解析器
custom_parser = CustomKeyValueParser()
custom_prompt = ChatPromptTemplate.from_template(
"提供关于{person}的基本信息。\n{format_instructions}"
)
custom_chain = custom_prompt | model | custom_parser
result = custom_chain.invoke({
"person": "牛顿",
"format_instructions": custom_parser.get_format_instructions()
})
组合解析器
1. 多重解析 (RouterOutputParser)
根据内容选择不同的解析器:
from langchain_core.output_parsers import RouterOutputParser
from langchain_core.output_parsers.openai_tools import PydanticToolsParser
class Person(BaseModel):
name: str
age: int
class Company(BaseModel):
name: str
industry: str
employees: int
# 创建组合解析器
router_parser = PydanticToolsParser(tools=[
Person,
Company
])
# 创建提示模板
router_prompt = ChatPromptTemplate.from_template(
"根据查询决定是返回人物信息还是公司信息。\n查询: {query}"
)
# 组合组件
router_chain = router_prompt | model.bind_tools([Person, Company]) | router_parser
# 人物查询
person_result = router_chain.invoke({"query": "告诉我关于比尔盖茨的信息"})
# 返回Person对象
# 公司查询
company_result = router_chain.invoke({"query": "告诉我关于微软的信息"})
# 返回Company对象
2. 错误处理与重试 (OutputFixingParser)
处理解析错误并尝试修复:
from langchain_core.output_parsers import OutputFixingParser
# 创建基础解析器(容易出错的格式)
base_parser = PydanticOutputParser(pydantic_object=Movie)
# 创建带修复功能的解析器
fixing_parser = OutputFixingParser.from_llm(
parser=base_parser,
llm=ChatOpenAI()
)
# 即使模型输出不完全符合格式要求,也能尝试修复并解析
try_chain = prompt | model | fixing_parser
构建复杂解析策略
1. 结构化提取与转换
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.runnables import RunnablePassthrough, RunnableBranch
from typing import List, Optional
# 定义多级数据结构
class Author(BaseModel):
name: str = Field(description="作者姓名")
background: Optional[str] = Field(description="作者背景", default=None)
class Book(BaseModel):
title: str = Field(description="书名")
year: int = Field(description="出版年份")
author: Author = Field(description="作者信息")
genres: List[str] = Field(description="图书类型")
summary: str = Field(description="内容简介")
# 创建解析器和提示
book_parser = PydanticOutputParser(pydantic_object=Book)
instructions = book_parser.get_format_instructions()
book_prompt = ChatPromptTemplate.from_template(
"提供关于{book_title}的详细信息,包括作者、出版年份、类型和简介。\n{format_instructions}"
)
# 处理结果的函数
def process_book(book: Book) -> dict:
# 计算出版至今年数
import datetime
current_year = datetime.datetime.now().year
years_since_pub = current_year - book.year
return {
"book": book,
"years_since_publication": years_since_pub,
"is_classic": years_since_pub > 50
}
# 组合处理链
book_chain = book_prompt | model | book_parser | process_book
# 调用链
result = book_chain.invoke({
"book_title": "战争与和平",
"format_instructions": instructions
})
2. 条件解析逻辑
from langchain_core.runnables import RunnableBranch
from langchain_core.output_parsers import JsonOutputParser, XMLOutputParser
# 创建多格式解析器
def is_json(text):
return text.strip().startswith('{')
def is_xml(text):
return text.strip().startswith('<')
# 创建条件分支解析器
multi_format_parser = RunnableBranch(
(is_json, JsonOutputParser()),
(is_xml, XMLOutputParser()),
StrOutputParser() # 默认解析器
)
# 在链中使用
flexible_chain = prompt | model | multi_format_parser
输出格式化技巧
1. 明确的格式指令
提供详细的格式说明,帮助模型生成可解析的输出:
from langchain_core.output_parsers.json import JsonOutputParser
json_parser = JsonOutputParser()
instructions = json_parser.get_format_instructions()
detailed_prompt = """生成一个包含电影信息的JSON对象。必须包含以下字段:
- title: 电影标题 (字符串)
- director: 导演姓名 (字符串)
- year: 上映年份 (整数)
- genres: 电影类型 (字符串数组)
- rating: 评分,1-10之间 (数值)
{format_instructions}
电影: {movie}"""
prompt = ChatPromptTemplate.from_template(detailed_prompt)
chain = prompt | model | json_parser
result = chain.invoke({"movie": "黑客帝国", "format_instructions": instructions})
2. 使用Enum限制选项
使用枚举类型限制可能的值:
from enum import Enum
from langchain_core.pydantic_v1 import BaseModel, Field
class Sentiment(str, Enum):
POSITIVE = "positive"
NEUTRAL = "neutral"
NEGATIVE = "negative"
class SentimentAnalysis(BaseModel):
text: str = Field(description="分析的文本")
sentiment: Sentiment = Field(description="文本情感 (positive, neutral, negative)")
confidence: float = Field(description="置信度 (0.0-1.0)")
class Config:
use_enum_values = True
# 创建解析器
sentiment_parser = PydanticOutputParser(pydantic_object=SentimentAnalysis)
3. 分步解析复杂输出
对于复杂任务,先将输出分解为简单部分:
from langchain_core.output_parsers import StrOutputParser
# 第一步:生成文本分析
analysis_prompt = ChatPromptTemplate.from_template("分析以下文本: {text}")
analysis_chain = analysis_prompt | model | StrOutputParser()
# 第二步:从分析中提取结构化数据
structure_prompt = ChatPromptTemplate.from_template(
"""基于下面的分析,提取主要观点和情感评分(1-10),以JSON格式返回:
分析: {analysis}
JSON格式:
{{
"main_points": ["观点1", "观点2", ...],
"sentiment_score": 评分
}}
"""
)
structure_chain = structure_prompt | model | JsonOutputParser()
# 组合两个步骤
full_chain = {"analysis": analysis_chain} | structure_chain
result = full_chain.invoke({"text": "这个产品质量很好,但价格有点贵。客服态度也不错,总体来说是不错的购物体验。"})
解析器验证与错误处理
1. 使用验证逻辑
Pydantic模型中添加验证器:
from langchain_core.pydantic_v1 import BaseModel, Field, validator
from typing import List
class Product(BaseModel):
name: str = Field(description="产品名称")
price: float = Field(description="产品价格")
features: List[str] = Field(description="产品特点列表")
rating: int = Field(description="产品评分(1-5)")
@validator("price")
def price_must_be_positive(cls, v):
if v <= 0:
raise ValueError("价格必须为正数")
return v
@validator("rating")
def rating_must_be_valid(cls, v):
if v < 1 or v > 5:
raise ValueError("评分必须在1到5之间")
return v
2. 错误处理策略
from langchain_core.output_parsers import OutputFixingParser
from langchain_core.runnables import RunnablePassthrough
# 创建基础解析器
base_parser = PydanticOutputParser(pydantic_object=Product)
# 添加错误处理
robust_parser = OutputFixingParser.from_llm(
parser=base_parser,
llm=ChatOpenAI()
)
# 实现错误捕获逻辑
def safe_parse(text):
try:
return robust_parser.parse(text)
except Exception as e:
return {"error": str(e), "raw_text": text}
# 在链中使用安全解析
robust_chain = prompt | model | RunnablePassthrough.assign(
parsed_output=lambda x: safe_parse(x.content)
)
与其他组件集成
1. 在智能体中使用解析器
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.tools import tool
# 定义工具函数
@tool
def search_products(query: str) -> str:
"""搜索产品数据库"""
# 假设实现
return "找到3个产品..."
# 定义输出格式
class SearchResult(BaseModel):
products: List[str] = Field(description="产品列表")
top_pick: Optional[str] = Field(description="最佳推荐")
reason: Optional[str] = Field(description="推荐理由")
search_parser = PydanticOutputParser(pydantic_object=SearchResult)
# 创建智能体
tools = [search_products]
agent = create_openai_tools_agent(
model,
tools,
"""你是一位产品专家,帮助用户查找产品。
最终输出必须是JSON格式,包含产品列表和推荐。
{format_instructions}""".format(
format_instructions=search_parser.get_format_instructions()
)
)
agent_executor = AgentExecutor(agent=agent, tools=tools)
# 处理用户查询
response = agent_executor.invoke({"input": "查找价格低于100元的手机配件"})
structured_result = search_parser.parse(response["output"])
2. 与检索系统集成
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
# 假设已经有一个向量数据库
vector_db = Chroma(embedding_function=OpenAIEmbeddings())
retriever = vector_db.as_retriever()
# 定义输出格式
class RetrievalSummary(BaseModel):
question: str = Field(description="用户问题")
sources: List[str] = Field(description="信息来源")
answer: str = Field(description="基于来源的回答")
confidence: float = Field(description="置信度 (0.0-1.0)")
# 创建解析器
rag_parser = PydanticOutputParser(pydantic_object=RetrievalSummary)
# 格式化检索文档
def format_docs(docs):
return "\n\n".join([f"来源 {i+1}: {doc.page_content}" for i, doc in enumerate(docs)])
# 创建RAG链
rag_chain = {
"question": lambda x: x["question"],
"sources": lambda x: format_docs(retriever.get_relevant_documents(x["question"]))
} | ChatPromptTemplate.from_template(
"""基于以下来源回答问题。如果来源中没有答案,就说你不知道。
问题: {question}
来源:
{sources}
{format_instructions}
"""
) | model | rag_parser
最佳实践
提供清晰的格式指南:明确告诉模型预期的输出格式
使用枚举和验证:限制可能的值范围,提高输出一致性
实施错误处理:针对解析失败的情况有备份策略
逐步处理复杂输出:将复杂解析任务分解为多个步骤
考虑格式的复杂性:在详细结构和容错性之间找到平衡
测试不同的提示形式:找到最能产生一致可解析输出的提示方式
常见陷阱与解决方案
模型忽略格式指南
解决方案:将格式指南放在提示的最后,添加明显的分隔符
嵌套数据结构解析失败
解决方案:将复杂结构分解为多个简单步骤,逐步构建
不一致的格式
解决方案:使用OutputFixingParser自动修复轻微格式问题
无法处理长输出
解决方案:实现分块处理策略,将长输出分段解析
总结
输出解析器是LangChain中的关键组件,它们将语言模型的自然语言输出转换为结构化数据,使其可以被应用程序有效处理。通过选择合适的解析器和实施适当的格式提示,可以显著提高语言模型输出的一致性和可用性。
无论是简单的列表和字典,还是复杂的嵌套结构,输出解析器都提供了强大的工具集来处理各种数据类型和格式需求。通过与LangChain的其他组件(如模型、提示模板和链)结合,输出解析器成为构建高效可靠的LLM应用程序的基石。
后续学习
提示模板 - 学习如何设计有效的提示来配合解析器
模型输入输出 - 了解更多关于模型交互的信息
链 - 探索如何将解析器集成到更复杂的工作流中
- 感谢你赐予我前进的力量