我正在参加「豆包MarsCode AI练中学体验活动」
今天我要带着你深入研究一下LangChain中的输出解析器,并用一个新的解析器——Pydantic 解析器来重构第5课中的程序。这节课也是模型I/O框架的最后一讲。
下面先来看看LangChain中的输出解析器究竟是什么,有哪些种类。
1.LangChain中的输出解析器
输出解析器是一种专用于处理和构建语言模型响应的类。 一个基本的输出解析器类通常需要实现两个核心方法。
- get_format_instructions:这个方法需要返回一个字符串,用于指导如何格式化语言模型的输出,告诉它应该如何组织并构建它的回答。
- parse:这个方法接收一个字符串(也就是语言模型的输出)并将其解析为特定的数据结构或格式。这一步通常用于确保模型的输出符合我们的预期,并且能够以我们需要的形式进行后续处理。
还有一个可选的方法。
- parse_with_prompt:这个方法接收一个字符串(也就是语言模型的输出)和一个提示(用于生成这个输出的提示),并将其解析为特定的数据结构。这样,你可以根据原始提示来修正或重新解析模型的输出,确保输出的信息更加准确和贴合要求。
class OutputParser:
def __init__(self):
pass
def get_format_instructions(self):
# 返回一个字符串,指导如何格式化模型的输出
pass
def parse(self, model_output):
# 解析模型的输出,转换为某种数据结构或格式
pass
def parse_with_prompt(self, model_output, prompt):
# 基于原始提示解析模型的输出,转换为某种数据结构或格式
pass
在LangChain中,通过实现get_format_instructions、parse 和 parse_with_prompt 这些方法,针对不同的使用场景和目标,设计了各种输出解析器。让我们来逐一认识一下。
- 列表解析器(List Parser):这个解析器用于处理模型生成的输出,当需要模型的输出是一个列表的时候使用。例如,如果你询问模型“列出所有鲜花的库存”,模型的回答应该是一个列表。
- 日期时间解析器(Datetime Parser):这个解析器用于处理日期和时间相关的输出,确保模型的输出是正确的日期或时间格式。
- 枚举解析器(Enum Parser):这个解析器用于处理预定义的一组值,当模型的输出应该是这组预定义值之一时使用。例如,如果你定义了一个问题的答案只能是“是”或“否”,那么枚举解析器可以确保模型的回答是这两个选项之一。
- 结构化输出解析器(Structured Output Parser):这个解析器用于处理复杂的、结构化的输出。如果你的应用需要模型生成具有特定结构的复杂回答(例如一份报告、一篇文章等),那么可以使用结构化输出解析器来实现。
- Pydantic(JSON)解析器:这个解析器用于处理模型的输出,当模型的输出应该是一个符合特定格式的JSON对象时使用。它使用Pydantic库,这是一个数据验证库,可以用于构建复杂的数据模型,并确保模型的输出符合预期的数据模型。
- 自动修复解析器(Auto-Fixing Parser):这个解析器可以自动修复某些常见的模型输出错误。例如,如果模型的输出应该是一段文本,但是模型返回了一段包含语法或拼写错误的文本,自动修复解析器可以自动纠正这些错误。
- 重试解析器(RetryWithErrorOutputParser):这个解析器用于在模型的初次输出不符合预期时,尝试修复或重新生成新的输出。例如,如果模型的输出应该是一个日期,但是模型返回了一个字符串,那么重试解析器可以重新提示模型生成正确的日期格式。
上面的各种解析器中,前三种很容易理解,而结构化输出解析器你已经用过了。所以接下来我们重点讲一讲Pydantic(JSON)解析器、自动修复解析器和重试解析器。
2.Pydantic(JSON)解析器
Pydantic 是一个 Python 数据验证和设置管理库,主要基于 Python 类型提示。尽管它不是专为 JSON 设计的,但由于 JSON 是现代 Web 应用和 API 交互中的常见数据格式,Pydantic 在处理和验证 JSON 数据时特别有用。
# ------Part 1
import os
os.environ["OPENAI_API_KEY"] = ''
model = 'ep-20241104131149-csxf9'
base_url="https://ark.cn-beijing.volces.com/api/v3"
# 创建模型实例
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model=model,
base_url=base_url,
api_key=os.environ["OPENAI_API_KEY"]
)
# ------Part 2
# 创建一个空的DataFrame用于存储结果
import pandas as pd
df = pd.DataFrame(
columns=["flower_type", "price", "description", "reason"])
# 数据准备
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
# 定义我们想要接收的数据格式
from pydantic.v1 import BaseModel, Field
class FlowerDescription(BaseModel):
flower_type: str = Field(description="鲜花的种类")
price: int = Field(description="鲜花的价格")
description: str = Field(description="鲜花的描述文案")
reason: str = Field(description="为什么要这样写这个文案")
# ------Part 3
# 创建输出解析器
from langchain.output_parsers import PydanticOutputParser
output_parser = PydanticOutputParser(pydantic_object=FlowerDescription)
# 获取输出格式指示
format_instructions = output_parser.get_format_instructions()
# 打印提示
print("输出格式:", format_instructions)
# ------Part 4
# 创建提示模板
from langchain import PromptTemplate
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower} ,您能提供一个吸引人的简短中文描述吗?
{format_instructions}"""
# 根据模板创建提示,同时在提示中加入输出解析器的说明
prompt = PromptTemplate.from_template(
prompt_template, partial_variables={"format_instructions": format_instructions}
)
# 打印提示
# print("提示:", prompt)
# ------Part 5
for flower, price in zip(flowers, prices):
# 根据提示准备模型的输入
input = prompt.format(flower=flower, price=price)
# 打印提示
print("提示:", input)
# 获取模型的输出
output = model.predict(input)
# 解析模型的输出
parsed_output = output_parser.parse(output)
parsed_output_dict = parsed_output.dict() # 将Pydantic格式转换为字典
# 将解析后的输出添加到DataFrame中
df.loc[len(df)] = parsed_output.dict()
# 打印字典
print("输出的数据:", df.to_dict(orient="records"))
结果如下:
3.自动修复解析器(OutputFixingParser)
你需要知道的是gpt模型给出的答案不一定会完全遵照你想要的格式给出,为了能够解析常见的问题,比如说json格式中的key应该使用双引号进行包裹,但是gpt给出的却是单引号(这个可以再python中的字典中解析),于是langchain给出了自动修复解析器,用于解析错误格式。
import os
os.environ["OPENAI_API_KEY"] = ''
model = 'ep-20241104131149-csxf9'
base_url="https://ark.cn-beijing.volces.com/api/v3"
# 导入所需要的库和模块
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List
# 使用Pydantic创建一个数据格式,表示花
class Flower(BaseModel):
name: str = Field(description="name of a flower")
colors: List[str] = Field(description="the colors of this flower")
# 定义一个用于获取某种花的颜色列表的查询
flower_query = "Generate the charaters for a random flower."
# 定义一个格式不正确的输出
misformatted = "{'name': '康乃馨', 'colors': ['粉红色','白色','红色','紫色','黄色']}"
# 创建一个用于解析输出的Pydantic解析器,此处希望解析为Flower格式
parser = PydanticOutputParser(pydantic_object=Flower)
# 使用Pydantic解析器解析不正确的输出
# parser.parse(misformatted) # 这行代码会出错
# 从langchain库导入所需的模块
from langchain.output_parsers import OutputFixingParser
from langchain_openai import ChatOpenAI
# 使用OutputFixingParser创建一个新的解析器,该解析器能够纠正格式不正确的输出
new_parser = OutputFixingParser.from_llm(
parser=parser,
llm=ChatOpenAI(
model=model,
base_url=base_url,
api_key=os.environ["OPENAI_API_KEY"]
),
)
# 使用新的解析器解析不正确的输出
result = new_parser.parse(misformatted) # 错误被自动修正
print(result) # 打印解析后的输出结果
4.重试解析器(RetryWithErrorOutputParser)
OutputFixingParser不错,但它只能做简单的格式修复。如果出错的不只是格式,比如,输出根本不完整,有缺失内容,那么仅仅根据输出和格式本身,是无法修复它的。
此时,通过实现输出解析器中parse_with_prompt方法,LangChain提供的重试解析器可以帮助我们利用大模型的推理能力根据原始提示找回相关信息。
我们来设计一个数据不完整错误:
# 定义一个模板字符串,这个模板将用于生成提问
template = """Based on the user question, provide an Action and Action Input for what step should be taken.
{format_instructions}
Question: {query}
Response:"""
# 定义一个Pydantic数据格式,这个格式描述了一个"行动"类及其属性
from pydantic import BaseModel, Field
class Action(BaseModel):
action: str = Field(description="action to take")
action_input: str = Field(description="input to the action")
# 使用Pydantic格式Action来初始化一个输出解析器
from langchain.output_parsers import PydanticOutputParser
parser = PydanticOutputParser(pydantic_object=Action)
# 定义一个提示模板,它将用于向模型提问
from langchain.prompts import PromptTemplate
prompt = PromptTemplate(
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
prompt_value = prompt.format_prompt(query="What are the colors of Orchid?")
# 定义一个错误格式的字符串
bad_response = '{"action": "search"}'
# parser.parse(bad_response) # 如果直接解析,它会引发一个错误
使用OutputFixing来解决这个问题:
# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = ''
model = 'ep-20241104131149-csxf9'
base_url="https://ark.cn-beijing.volces.com/api/v3"
from langchain_openai import ChatOpenAI
# 尝试用OutputFixingParser来解决这个问题
from langchain.output_parsers import OutputFixingParser
fix_parser = OutputFixingParser.from_llm(
parser=parser,
llm=ChatOpenAI(
model=model,
base_url=base_url,
api_key=os.environ["OPENAI_API_KEY"]
),
)
parse_result = fix_parser.parse(bad_response)
print("OutputFixingParser的parse结果:", parse_result)
实际上上述代码并没做任何改变,于是,我们需要使用RetryWithErrorOutputParse这个解析器
# 初始化RetryWithErrorOutputParser,它会尝试再次提问来得到一个正确的输出
from langchain.output_parsers import RetryWithErrorOutputParser
retry_parser = RetryWithErrorOutputParser.from_llm(
parser=parser, llm=ChatOpenAI(
model=model,
base_url=base_url,
api_key=os.environ["OPENAI_API_KEY"],
temperature=0
))
parse_result = retry_parser.parse_with_prompt(bad_response, prompt_value)
print("RetryWithErrorOutputParser的parse结果:", parse_result)
5.整体代码:
# 定义一个模板字符串,这个模板将用于生成提问
template = """Based on the user question, provide an Action and Action Input for what step should be taken.
{format_instructions}
Question: {query}
Response:"""
# 定义一个Pydantic数据格式,这个格式描述了一个"行动"类及其属性
from pydantic import BaseModel, Field
class Action(BaseModel):
action: str = Field(description="action to take")
action_input: str = Field(description="input to the action")
# 使用Pydantic格式Action来初始化一个输出解析器
from langchain.output_parsers import PydanticOutputParser
parser = PydanticOutputParser(pydantic_object=Action)
# 定义一个提示模板,它将用于向模型提问
from langchain.prompts import PromptTemplate
prompt = PromptTemplate(
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
prompt_value = prompt.format_prompt(query="What are the colors of Orchid?")
# 定义一个错误格式的字符串
bad_response = '{"action": "search"}'
# parser.parse(bad_response) # 如果直接解析,它会引发一个错误
# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = ''
model = 'ep-20241104131149-csxf9'
base_url="https://ark.cn-beijing.volces.com/api/v3"
from langchain_openai import ChatOpenAI
# 尝试用OutputFixingParser来解决这个问题
from langchain.output_parsers import OutputFixingParser
fix_parser = OutputFixingParser.from_llm(
parser=parser,
llm=ChatOpenAI(
model=model,
base_url=base_url,
api_key=os.environ["OPENAI_API_KEY"]
),
)
parse_result = fix_parser.parse(bad_response)
print("OutputFixingParser的parse结果:", parse_result)
# 初始化RetryWithErrorOutputParser,它会尝试再次提问来得到一个正确的输出
from langchain.output_parsers import RetryWithErrorOutputParser
retry_parser = RetryWithErrorOutputParser.from_llm(
parser=parser, llm=ChatOpenAI(
model=model,
base_url=base_url,
api_key=os.environ["OPENAI_API_KEY"],
temperature=0
))
parse_result = retry_parser.parse_with_prompt(bad_response, prompt_value)
print("RetryWithErrorOutputParser的parse结果:", parse_result)
6.延伸阅读
- 工具:Pydantic 是一个Python库,用于数据验证,可以确保数据符合特定的格式
- 文档:LangChain中的各种 Output Parsers