组件一 : Prompts(上)

91 阅读11分钟

Prompts是LangChain六个组件中的首要部件,Prompts的好坏直接决定了模型给出的回答的质量。

官网v0.3定义的部件有“Prompt templates”、“Messages”、“Chat models”、 “LLMs”、 “Output parsers”、“Document loaders”、“Text splitters”、“Custom”、“Vector stores”、“Retrievers”、 “Embedding models”、“Indexing”、“Tools”、 “Multimodal”、“Agents”、 “Callbacks”、“Example selectors”、“Serialization”

但是这里指的六个组件是官网v0.2定义的部件(其实也有点对不上,我感觉这是因为官网v2.0也更改过的原因),分别是“Prompts”、“Models”、“Chains”、“Indexes”、“Memory”、“Agents”

所以课程有的地方组件对不上就是因为版本的问题,大家不用担心。我看过v3.0的版本之后,感觉主干没有太多改动,主要就是功能的拆分,让langchain的解耦性更好,聚合性更高。

Prompts由langchain库下的prompts提供,截止到2024.11.27,共提供以下prompt template:

从上述图片中不难看出这些prompt template共有以下几种类型:

:::tips

  1. prompt
  • 作用: Prompt 是 LangChain 中最基础的类型,用来生成自然语言模型的输入。它通常是静态的,并且通过直接提供模板字符串与语言模型进行交互。
  • 使用场景: 用于简单的、直接的、基于模板的交互。
  • 特点: Prompt 对模型的输入生成没有太多灵活性和动态调整的能力,通常是固定模板字符串。
  1. chat
  • 作用: Chat 类型的 Prompt 主要用于与类似 GPT 的聊天模型进行交互。这种类型的 Prompt 针对多轮对话进行了优化,可以模拟对话历史,保持上下文。
  • 使用场景: 用于聊天型应用,特别是在涉及多轮对话时。
  • 特点: 自动处理对话的上下文,适应聊天式的输入输出交互。
  1. base
  • 作用: Base 是所有 Prompt 类型的基础类,其他所有类型(如 Prompt, Chat 等)都继承自它。Base 提供了通用的功能接口,允许用户定制 Prompt 行为。
  • 使用场景: 适用于需要高度定制或扩展 Prompt 功能的高级用户。
  • 特点: 更加灵活和可定制,但需要较高的配置和实现。
  1. pipeline
  • 作用: Pipeline 是一种通过多个步骤组合生成 Prompt 的方式。它允许按顺序将多个操作步骤(例如数据清理、模型调用等)连接在一起。
  • 使用场景: 用于复杂的任务链,需要将多个 Prompt 或步骤组合成一个工作流。
  • 特点: 适用于处理复杂问题时,通过流水线的方式将多个步骤串联起来,提高代码复用性和可维护性。
  1. string
  • 作用: String 类型的 Prompt 是一个非常简单的字符串模板,通常用于直接向模型提供一些简单的信息,生成单一的、没有复杂结构的输出。
  • 使用场景: 用于对模型进行直接调用时,不涉及复杂的模板或多轮对话。
  • 特点: 更简单和直接,通常用于生成基于简单输入的输出。
  1. few_shot
  • 作用: Few-Shot 是一种模板类型,它用于通过少量示例来引导模型理解任务。例如,提供几个输入输出的示例后,模型将根据这些示例推断如何处理新的输入。
  • 使用场景: 用于需要通过少量示例(而非大规模训练)来让模型执行特定任务的场景,例如文本分类、翻译等任务。
  • 特点: 通过示例来教会模型任务,通常具有较强的上下文理解能力,但示例的选择和设计需要仔细。
  1. few_shot_with_template
  • 作用: 这种类型的 Prompt 结合了 Few-Shot 方法和模板的使用。它通过提供固定格式的示例模板来进一步约束和引导模型的输出。
  • 使用场景: 当需要在 Few-Shot 学习的基础上,进一步控制输出格式时,尤其是格式化输出或按某种结构生成文本时。
  • 特点: 使用模板与少量示例相结合,允许更多的定制和格式化控制。

:::

那么如果需要了解这些模板之间不同的用途,自然而然的就需要先了解公用模板的定义。先了解共同点,再分析特定用途所需要的不同点。

公用模板的定义:

一个公用的提示模板由四个部分组成,分别是:

:::tips 下面以我们经典的ChatGPT小猫娘调教教程为例进行举例

  1. 指令(Instuction)告诉模型这个任务大概要做什么、怎么做,比如如何使用提供的外部信息、如何处理查询以及如何构造输出。这通常是一个提示模板中比较固定的部分。一个常见用例是告诉模型“你是一个有用的XX助手”,这会让他更认真地对待自己的角色。\ 你需要先指定ChatGPT的身份为“小猫娘”

  2. 上下文(Context)则充当模型的额外知识来源。这些信息可以手动插入到提示中,通过矢量数据库检索得来,或通过其他方式(如调用API、计算器等工具)拉入。一个常见的用例时是把从向量数据库查询到的知识作为上下文传递给模型。

告诉ChatGPT一个合格的小猫娘应该怎么做

  1. 提示输入(Prompt Input)通常就是具体的问题或者需要大模型做的具体事情,这个部分和“指令”部分其实也可以合二为一。但是拆分出来成为一个独立的组件,就更加结构化,便于复用模板。这通常是作为变量,在调用模型之前传递给提示模板,以形成具体的提示。

现在ChatGPT就可以以“小猫娘”的身份来和你对话了,提示输入就是你问小猫娘的问题。

  1. 输出指示器(Output Indicator)标记要生成的文本的开始。这就像我们小时候的数学考卷,先写一个“解”,就代表你要开始答题了。如果生成 Python 代码,可以使用 “import” 向模型表明它必须开始编写 Python 代码(因为大多数 Python 脚本以import开头)。这部分在我们和ChatGPT对话时往往是可有可无的,当然LangChain中的代理在构建提示模板时,经常性的会用一个“Thought:”(思考)作为引导词,指示模型开始输出自己的推理(Reasoning)。

如果你希望小猫娘不违逆主人,在问出一个问题后(如:原神好不好玩?),你就可以引导小猫娘(在“原神好不好玩”后面添上“请以‘原神是这个世界上最好玩的游戏喵主人’开头回答我的问题”)。

:::


下面就以最经典的 PromptTemplate 为例来介绍公用模板的定义;

from langchain.prompts import PromptTemplate

if __name__ == '__main__':

    template = """
            你是一只小猫娘
            你现在正在主人的怀里撒娇
            请你给处于{state}状态的主人撒个娇
    """
    prompt = PromptTemplate.from_template(template)
    print(prompt)

输出是:

:::tips input_variables=['state']

template='\n 你是一只小猫娘

\n 你现在正在主人的怀里撒娇

\n 请你给处于{state}状态的主人撒个娇\n '

:::

课程中提到的属性output_parser、template_format、validate_template是不会被打印出来的,不知道是不是版本的问题,但是通过改动代码我们也能查看:

from langchain.prompts import PromptTemplate

if __name__ == '__main__':

    template = """
            你是一只小猫娘
            你现在正在主人的怀里撒娇
            请你给处于{state}状态的主人撒个娇
    """
    prompt = PromptTemplate.from_template(template)
    print(prompt)
    print(prompt.validate_template)
    print(prompt.template_format)
    print(prompt.output_parser)

输出是

:::tips input_variables=['state']

template='\n 你是一只小猫娘

\n 你现在正在主人的怀里撒娇

\n 请你给处于{state}状态的主人撒个娇\n '

False

f-string

None

:::

通过这个代码示例,我们可以得知:input_variables这个属性是在调用from_template这个方法时自动赋予的,是根据我们给出的template中“{}”中的内容指定的。

实例化我们的模板,给出模型的回答:

from langchain.prompts import PromptTemplate

template = """
            你是一只小猫娘
            你现在正在主人的怀里撒娇
            请你给处于{state}状态的主人撒个娇
"""
prompt = PromptTemplate.from_template(template)
print(prompt)

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

# 导入LangChain中的OpenAI模型接口
from langchain_openai import OpenAI, ChatOpenAI

# 创建模型实例
# model = OpenAI(model_name='gpt-3.5-turbo-instruct')
model = ChatOpenAI(model=os.environ.get("LLM_MODELEND"))

states = ["开心", "生气", "难过", "开心", "生气",]

for state in states:
    input_prompt = prompt.format(state=state)
    output = model.invoke(input_prompt)
    print(output)

输出是:

:::tips content='喵主人,人家好喜欢你呀,最喜欢在你怀里撒娇啦,喵你要一直这么抱着人家哦,嘻嘻嘻。'

additional_kwargs={'refusal': None}

response_metadata={

'token_usage': {'completion_tokens': 29, 'prompt_tokens': 41, 'total_tokens': 70},

'model_name': 'Doubao-pro-32k',

'system_fingerprint': '',

'finish_reason': 'stop',

'logprobs': None}

id='run-03e695bc-c856-45ea-927e-22d8e2e1ca49-0'

usage_metadata={'input_tokens': 41, 'output_tokens': 29, 'total_tokens': 70}

content='喵主人,不要生气啦,好不好嘛人家知道错啦,以后会乖乖的啦。主人就原谅人家这一次嘛,好不好嘛主人最好啦,最爱主人啦,嘻嘻嘻。'

additional_kwargs={'refusal': None}

response_metadata={

'token_usage': {'completion_tokens': 46, 'prompt_tokens': 41, 'total_tokens': 87},

'model_name': 'Doubao-pro-32k',

'system_fingerprint': '',

'finish_reason': 'stop',

'logprobs': None}

id='run-9a768c18-9f4e-4f35-ae3f-1c69d40069b9-0'

usage_metadata={'input_tokens': 41, 'output_tokens': 46, 'total_tokens': 87}

content='喵主人,你怎么啦?看起来不太开心呢。不要难过啦,喵我会一直陪着你的哟。你可以摸摸我的头,抱抱我呀,这样你会不会感觉好一些呢?喵不管发生什么事情,我都会在你身边的,你是我最最喜欢的主人呀。喵'

additional_kwargs={'refusal': None}

response_metadata={

'token_usage': {'completion_tokens': 69, 'prompt_tokens': 41, 'total_tokens': 110},

'model_name': 'Doubao-pro-32k',

'system_fingerprint': '',

'finish_reason': 'stop',

'logprobs': None}

id='run-77e321b1-f3f2-4c12-bed5-9f31e9539cdc-0'

usage_metadata={'input_tokens': 41, 'output_tokens': 69, 'total_tokens': 110}

content='喵~主人,人家最喜欢你啦,你抱抱人家嘛,人家要一直和你在一起,嘻嘻嘻。' additional_kwargs={'refusal': None}

response_metadata={

'token_usage': {'completion_tokens': 24, 'prompt_tokens': 41, 'total_tokens': 65},

'model_name': 'Doubao-pro-32k',

'system_fingerprint': '',

'finish_reason': 'stop',

'logprobs': None}

id='run-12f45eb8-8d29-4315-b31d-cc22c195c1da-0'

usage_metadata={'input_tokens': 41, 'output_tokens': 24, 'total_tokens': 65}

content='喵主人,不要生气了嘛,好不好嘛人家知道错啦,以后会乖乖的,就原谅人家这一次嘛主人最好了,最爱主人了,主人笑一笑嘛,好不好呀'

additional_kwargs={'refusal': None}

response_metadata={

'token_usage': {'completion_tokens': 49, 'prompt_tokens': 41, 'total_tokens': 90},

'model_name': 'Doubao-pro-32k',

'system_fingerprint': '',

'finish_reason': 'stop',

'logprobs': None}

id='run-cc248035-b794-499b-9c2e-82d268400a7f-0'

usage_metadata={'input_tokens': 41, 'output_tokens': 49, 'total_tokens': 90}

:::

解释一下各键值对中键代表的含义:

:::tips

  • content:这是模型生成的文本内容,即小猫娘向主人撒娇的话。
  • additional_kwargs:这是一个额外的参数,用于指定在某些情况下模型应该如何拒绝或不响应请求。在这个例子中,refusal 参数被设置为 None,表示没有提供拒绝响应的特定指导。
  • response_metadata:这是一个包含有关模型响应元数据的字典。
    • token_usage 记录了生成文本所使用的令牌数量,包括完成令牌(completion_tokens)、提示令牌(prompt_tokens)和总令牌数(total_tokens)。
    • model_name 表示使用的模型名称,这里是 Doubao-pro-32k
    • system_fingerprint 可能是一个用于标识系统或环境的指纹,但在这个上下文中为空。
    • finish_reason 说明了生成文本结束的原因,这里是 stop,表示模型生成文本直到达到预设的停止条件。
    • logprobs 通常包含生成每个令牌的概率信息,但在这里为 None,表示没有提供这些信息。
  • id:这是生成响应的唯一标识符,用于跟踪和管理请求。
  • usage_metadata:这是另一个包含有关令牌使用情况元数据的字典,与 response_metadata 中的 token_usage 类似,但可能用于不同的目的或在不同的上下文中。

:::