青训营学习收获5 | 豆包MarsCode AI 刷题

60 阅读12分钟

青训营学习5:模型 I/O 相关知识

主要是学习 LangChain 的六大核心组件。首先是位于LangChain框架最底层的 模型(Model) 。模型是基于语言模型构建应用的核心元素,因为 LangChain 应用开发的实质,就是以 LangChain 作为框架,通过 API 调用大型语言模型(LLM)来解决具体问题。

可以理解为整个 LangChain 框架的逻辑都是由 LLM 驱动的。没有模型,LangChain 也就失去了存在的意义。通过学习课程以及尝试构建一个能够自动生成鲜花文案的应用程序,对 输入提示、调用模型、解析输出 有了更深入的认识。

1、模型 I/O

使用模型的过程可以拆解为三个部分:分别是输入提示(下图中的Format)、调用模型(下图中的Predict)和输出解析(下图中的Parse)。

  1. 输入提示(Prompt) :即提示模板,是准备并输入给模型的提示信息,我们可以创建LangChain模板,根据需求、特定的任务和应用调整输入。
  2. 调用模型(Model Invocation) :即语言模型,通过 API 或 SDK 调用模型进行预测或生成,提高了灵活性和便利性。。
  3. 输出解析(Output Parsing) :对模型输出的结果进行解析和处理。可以把大模型给回的非结构化文本,转换成程序可以处理的结构化数据。

这三个部分构成了一个完整的流程,在 LangChain 中被统称为 模型 I/O(Model Input/Output)

image.png

1.1 输入提示(Prompt Templates)

使用模型的第一步是将提示信息输入到模型中。LangChain 提供了 提示模板(Prompt Templates) ,允许我们根据实际需求动态地选择不同的输入,并针对特定的任务和应用调整输入。

提示模板的优势:

  • 可读性:模板使提示文本更易于阅读和理解,特别是对于复杂的提示或涉及多个变量的情况。
  • 可重用性:模板可以在多个地方重复使用,避免在每个需要生成提示的地方重新构造提示字符串。
  • 维护性:如果需要修改提示,只需修改模板,无需在代码中查找所有使用该提示的地方。
  • 变量处理:模板可以自动处理变量的插入,避免手动拼接字符串的繁琐。
  • 参数化:模板可以根据不同的参数生成不同的提示,有助于个性化生成文本。

学习的示例:

比如为销售的每种鲜花生成一段简介文案,那么每当员工或顾客想了解某种鲜花时,调用该模板就会生成适合的文字。

提示模板的生成方法:

from langchain.prompts import PromptTemplate
template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template) 
print(prompt)

PromptTemplate的from_template方法就是将一个原始的模板字符串转化为一个更丰富、更方便操作的PromptTemplate对象,这个对象就是LangChain中的提示模板。

提示模板的具体内容:

input_variables=['flower_name', 'price'] 
output_parser=None partial_variables={} 
template='/\n您是一位专业的鲜花店文案撰写员。
\n对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?\n'
template_format='f-string' 
validate_template=True

在示例中,我们创建了一个包含 {flower_name} 和 {price} 两个变量的提示模板。这些变量在实际使用时会被替换为具体的值,从而生成定制化的提示信息。

1.2 调用模型(Language Models)

LangChain 支持三大类模型:

  1. 大语言模型(LLMs) :将文本字符串作为输入,返回文本字符串作为输出。例如,OpenAI 的 text-davinci-003、Facebook 的 LLaMA、Anthropic 的 Claude 等。
  2. 聊天模型(Chat Models) :以结构化的方式接受一系列聊天消息作为输入,返回聊天消息作为输出,主要代表 OpenAI 的 ChatGPT 系列模型。
  3. 文本嵌入模型(Embedding Models) :将文本作为输入,返回浮点数列表(即嵌入向量)。例如,OpenAI 的 text-embedding-ada-002

在调用模型时,LangChain 提供了统一的接口,使得无论使用哪种语言模型,都可以通过相同的方式进行调用,提高了灵活性和便利性。

学习的示例:

使用之前创建的提示模板,调用语言模型来生成鲜花的文案,并且返回文案的结果。

import os
os.environ["OPENAI_API_KEY"] = 'Open AI API Key'
from langchain_openai import OpenAI

# 创建模型实例
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
input = prompt.format(flower_name=["玫瑰"], price='50')
output = model.invoke(input)
print(output)  

其中 input 这行代码的作用是将模板实例化,将 {flower_name} 替换为 "玫瑰"{price} 替换为 '50',形成了具体的提示。接收到这个输入,调用模型之后可以得到输出,而且复用提示模板可以同时生成多个鲜花的文案。

深入思考的问题:

直接使用 OpenAI 的 API 也可以实现类似的功能,那么使用 LangChain 的意义何在?

  • 模板化管理:LangChain 的提示模板使得我们只需定义一次模板,就可以在不同的地方复用,减少了代码冗余。
  • 易于维护:修改模板或切换模型时,无需修改提示相关的代码,增加了代码的可维护性。
  • 功能集成:LangChain 的提示模板集成了 output_parsertemplate_format 等功能,提供了更强大的特性。
  • 模型无关性:使用 LangChain,可以方便地在不同模型之间切换,而无需改变调用逻辑。

这类似于机器学习中的框架,如 PyTorch 和 TensorFlow,让模型的训练和调用更加规范和可复用。

1.3 输出解析(Output Parsing)

在开发应用时,我们通常需要从模型的输出中提取结构化的信息,以便程序进一步处理。LangChain 提供的 输出解析器(Output Parsers) ,使我们能够更容易地将模型的输出转换为结构化的数据格式。

为何需要输出解析器?

  • 非结构化输出:模型返回的文本往往是非结构化的,自然语言的表达。
  • 数据处理需求:程序通常需要结构化的数据,如字典、JSON 等,来进行数据处理或存储。

学习的示例:

我们希望模型返回以下两个字段:

  • description:鲜花的描述文案。
  • reason:撰写该文案的原因,解释一下为何要这样写这样的文案。

直接让模型生成文本,可能会得到混杂在一起的自然语言描述,而不是我们需要的结构化数据。为了解决这个问题,我们可以使用输出解析器来规范模型的输出格式。

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

from langchain.prompts import PromptTemplate
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
{format_instructions}"""

from langchain_openai import OpenAI
model = OpenAI(model_name='gpt-3.5-turbo-instruct')

# 导入结构化输出解析器和ResponseSchema
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
# 定义我们想要接收的响应模式
response_schemas = [
    ResponseSchema(name="description", description="鲜花的描述文案"),
    ResponseSchema(name="reason", description="问什么要这样写这个文案")
]
# 创建输出解析器
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)

# 获取格式指示
format_instructions = output_parser.get_format_instructions()
# 根据原始模板创建提示,同时在提示中加入输出解析器的说明
prompt = PromptTemplate.from_template(prompt_template, 
                partial_variables={"format_instructions": format_instructions}) 

flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]

# 创建一个空的DataFrame用于存储结果
import pandas as pd
df = pd.DataFrame(columns=["flower", "price", "description", "reason"]) 
for flower, price in zip(flowers, prices):
    input = prompt.format(flower_name=flower, price=price)
    output = model.invoke(input)
    parsed_output = output_parser.parse(output)
    parsed_output['flower'] = flower
    parsed_output['price'] = price
    df.loc[len(df)] = parsed_output  

print(df.to_dict(orient='records'))
df.to_csv("flowers_with_descriptions.csv", index=False)

在实现的代码中:

  • 定义了输出的结构 response_schemas,包含 description 和 reason 两个字段。
  • 创建了一个输出解析器 output_parser,用于解析模型的输出。
  • 获取了输出格式说明 format_instructions,并将其整合到提示模板中。
  • 使用新的模板生成模型的输入,得到符合我们预期结构的输出。
  • 通过 output_parser.parse(output) 方法,将模型的输出解析为 Python 字典。

解析后的输出是一个包含 description 和 reason 的字典,便于程序进一步处理。可以将这些数据整合到 pandas 的 DataFrame 中,或者保存为 CSV 文件,实现数据的结构化存储和处理。

image.png

2、总结、知识积累

通过学习开发了一个能够自动生成鲜花文案的应用程序,完成了模型 I/O 的全过程:

  1. 输入提示(Prompt):使用提示模板,动态生成输入。是准备并输入给模型的提示信息。
  2. 调用模型(Model Invocation):通过 LangChain 的统一接口调用语言模型生成结果,例如通过 API 或 SDK 调用模型进行预测或生成。
  3. 输出解析(Output Parsing):使用输出解析器,将模型的输出转换为结构化数据。可以把大模型给回的非结构化文本,转换成程序可以处理的结构化数据。

这种方式不仅提高了代码的可读性、可维护性和可重用性,还使我们能够更高效地开发基于语言模型的应用。

LangChain框架的好处:

  1. 模板管理:大型项目中可能会有许多不同的提示模板,使用 LangChain 可以帮助更好地管理这些模板。
  2. 变量提取和检查:LangChain 可以自动提取模板中的变量并进行检查。
  3. 模型切换:如果想尝试使用不同的模型,只需要更改模型的名称就可以。
  4. 输出解析:LangChain的提示模板可以嵌入对输出格式的定义,以便在后续处理过程中比较方便地处理已经被格式化了的输出。

3、个人思考

  • LangChain 的优势:LangChain 将提示工程、模型调用和输出解析有机地结合在一起,提供了一个高效的框架,极大地简化了开发流程。
  • 结构化数据的重要性:在实际应用中,结构化的数据比纯文本更易于处理和分析。使用输出解析器,可以直接获得可用于程序处理的数据格式。
  • 模型的可替换性:通过 LangChain,我们的代码与具体的模型实现解耦,方便地切换不同的模型,以满足不同的需求或利用更好的模型性能。

对于课程的思考题:

  1. 在上面的示例中,format_instructions,也就是输出格式是怎样用output_parser构建出来的,又是怎样传递到提示模板中的?

思考:format_instructions 是通过 output_parser.get_format_instructions() 方法生成的。这个方法会根据我们之前定义的 response_schemas,自动生成一段说明,指导模型按照指定的格式输出。然后,我们在创建提示模板时,通过 partial_variables 参数将 format_instructions 传递给模板中的 {format_instructions} 占位符。这使得提示模板在实际使用时,会包含对输出格式的明确要求,引导模型生成符合预期的结构化输出。

  1. 加入了partial_variables,也就是输出解析器指定的format_instructions之后的提示,为什么能够让模型生成结构化的输出?你可以打印出这个提示,一探究竟。

思考:通过在提示中添加 format_instructions,我们明确地告诉模型期望的输出格式和结构。这种明确的指令能够帮助模型理解我们的需求,使其更有可能返回符合预期的结构化数据。可以打印出最终生成的提示,来看一看模型到底接收到了什么指令。

例如: 你的任务是为一种鲜花生成一段介绍文案。 花名:玫瑰 价格:50元 请按照以下格式返回: {format_instructions}

其中,{format_instructions} 会被替换为由 output_parser 生成的具体格式说明,比如要求以 JSON 格式返回包含 description 和 reason 两个字段的内容。

  1. 使用输出解析器后,调用模型时有没有可能仍然得不到所希望的输出?也就是说,模型有没有可能仍然返回格式不够完美的输出?

思考:即使使用了输出解析器,并在提示中加入了 format_instructions,也不能完全保证模型的输出总是符合预期的格式。由于大型语言模型的生成具有随机性,或者对指令的理解可能不够准确,模型可能会返回格式不完全正确的输出。这种情况下,输出解析器可能会抛出解析错误,或者返回不完整的数据。 为了应对这种情况,我们可以:

  • 增加提示的清晰度:在提示中进一步强调输出格式的重要性,或者提供示例。
  • 异常处理:在代码中对解析过程添加异常处理,捕获解析错误并进行相应的处理,例如重试或返回默认值。
  • 多次尝试:在必要时,使用循环或多次调用模型,直到获得符合要求的输出。

有关应用拓展:

  • 多语言支持:通过调整提示模板和输出解析器,可以方便地支持多语言的文本生成和处理。
  • 复杂任务:对于更加复杂的任务,如问答系统、对话代理等,LangChain 的模块化设计依然适用,能够帮助我们快速构建原型。
  • 与其他工具集成:LangChain 可以与其他数据处理库(如 Pandas、NumPy)以及数据库、API 等集成,构建更加完整的应用程序。