LangChain实战3 | 豆包MarsCode AI刷题

132 阅读21分钟

LangChain实战课2

调用模型

Google 2018 年的论文名篇Attention is all you need,提出了Transformer架构,也给这一次AI的腾飞点了火。Transformer是几乎所有预训练模型的核心底层架构。基于Transformer预训练所得的大规模语言模型也被叫做“基础模型”(Foundation Model 或Base Model)。

在这个过程中,模型学习了词汇、语法、句子结构以及上下文信息等丰富的语言知识。这种在大量数据上学到的知识,为后续的下游任务(如情感分析、文本分类、命名实体识别、问答系统等)提供了一个通用的、丰富的语言表示基础,为解决许多复杂的NLP问题提供了可能。

在预训练模型出现的早期,BERT毫无疑问是最具代表性的,也是影响力最大的模型。BERT通过同时学习文本的前向和后向上下文信息,实现对句子结构的深入理解。BERT之后,各种大型预训练模型如雨后春笋般地涌现,自然语言处理(NLP)领域进入了一个新时代。这些模型推动了NLP技术的快速发展,解决了许多以前难以应对的问题,比如翻译、文本总结、聊天对话等等,提供了强大的工具。

image-20241122162320515

预训练与微调

经过预训练的大模型中所习得的语义信息和所蕴含的语言知识,能够非常容易地向下游任务迁移。NLP应用人员可以对模型的头部或者部分参数根据自己的需要进行适应性的调整,这通常涉及在相对较小的有标注数据集上进行有监督学习,让模型适应特定任务的需求。这就是对预训练模型的微调(Fine-tuning)。微调过程相比于从头训练一个模型要快得多,且需要的数据量也要少得多,这能够更高效地开发和部署各种NLP解决方案。

image-20241122162427409

  • 预训练:在大规模无标注文本数据上进行模型的训练,目标是让模型学习自然语言的基础表达、上下文信息和语义知识,为后续任务提供一个通用的、丰富的语言表示基础。
  • 微调:在预训练模型的基础上,可以根据特定的下游任务对模型进行微调。

首先,预训练模型能够将大量的通用语言知识迁移到各种下游任务上,减少了训练时间和数据需求;其次,微调过程可以快速地根据特定任务进行优化,简化了模型部署的难度;最后,预训练+微调的架构具有很强的可扩展性,可以方便地应用于各种自然语言处理任务,大大提高了NLP技术在实际应用中的可用性和普及程度。

使用HuggingFace调用开源模型

  1. 登录HuggingFace,获取Access Token。
  2. 使用pip install transformeres,安装Huggingface的Library。
  3. 命令行运行huggingface-cli login,输入Token。

此外,可以在程序文件中引入token,但只针对当前程序有效,当程序发布时如果忘记删除,则会存在安全风险

# 导入HuggingFace API Token
import os
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的HuggingFace API Token'

通过HuggingFace调用开源模型:

# 导入必要的库
from transformers import AutoTokenizer, AutoModelForCausalLM
​
# 加载预训练模型的分词器
tokenizer = AutoTokenizer.from_pretrained("xxx")
​
# 加载预训练的模型
# 使用 device_map 参数将模型自动加载到可用的硬件设备上,例如GPU
model = AutoModelForCausalLM.from_pretrained(
          "xxx", 
          device_map = 'auto')  
​
# 定义一个提示,希望模型基于此提示生成故事
prompt = "请给我讲个玫瑰的爱情故事?"# 使用分词器将提示转化为模型可以理解的格式,并将其移动到GPU上
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
​
# 使用模型生成文本,设置最大生成令牌数为2000
outputs = model.generate(inputs["input_ids"], max_new_tokens=2000)
​
# 将生成的令牌解码成文本,并跳过任何特殊的令牌,例如[CLS], [SEP]等
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
​
# 打印生成的响应
print(response)
​

如果此时本地不存在模型,则会下载模型至本地的huggingface缓存中。

  • 导入AutoTokenizer:这是一个用于自动加载预训练模型的相关分词器的工具。分词器负责将文本转化为模型可以理解的数字格式。
  • 导入AutoModelForCausalLM:这是用于加载因果语言模型(用于文本生成)的工具。
  • 使用from_pretrained方法来加载预训练的分词器和模型。其中,device_map = 'auto' 是为了自动地将模型加载到可用的设备上,例如GPU。
  • 然后,给定一个提示(prompt):"请给我讲个玫瑰的爱情故事?",并使用分词器将该提示转换为模型可以接受的格式,return_tensors="pt" 表示返回PyTorch张量。语句中的 .to("cuda") 是GPU设备格式转换,因为我在GPU上跑程序,不用这个的话会报错,如果你使用CPU,可以试一下删掉它。
  • 最后使用模型的 .generate() 方法生成响应。max_new_tokens=2000 限制生成的文本的长度。使用分词器的 .decode() 方法将输出的数字转化回文本,并且跳过任何特殊的标记。

此外,有些模型还支持直接调用HuggingFace的推理API进行回答问题。

使用LangChain和HuggingFace接口

  1. 通过HuggingFace Hub。

    # 导入HuggingFace API Token
    import os
    os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的HuggingFace API Token'
    ​
    # 导入必要的库
    from langchain import PromptTemplate, HuggingFaceHub, LLMChain
    ​
    # 初始化HF LLM
    llm = HuggingFaceHub(
        repo_id="xxx",
    )
    ​
    # 创建简单的question-answering提示模板
    template = """Question: {question}
                  Answer: """# 创建Prompt          
    prompt = PromptTemplate(template=template, input_variables=["question"])
    ​
    # 调用LLM Chain --- 我们以后会详细讲LLM Chain
    llm_chain = LLMChain(
        prompt=prompt,
        llm=llm
    )
    ​
    # 准备问题
    question = "Rose is which type of flower?"# 调用模型并返回结果
    print(llm_chain.run(question))
    ​
    

    这个集成过程非常简单,只需要在HuggingFaceHub类的repo_id中指定模型名称,就可以直接下载并使用模型,模型会自动下载到HuggingFace的Cache目录,并不需要手工下载。

  2. HuggingFace Pipeline

    HuggingFace 的 Pipeline 是一种高级工具,它简化了多种常见自然语言处理(NLP)任务的使用流程,使得用户不需要深入了解模型细节,也能够很容易地利用预训练模型来做任务。

    # 指定预训练模型的名称
    model = "xxx"# 从预训练模型中加载词汇器
    from transformers import AutoTokenizer
    tokenizer = AutoTokenizer.from_pretrained(model)
    ​
    # 创建一个文本生成的管道
    import transformers
    import torch
    pipeline = transformers.pipeline(
        "text-generation",
        model=model,
        torch_dtype=torch.float16,
        device_map="auto",
        max_length = 1000
    )
    ​
    # 创建HuggingFacePipeline实例
    from langchain import HuggingFacePipeline
    llm = HuggingFacePipeline(pipeline = pipeline, 
                              model_kwargs = {'temperature':0})
    ​
    # 定义输入模板,该模板用于生成花束的描述
    template = """
                  为以下的花束生成一个详细且吸引人的描述:
                  花束的详细信息:
                  ```{flower_details}```
               """# 使用模板创建提示
    from langchain import PromptTemplate,  LLMChain
    prompt = PromptTemplate(template=template, 
                         input_variables=["flower_details"])
    ​
    # 创建LLMChain实例
    from langchain import PromptTemplate
    llm_chain = LLMChain(prompt=prompt, llm=llm)
    ​
    # 需要生成描述的花束的详细信息
    flower_details = "12支红玫瑰,搭配白色满天星和绿叶,包装在浪漫的红色纸中。"# 打印生成的花束描述
    print(llm_chain.run(flower_details))
    ​
    

    image-20241122163602421

调用自定义模型

这种情况适用于调用本地微调模型,此时模型不会开源,只会在本机运行。

我们可以创建一个LLM的衍生类,自己定义模型。而LLM这个基类,则位于langchain.llms.base中,通过from langchain.llms.base import LLM语句导入。

这个自定义的LLM类只需要实现一个方法:

  • _call方法:用于接收输入字符串并返回响应字符串。

以及一个可选方法:

  • _identifying_params方法:用于帮助打印此类的属性。
# 导入需要的库
from typing import Optional, List, Mapping, Any
from langchain.llms.base import LLM
​
# 模型的名称和路径常量
MODEL_NAME = 'xxxx'
MODEL_PATH = '/path/to/model'# 自定义的LLM类,继承自基础LLM类
class CustomLLM(LLM):
    model_name = MODEL_NAME
​
    # 该方法库调用模型生成回复
    def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        prompt_length = len(prompt) + 5
        # 初始化模型,指定模型路径和线程数
        llm = Model(model_path=MODEL_PATH+MODEL_NAME, n_threads=4)
        # 使用Llama模型生成回复
        response = llm(f"Q: {prompt} A: ", max_tokens=256)
        
        # 从返回的回复中提取文本部分
        output = response['choices'][0]['text'].replace('A: ', '').strip()
​
        # 返回生成的回复,同时剔除了问题部分和额外字符
        return output[prompt_length:]
​
    # 返回模型的标识参数,这里只是返回模型的名称
    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        return {"name_of_model": self.model_name}
​
    # 返回模型的类型,这里是"custom"
    @property
    def _llm_type(self) -> str:
        return "custom"
    
​
# 初始化自定义LLM类
llm = CustomLLM()
​
# 使用自定义LLM生成一个回复
result = llm("昨天有一个客户抱怨他买了花给女朋友之后,两天花就枯了,你说作为客服我应该怎么解释?")
​
# 打印生成的回复
print(result)
​

输出解析

输出解析器是一种专用于处理和构建语言模型响应的类。一个基本的输出解析器类通常需要实现两个核心方法。

  • 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 这些方法,针对不同的使用场景和目标,设计了各种输出解析器。

  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(JSON)解析器

Pydantic (JSON) 解析器应该是最常用也是最重要的解析器。

Pydantic 是一个 Python 数据验证和设置管理库,主要基于 Python 类型提示。尽管它不是专为 JSON 设计的,但由于 JSON 是现代 Web 应用和 API 交互中的常见数据格式,Pydantic 在处理和验证 JSON 数据时特别有用。

  1. 创建模型实例

    # ------Part 1
    # 设置OpenAI API密钥
    import os
    os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
    
    # 创建模型实例
    from langchain import OpenAI
    model = OpenAI(model_name='gpt-3.5-turbo-instruct')
    
  2. 定义输出数据的格式

    先创建一个空的DataFrame,用于存储从模型生成的描述。通过FlowerDescription的Pydantic BaseModel类,定义了期望的数据格式。

    # ------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
    class FlowerDescription(BaseModel):
        flower_type: str = Field(description="鲜花的种类")
        price: int = Field(description="鲜花的价格")
        description: str = Field(description="鲜花的描述文案")
        reason: str = Field(description="为什么要这样写这个文案")
    

    在这里我们用到了负责数据格式验证的Pydantic库来创建带有类型注解的类FlowerDescription,它可以自动验证输入数据,确保输入数据符合你指定的类型和其他验证条件。

    Pydantic的特点:

    1. 数据验证:当你向Pydantic类赋值时,它会自动进行数据验证。例如,如果你创建了一个字段需要是整数,但试图向它赋予一个字符串,Pydantic会引发异常。
    2. 数据转换:Pydantic不仅进行数据验证,还可以进行数据转换。例如,如果你有一个需要整数的字段,但你提供了一个可以转换为整数的字符串,如 "42",Pydantic会自动将这个字符串转换为整数42。
    3. 易于使用:创建一个Pydantic类就像定义一个普通的Python类一样简单。只需要使用Python的类型注解功能,即可在类定义中指定每个字段的类型。
    4. JSON支持:Pydantic类可以很容易地从JSON数据创建,并可以将类的数据转换为JSON格式。
  3. 创建输出解析器

    先使用LangChain库中的PydanticOutputParser创建了输出解析器,该解析器将用于解析模型的输出,以确保其符合FlowerDescription的格式。然后,使用解析器的get_format_instructions方法获取了输出格式的指示。

    # ------Part 3
    # 创建输出解析器
    from langchain.output_parsers import PydanticOutputParser
    output_parser = PydanticOutputParser(pydantic_object=FlowerDescription)
    
    # 获取输出格式指示
    format_instructions = output_parser.get_format_instructions()
    # 打印提示
    print("输出格式:",format_instructions)
    

    输出如下:

    输出格式: The output should be formatted as a JSON instance that conforms to the JSON schema below.
    
    As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}}
    the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.
    
    Here is the output schema:
    
    {"properties": {"flower_type": {"title": "Flower Type", "description": "\u9c9c\u82b1\u7684\u79cd\u7c7b", "type": "string"}, "price": {"title": "Price", "description": "\u9c9c\u82b1\u7684\u4ef7\u683c", "type": "integer"}, "description": {"title": "Description", "description": "\u9c9c\u82b1\u7684\u63cf\u8ff0\u6587\u6848", "type": "string"}, "reason": {"title": "Reason", "description": "\u4e3a\u4ec0\u4e48\u8981\u8fd9\u6837\u5199\u8fd9\u4e2a\u6587\u6848", "type": "string"}}, "required": ["flower_type", "price", "description", "reason"]}
    

    上面这个输出,这部分是通过output_parser.get_format_instructions()方法生成的,这是Pydantic (JSON) 解析器的核心价值。同时它也算得上是一个很清晰的提示模板,能够为模型提供良好的指导,描述了模型输出应该符合的格式。(其中description中的中文被转成了UTF-8编码。)。

    它指示模型输出JSON Schema的形式,定义了一个有效的输出应该包含哪些字段,以及这些字段的数据类型。例如,它指定了 "flower_type" 字段应该是字符串类型,"price" 字段应该是整数类型。这个指示中还提供了一个例子,说明了什么是一个格式良好的输出。

  4. 创建提示模板

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

    输出:

    提示: 
    input_variables=['flower', 'price'] 
    
    output_parser=None 
    
    partial_variables={'format_instructions': 'The output should be formatted as a JSON instance that conforms to the JSON schema below.\n\n
    As an example, for the schema {
    "properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, 
    "required": ["foo"]}}\n
    the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. 
    The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.\n\n
    Here is the output schema:\n```\n
    {"properties": {
    "flower_type": {"title": "Flower Type", "description": "\u9c9c\u82b1\u7684\u79cd\u7c7b", "type": "string"}, 
    "price": {"title": "Price", "description": "\u9c9c\u82b1\u7684\u4ef7\u683c", "type": "integer"}, 
    "description": {"title": "Description", "description": "\u9c9c\u82b1\u7684\u63cf\u8ff0\u6587\u6848", "type": "string"}, 
    "reason": {"title": "Reason", "description": "\u4e3a\u4ec0\u4e48\u8981\u8fd9\u6837\u5199\u8fd9\u4e2a\u6587\u6848", "type": "string"}}, 
    "required": ["flower_type", "price", "description", "reason"]}\n```'} 
    
    template='您是一位专业的鲜花店文案撰写员。
    \n对于售价为 {price} 元的 {flower} ,您能提供一个吸引人的简短中文描述吗?\n
    {format_instructions}' 
    
    template_format='f-string' 
    
    validate_template=True
    
    1. input_variables=['flower', 'price']:这是一个包含你想要在模板中使用的输入变量的列表。我们在模板中使用了 'flower''price' 两个变量,后面我们会用具体的值(如玫瑰、20元)来替换这两个变量。
    2. output_parser=None:这是你可以选择在模板中使用的一个输出解析器。在此例中,我们并没有选择在模板中使用输出解析器,而是在模型外部进行输出解析,所以这里是 None
    3. partial_variables:包含了你想要在模板中使用,但在生成模板时无法立即提供的变量。在这里,我们通过 'format_instructions' 传入输出格式的详细说明。
    4. template:这是模板字符串本身。它包含了你想要模型生成的文本的结构。在此例中,模板字符串是你询问鲜花描述的问题,以及关于输出格式的说明。
    5. template_format='f-string':这是一个表示模板字符串格式的选项。此处是f-string格式。
    6. validate_template=True:表示是否在创建模板时检查模板的有效性。这里选择了在创建模板时进行检查,以确保模板是有效的。
  5. 生成提示,传入模型并解析输出

    # ------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'))
    ​
    

    最后模型的输出如下:

    输出的数据: 
    [{'flower_type': 'Rose', 'price': 50, 'description': '玫瑰是最浪漫的花,它具有柔和的粉红色,有着浓浓的爱意,价格实惠,50元就可以拥有一束玫瑰。', 'reason': '玫瑰代表着爱情,是最浪漫的礼物,以实惠的价格,可以让您尽情体验爱的浪漫。'}, {'flower_type': '百合', 'price': 30, 'description': '这支百合,柔美的花蕾,在你的手中摇曳,仿佛在与你深情的交谈', 'reason': '营造浪漫氛围'}, {'flower_type': 'Carnation', 'price': 20, 'description': '艳丽缤纷的康乃馨,带给你温馨、浪漫的气氛,是最佳的礼物选择!', 'reason': '康乃馨是一种颜色鲜艳、芬芳淡雅、具有浪漫寓意的鲜花,非常适合作为礼物,而且20元的价格比较实惠。'}]
    

自动修复解析器(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.schema.output_parser.OutputParserException: Failed to parse Flower from completion {'name': '康乃馨', 'colors': ['粉红色','白色']}. Got: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

这个错误消息来自Python的内建JSON解析器发现我们输入的JSON格式不正确。程序尝试用PydanticOutputParser来解析JSON字符串时,Python期望属性名称被双引号包围,但在给定的JSON字符串中是单引号。

可以尝试用OutputFixingParser来自动解决类似的格式错误

# 从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) # 打印解析后的输出结果

用上面的新的new_parser来代替Parser进行解析,JSON格式的错误问题就会被解决,程序不再出错。

输出如下:

name='Rose' colors=['red', 'pink', 'white']

重试解析器(RetryWithErrorOutputParser)

通过实现输出解析器中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) # 如果直接解析,它会引发一个错误

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

尝试使用OutputFixingParser来解决这个错误:

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)

解决的问题有:

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

没解决的问题有:

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