LangChain 实战08 - OutputParser

我正在参加「豆包MarsCode AI练中学体验活动」详情请看:掘金小册上线 AI练中学功能 | 你的 AI 编程助教喊你免费领小册啦!

前言

深入研究一下LangChain中的输出解析器,并用一个新的解析器------Pydantic 解析器来重构之前的代码

LangChain 中的输出解析器

语言模型产生的结果通常是人类可读的文本。然而,在某些情况下,我们可能希望得到的是更适合机器处理的结构化数据。这时,就需要用到输出解析器了。

输出解析器是一种专用于处理和构建语言模型响应的类,包含两个主要的功能:

  • get_format_instructions 方法:这个方法需要返回一个字符串,用于指导如何格式化语言模型的输出,告诉它应该如何组织并构建它的回答。
  • parse 方法:此功能负责接受语言模型的输出------即一段字符串,并将其转换成特定的数据格式或结构。这一过程有助于确保模型的输出满足既定的要求,并且便于后续的处理和应用。

此外,还有一个非强制性的功能:

  • parse_with_prompt 方法:这个方法接收一个字符串(也就是语言模型的输出)和一个提示(用于生成这个输出的提示),并将其解析为特定的数据结构。这样,你可以根据原始提示来修正或重新解析模型的输出,确保输出的信息更加准确和贴合要求。

接下来,提供一个简单的伪代码实例,展示了如何基于以上介绍实现一个输出解析器:

python 复制代码
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_instructionsparse以及parse_with_prompt等方法,开发了多种输出解析器。下面我们将逐一介绍这些解析器的设计理念和功能特点:

  1. 列表解析器(List Parser):这个解析器用于处理模型生成的输出,当需要模型的输出是一个列表的时候使用。例如,如果你询问模型"列出所有鲜花的库存",模型的回答应该是一个列表。
  2. 日期时间解析器(Datetime Parser):这个解析器用于处理日期和时间相关的输出,确保模型的输出是正确的日期或时间格式。
  3. 枚举解析器(Enum Parser):这个解析器用于处理预定义的一组值,当模型的输出应该是这组预定义值之一时使用。例如,如果你定义了一个问题的答案只能是"是"或"否",那么枚举解析器可以确保模型的回答是这两个选项之一。
  4. 结构化输出解析器(Structured Output Parser):这个解析器用于处理复杂的、结构化的输出。如果你的应用需要模型生成具有特定结构的复杂回答(例如一份报告、一篇文章等),那么可以使用结构化输出解析器来实现。
  5. Pydantic(JSON)解析器:这个解析器用于处理模型的输出,当模型的输出应该是一个符合特定格式的JSON对象时使用。它使用Pydantic库,这是一个数据验证库,可以用于构建复杂的数据模型,并确保模型的输出符合预期的数据模型。
  6. 自动修复解析器(Auto-Fixing Parser):这个解析器可以自动修复某些常见的模型输出错误。例如,如果模型的输出应该是一段文本,但是模型返回了一段包含语法或拼写错误的文本,自动修复解析器可以自动纠正这些错误。
  7. 重试解析器(RetryWithErrorOutputParser):这个解析器用于在模型的初次输出不符合预期时,尝试修复或重新生成新的输出。例如,如果模型的输出应该是一个日期,但是模型返回了一个字符串,那么重试解析器可以重新提示模型生成正确的日期格式。

Pydantic解析器

Pydantic 是一个强大的 Python 库,专注于数据验证和管理,它利用了 Python 的类型注解特性。虽然 Pydantic 并非专门为处理 JSON 数据而设计,但鉴于 JSON 在现代 Web 应用程序和 API 交互中的广泛应用,Pydantic 成为了处理和验证 JSON 数据的理想选择。

python 复制代码
# ------Part 1
# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'

# 创建模型实例
from langchain import OpenAI
model = OpenAI(model_name='text-davinci-003')


# ------Part 2
# 创建一个空的DataFrame用于存储结果
import pandas as pd
df = pd.DataFrame(columns=["flower_type", "price", "description", "reason"])

# 数据准备
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]

# 定义我们想要接收的数据格式
from pydantic import BaseModel, Field 
# pydantic:自动验证输入数据,确保输入数据符合你指定的类型和其他验证条件。
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(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'))

自动修复解析器(OutputFixingParser)

Python期望属性名称被双引号包围,但在给定的JSON字符串中是单引号。

python 复制代码
# 导入所需要的库和模块
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)

修改后

python 复制代码
# 从langchain库导入所需的模块
from langchain.chat_models import ChatOpenAI
from langchain.output_parsers import OutputFixingParser

# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'

# 使用OutputFixingParser创建一个新的解析器,该解析器能够纠正格式不正确的输出
new_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())

# 使用新的解析器解析不正确的输出
result = new_parser.parse(misformatted) # 错误被自动修正
print(result) # 打印解析后的输出结果

重试解析器(RetryWithErrorOutputParser)

OutputFixingParser不错,但它只能做简单的格式修复。如果出错的不只是格式,比如,输出根本不完整,有缺失内容,那么仅仅根据输出和格式本身,是无法修复它的。

python 复制代码
# 定义一个模板字符串,这个模板将用于生成提问
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) # 如果直接解析,它会引发一个错误

由于bad_response只提供了action字段,而没有提供action_input字段,这与Action数据格式的预期不符,所以解析会失败。

我们首先尝试用OutputFixingParser来解决这个错误。

python 复制代码
from langchain.output_parsers import OutputFixingParser
from langchain.chat_models import ChatOpenAI
fix_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())
parse_result = fix_parser.parse(bad_response)
print('OutputFixingParser的parse结果:',parse_result)

OutputFixingParser的parse结果: action='search' action_input='query'

我们来看看这个尝试解决了什么问题,没解决什么问题。

解决的问题有:

  • 不完整的数据:原始的bad_response只提供了action字段而没有action_input字段。OutputFixingParser已经填补了这个缺失,为action_input字段提供了值 'query'

没解决的问题有:

  • 具体性:尽管OutputFixingParser为action_input字段提供了默认值 'query',但这并不具有描述性。真正的查询是 "Orchid(兰花)的颜色是什么?"。所以,这个修复只是提供了一个通用的值,并没有真正地回答用户的问题。
  • 可能的误导: 'query' 可能被误解为一个指示,要求进一步查询某些内容,而不是作为实际的查询输入。

当然,还有更鲁棒的选择,我们最后尝试一下RetryWithErrorOutputParser这个解析器。

python 复制代码
# 初始化RetryWithErrorOutputParser,它会尝试再次提问来得到一个正确的输出
from langchain.output_parsers import RetryWithErrorOutputParser
from langchain.llms import OpenAI
retry_parser = RetryWithErrorOutputParser.from_llm(
    parser=parser, llm=OpenAI(temperature=0)
)
parse_result = retry_parser.parse_with_prompt(bad_response, prompt_value)
print('RetryWithErrorOutputParser的parse结果:',parse_result)

这个解析器没有让我们失望,成功地还原了格式,甚至也根据传入的原始提示,还原了action_input字段的内容。

RetryWithErrorOutputParser的parse结果: action='search' action_input='colors of Orchid'

相关推荐
行码棋23 分钟前
【机器学习】回归模型(线性回归+逻辑回归)原理详解
人工智能·机器学习·线性回归
liulanba24 分钟前
Kotlin的data class
前端·微信·kotlin
战族狼魂42 分钟前
淘宝客结合C#使用WebApi和css绘制商品图片
前端·css·c#
活宝小娜1 小时前
vue项目使用element-ui中的radio,切换radio时报错: Blocked aria-hidden
前端·vue.js·ui
狐凄1 小时前
AI 在软件开发
人工智能
学步_技术1 小时前
自动驾驶系列—自动驾驶数据脱敏:保护隐私与数据安全的关键技术
人工智能·机器学习·自动驾驶·数据安全·数据脱敏
PasteSpider1 小时前
贴代码框架PasteForm特性介绍之datetime,daterange
前端·html·.netcore·crud
学步_技术1 小时前
自动驾驶系列—深入解析自动驾驶车联网技术及其应用场景
人工智能·机器学习·自动驾驶·车联网
whaosoft-1431 小时前
51c自动驾驶~合集27
人工智能
跑得动1 小时前
uni-ui自动化导入
前端·ui·自动化