输出解析 | 豆包MarsCode AI刷题

169 阅读14分钟

LangChain 中的输出解析器

  1. 定义与作用

    • LangChain 中的输出解析器(Output Parsers)是用于处理语言模型输出结果的工具。它的主要功能是将语言模型生成的原始文本转换为结构化、有意义的数据格式,以便进一步处理和使用。
  2. 主要功能

    • 格式转换

      • 语言模型通常以自然语言的形式生成输出,而在实际应用中,往往需要将这些自然语言转换为特定的格式。例如,语言模型可能会输出 “明天的日期是 2023 年 7 月 15 日”,输出解析器可以将其转换为 “2023 - 07 - 15” 这种标准的日期格式。
    • 数据提取与结构化

      • 当语言模型生成包含多个信息的文本时,输出解析器可以从中提取关键数据并进行结构化处理。例如,模型输出 “张三,30 岁,男性,工程师”,输出解析器可以将其转换为{"name": "张三", "age": 30, "gender": "男", "occupation": "工程师"}这种字典格式的数据。
    • 验证与纠错

      • 输出解析器还能检查语言模型的输出是否符合预期格式。如果不符合,它可以尝试纠正或者标记错误。例如,若模型被要求输出一个数字,却输出了字母,输出解析器可以检测到这种不一致并进行处理。
  3. 常见类型

    • 列表解析器(List Parsers)

      • 当语言模型输出以列表形式呈现的文本时,如 “苹果、香蕉、橙子”,列表解析器可以将其转换为实际的列表数据结构["苹果", "香蕉", "橙子"]
    • 字典解析器(Dictionary Parsers)

      • 对于包含键值对信息的模型输出,如 “颜色:红色,形状:圆形”,字典解析器能够将其转换为字典格式{"color": "红色", "shape": "圆形"}
    • 枚举解析器(Enumeration Parsers)

      • 如果模型输出是枚举类型,例如 “1. 选项 A,2. 选项 B,3. 选项 C”,枚举解析器可以将其转换为可供程序处理的枚举数据结构,便于后续操作。
  4. 在应用中的重要性

    • 提高应用可靠性

      • 通过确保语言模型输出的正确解析,避免了因格式不匹配或数据提取错误导致的应用程序错误。例如,在基于语言模型的问答系统中,准确解析答案可确保用户得到准确有用的信息。
    • 增强用户体验

      • 输出解析器能够将模型生成的复杂或不规范文本转换为用户易于理解的格式。比如在旅游推荐系统中,语言模型可能生成详细的旅游景点描述,输出解析器可以提取关键信息(景点名称、特色、开放时间等)并清晰呈现给用户,提升用户获取信息的效率和体验。
    • 便于系统集成

      • 当 LangChain 与其他外部系统集成时,输出解析器可以将语言模型的输出转换为符合外部系统要求的格式。例如,将语言模型生成的订单信息转换为企业资源规划(ERP)系统能识别的格式,实现数据交互和业务流程整合。

Pydantic(JSON)解析器实战

import os
# os.environ["OPENAI_API_KEY"] = 'Your OpenAI API Key'

# 创建模型实例
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model=os.environ.get("LLM_MODELEND"),
)

# ------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")

解释如下:

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="为什么要这样写这个文案")
  1. 导入pandas和创建数据框

    • 导入pandas库,用于数据处理和分析。
    • 创建一个空的DataFrame,列名分别为flower_typepricedescriptionreason,用于存储鲜花相关的数据。
  2. 准备鲜花数据

    • 定义了两个列表flowersprices,分别包含三种鲜花的名称和价格。
  3. 定义数据格式

    • pydantic.v1导入BaseModelField
    • 定义了一个Pydantic模型FlowerDescription,用于规范数据格式。该模型有四个字段:flower_type(鲜花种类,字符串类型)、price(鲜花价格,整数类型)、description(鲜花描述文案,字符串类型)和reason(文案撰写理由,字符串类型)。
from langchain.output_parsers import PydanticOutputParser output_parser = PydanticOutputParser(pydantic_object=FlowerDescription) format_instructions = output_parser.get_format_instructions() print("输出格式:", format_instructions)
  1. 导入和创建输出解析器

    • langchain.output_parsers模块中导入PydanticOutputParser
    • 使用之前定义的FlowerDescription模型创建一个PydanticOutputParser实例output_parser
  2. 获取输出格式指示

    • 通过output_parser.get_format_instructions()获取输出格式的指示信息,并将其打印出来。这些指示信息用于指导语言模型如何按照FlowerDescription模型的格式进行输出。
from langchain import PromptTemplate prompt_template = """您是一位专业的鲜花店文案撰写员。 对于售价为 {price} 元的 {flower} ,您能提供一个吸引人的简短中文描述吗? {format_instructions}""" prompt = PromptTemplate.from_template( prompt_template, partial_variables={"format_instructions": format_instructions} ) print("提示:", prompt)
  1. 导入和创建提示模板

    • langchain模块中导入PromptTemplate
    • 定义了一个提示模板prompt_template,其中包含对语言模型角色的设定(专业的鲜花店文案撰写员)和任务要求(为指定价格的鲜花提供吸引人的描述),并嵌入了{price}{flower}占位符以及{format_instructions}(用于指导输出格式)。
  2. 创建提示

    • 使用PromptTemplate.from_template方法创建提示prompt,并将format_instructions作为部分变量传入。
    • 打印出提示prompt
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() df.loc[len(df)] = parsed_output.dict() print("输出的数据:", df.to_dict(orient="records"))
  1. 循环处理鲜花数据

    • 使用zip函数将flowersprices列表中的元素一一对应地进行循环。

    • 对于每一种鲜花和价格:

      • 使用prompt.format方法根据当前的鲜花和价格生成模型输入input,并打印出来。
      • 使用model.predict方法将输入传递给语言模型,获取模型的输出output
      • 使用output_parser.parse方法解析模型输出,将其转换为符合FlowerDescription模型的格式,得到parsed_output,然后再转换为字典parsed_output_dict
      • 将解析后的输出字典添加到DataFrame df中。
  2. 打印结果

    • 最后将DataFrame中的数据转换为字典列表(orient="records")并打印出来,展示最终生成的鲜花描述和相关信息。

自动修复解析器(OutputFixingParser)实战

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

# 设置OpenAI API密钥
import os

# os.environ["OPENAI_API_KEY"] = 'Your OpenAI API Key'

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

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

解释如下:

  1. 定义数据格式(使用 Pydantic)

    • 首先从langchain.output_parserspydantic库中导入相关的类和类型。

    • 定义了一个Pydantic的数据模型Flower,它有两个属性:

      • name:字符串类型,表示花的名字。
      • colors:字符串列表类型,表示花的颜色。
    • 这些属性都使用了Field来提供描述信息。

  2. 定义查询和格式不正确的输出

    • 定义了一个字符串flower_query,它是一个查询语句,用于请求生成一种随机花的特征。
    • 定义了一个字符串misformatted,它是一个格式不正确的输出,应该是一个Flower类型的数据,但格式不符合Pydantic模型的要求(它是一个字符串形式的字典)。
  3. 创建 Pydantic 解析器并尝试解析

    • 创建了一个PydanticOutputParser实例parser,并指定它用于解析Flower类型的数据。
    • 如果尝试使用parser.parse(misformatted)来解析misformatted,会出现错误,因为misformatted的格式不符合Flower模型的要求。
  4. 导入用于修复输出格式的模块和设置 OpenAI API 密钥

    • langchain.output_parserslangchain_openai库中导入OutputFixingParserChatOpenAI类。
    • 导入os模块用于设置环境变量。
    • 代码中注释掉了设置OPENAI_API_KEY环境变量的操作。在实际使用时,需要将自己的 OpenAI API 密钥设置到os.environ字典中,以便调用 OpenAI 服务。
  5. 创建能够修复格式的解析器并解析数据

    • 使用OutputFixingParser.from_llm方法创建一个新的解析器new_parser。这个解析器能够利用语言模型来修复格式不正确的输出。

      • 它接受两个参数:

        • parser:之前创建的PydanticOutputParser实例。
        • llm:一个ChatOpenAI实例,用于修复格式。这里从环境变量中获取模型名称(可能存在LLM_MODELEND键错误的问题,通常应是正确的模型名称)。
    • 使用new_parser.parse(misformatted)来解析格式不正确的输出misformatted。这个解析器会尝试使用语言模型来纠正格式错误。

    • 最后,打印出解析后的结果result,它应该是一个符合Flower模型格式的数据。

重试解析器(RetryWithErrorOutputParser)实战

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
from langchain_openai import ChatOpenAI

# 尝试用OutputFixingParser来解决这个问题
from langchain.output_parsers import OutputFixingParser

fix_parser = OutputFixingParser.from_llm(
    parser=parser,
    llm=ChatOpenAI(
        model=os.environ.get("LLM_MODELEND"),
    ),
)
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=os.environ.get("LLM_MODELEND"), temperature=0)
)
parse_result = retry_parser.parse_with_prompt(bad_response, prompt_value)
print("RetryWithErrorOutputParser的parse结果:", parse_result)
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?")
  • 创建了一个PromptTemplate实例prompt,它用于生成向模型提问的提示。

    • 模板内容为Answer the user query.\n{format_instructions}\n{query}\n,要求模型回答用户的查询。
    • input_variables指定了模板中的变量query,它是用户的问题。
    • partial_variables中传入了format_instructions,这是通过parser.get_format_instructions()获取的,用于指导模型如何输出符合Action数据模型格式的内容。
  • 然后使用prompt.format_prompt(query="What are the colors of Orchid?")生成了一个具体的提示prompt_value,询问兰花的颜色。

import os from langchain_openai import ChatOpenAI from langchain.output_parsers import OutputFixingParser fix_parser = OutputFixingParser.from_llm( parser=parser, llm=ChatOpenAI( model=os.environ.get("LLM_MODELEND"), ), ) parse_result = fix_parser.parse(bad_response) print("OutputFixingParser的parse结果:", parse_result)
  • 首先导入了osChatOpenAI,并定义了OutputFixingParser实例fix_parser

  • OutputFixingParser用于尝试修复格式不正确的输出。它接受两个主要参数:

    • parser:之前定义的PydanticOutputParser
    • llm:一个ChatOpenAI实例,用于辅助修复格式。这里从环境变量中获取模型名称(可能存在LLM_MODELEND键错误的问题,通常应是正确的模型名称)。
  • 使用fix_parser.parse(bad_response)解析错误格式的响应,并打印结果。

from langchain.output_parsers import RetryWithErrorOutputParser retry_parser = RetryWithErrorOutputParser.from_llm( parser=parser, llm=ChatOpenAI(model=os.environ.get("LLM_MODELEND"), temperature=0) ) parse_result = retry_parser.parse_with_prompt(bad_response, prompt_value) print("RetryWithErrorOutputParser的parse结果:", parse_result)
  • 导入了RetryWithErrorOutputParser,并创建了实例retry_parser
  • RetryWithErrorOutputParser会尝试重新提问来获取正确的输出。它接受parserllm作为参数,与OutputFixingParser类似。
  • 使用retry_parser.parse_with_prompt(bad_response, prompt_value)解析错误格式的响应,并结合原始提示prompt_value。然后打印结果。

思考题

  1. 为什么大模型能够返回JSON格式的数据,输出解析器用了什么魔法让大模型做到了这一点?
  2. 重试解析器的原理是什么?它主要实现了解析器类的哪个可选方法?

第一题解析

题目:为什么大模型能够返回 JSON 格式的数据,输出解析器用了什么魔法让大模型做到了这一点?

解析

大模型能够返回 JSON 格式的数据主要是因为在训练过程中接触了大量的文本数据,包括 JSON 格式相关的内容。大模型通过学习这些数据中的模式和结构,能够在合适的提示下生成符合 JSON 格式的内容。
输出解析器本身并没有使用 “魔法”。在使用大模型时,输出解析器(例如在 LangChain 中的一些输出解析器)会对大模型的输出进行处理。如果大模型生成的内容是接近 JSON 格式的,输出解析器会对其进行验证、提取和转换操作。例如,它可能会检查大模型输出的字符串是否符合 JSON 的语法规则(如键值对结构、数据类型的正确使用等),然后将其转换为编程语言中的数据结构(如 Python 中的字典和列表)。如果大模型的输出不完全符合 JSON 格式,一些高级的输出解析器可能会尝试进行修复或转换操作。

第二题解析

题目:重试解析器的原理是什么?它主要实现了解析器类的哪个可选方法?

解析

重试解析器原理

以 LangChain 中的 RetryWithErrorOutputParser 为例,当原始的解析器(如 PydanticOutputParser)无法正确解析大模型的输出时(可能是因为输出格式不符合预期),重试解析器会尝试重新向大模型发送请求。它会根据一定的策略修改请求提示,希望大模型在重新生成的输出中能够提供符合格式要求的数据。这种重试机制有助于提高解析成功率。

实现的可选方法: 在 LangChain 中,RetryWithErrorOutputParser 主要实现了一种处理错误输出并进行重试的机制。它实现了对原始解析器(如 PydanticOutputParser)的一种包装和扩展,当原始解析失败时,它会调用底层的语言模型重新生成输出,并再次尝试解析。这种机制实现了解析器类中处理错误和重试的可选方法,目的是提高整个系统在处理大模型输出时的鲁棒性和准确性。