AI实战课程笔记-04 提示模板(上)

162 阅读13分钟

LangChain六大核心组件

  • Model
  • Prompts
  • Chains
  • Memory
  • Agents
  • Indexes

Prompts

1 提示的原则

  1. 写出清晰而具体的指示
  2. 给模型思考的时间

吴恩达  ChatGPT Prompt Engineering for Developers公开课

提示工程的6大策略:

  1. 写清晰的指示
  2. 给模型提供参考(也就是示例)
  3. 将复杂任务拆分成子任务
  4. 给GPT时间思考
  5. 使用外部工具
  6. 反复迭代问题

Open AI的官方文档《GPT最佳实践》

2 提示的结构

  • 指令(Instuction)告诉模型这个任务大概要做什么、怎么做,比如如何使用提供的外部信息、如何处理查询以及如何构造输出。这通常是一个提示模板中比较固定的部分。一个常见用例是告诉模型“你是一个有用的XX助手”,这会让他更认真地对待自己的角色。
  • 上下文(Context)则充当模型的额外知识来源。这些信息可以手动插入到提示中,通过矢量数据库检索得来,或通过其他方式(如调用API、计算器等工具)拉入。一个常见的用例时是把从向量数据库查询到的知识作为上下文传递给模型。
  • 提示输入(Prompt Input)通常就是具体的问题或者需要大模型做的具体事情,这个部分和“指令”部分其实也可以合二为一。但是拆分出来成为一个独立的组件,就更加结构化,便于复用模板。这通常是作为变量,在调用模型之前传递给提示模板,以形成具体的提示。
  • 输出指示器(Output Indicator)标记要生成的文本的开始。这部分在我们和ChatGPT对话时往往是可有可无的,当然LangChain中的代理在构建提示模板时,经常性的会用一个“Thought:”(思考)作为引导词,指示模型开始输出自己的推理(Reasoning)。

3 LangChain提示模版类型

3.1 提示模板的含义和导入方式

  1. PromptTemplate:这是最常用的 String 提示模板,在AI实战课程笔记-03中就是使用的这个模板类型。
from langchain.prompts.prompt import PromptTemplate
from langchain import PromptTemplate

上述两种方式最终都导入了同一个 PromptTemplate 类,导入效果是相同的。第一种方式指定完整的模块路径,从最顶层的 langchain 包开始,经过 prompts 子包,再到 prompt 模块,最后导入 PromptTemplate 类。第二种不需要指定完整的模块路径,而是直接从 langchain 包中导入 PromptTemplate 类。第一种方式能够使代码更加模块化和清晰,第二种方式更加简洁。

  1. ChatPromptTemplate:常用的 Chat 提示模板,用于组合各种角色的消息模板,传入聊天模型(Chat Model),具体消息模板包括 ChatMessagePromptTemplateHumanMessagePromptTemplateAIMessagePromptTemplateSystemMessagePromptTemplate
from langchain.prompts import ChatPromptTemplate 
from langchain.prompts import ( 
    ChatMessagePromptTemplate, 
    SystemMessagePromptTemplate, 
    AIMessagePromptTemplate, 
    HumanMessagePromptTemplate,
)
  1. FewShotPromptTemplate:少样本提示模版,通过示例的展示来“教”模型如何回答。
from langchain.prompts import FewShotPromptTemplate
  1. PipelinePrompt:用于把几个提示组合在一起使用。
from langchain.prompts.pipeline import PipelinePromptTemplate
  1. 自定义模板: LangChain还允许我们基于其他模板类来定制自己的提示模板。

3.2 PromptTemplate

template = """\ 你是业务咨询顾问。 你给一个销售{product}的电商公司,起一个好的名字? """ 
prompt = PromptTemplate.from_template(template) 
print(prompt.format(product="鲜花"))

在AI实战课程笔记-03中创建提示模板时使用的就是 from_template 方法。该方法可以从传入的字符串中自动提取变量名称(如product),而无需刻意指定。上面程序中的product自动成为了format方法中的一个参数

prompt = PromptTemplate(
    input_variables=["product", "market"], 
    template="你是业务咨询顾问。对于一个面向{market}市场的,专注于销售{product}的公司,你会推荐哪个名字?"
)
print(prompt.format(product="鲜花", market="高端"))

也可以通过提示模板类的构造函数,在创建模板时手工指定input_variables。

3.2 ChatPromptTemplate

AI实战课程笔记-02中的Chat Model模型提及过消息的各种角色。

消息必须是消息对象的数组,其中每个对象都有一个角色(系统、用户或助理)和内容。对话可以短至一条消息,也可以来回多次。通常,对话首先由系统消息格式化,然后是交替的用户消息和助理消息。  

系统消息有助于设置助手的行为。例如,你可以修改助手的个性或提供有关其在整个对话过程中应如何表现的具体说明。但请注意,系统消息是可选的,并且没有系统消息的模型的行为可能类似于使用通用消息,例如“你是一个有用的助手”。  

用户消息提供助理响应的请求或评论。  

助理消息存储以前的助理响应,但也可以由你编写以给出所需行为的示例。

构建模板

template="你是一位专业顾问,负责为专注于{product}的公司起名。"
system_message_prompt = SystemMessagePromptTemplate.from_template(template)

human_template="公司主打产品是{product_detail}。"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)

prompt_template = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])
  • template:定义了一个系统消息模板,提示顾问需要为专注于特定产品的公司起名。
  • human_template:定义了一个用户消息模板,提供了公司主打产品的详细信息。

格式化提示消息生成提示

prompt = prompt_template.format_prompt(
    product="鲜花装饰", product_detail="创新的鲜花设计。"
).to_messages()

format_prompt 方法填充 prompt_template 中的占位符。.to_messages() 将格式化后的提示模板转换为一个包含系统消息和用户消息的列表,这个列表可以被用于与语言模型进行交互。

调用模型,把提示消息传入模型,生成结果

import os
from langchain_openai import ChatOpenAI

chat = ChatOpenAI(
    model=os.environ.get("LLM_MODELEND"),
)
result = chat(prompt)
print(result)

3.2 FewShotPromptTemplate

  • Few-Shot Learning:OpenAI在介绍GPT-3模型的重要论文《Language models are Few-Shot learners(语言模型是少样本学习者)》中,更是直接指出:GPT-3模型,作为一个大型的自我监督学习模型,通过提升模型规模,实现了出色的Few-Shot学习性能。
  • One-Shot Learning:一个重要的参考文献是2016年Vinyals, O.的论文《小样本学习的匹配网络》。这篇论文提出了一种新的学习模型——匹配网络(Matching Networks),专门针对单样本学习(One-Shot Learning)问题设计,可以看作是一种最常见的Few-Shot学习的情况。
  • Zero-Shot Learning:一个代表性的参考文献是Palatucci, M.在2009年提出的《基于语义输出编码的零样本学习(Zero-Shot Learning with semantic output codes)》,这篇论文提出了零次学习(Zero-Shot Learning)的概念,其中的学习系统可以根据类的语义描述来识别之前未见过的类。

在提示工程(Prompt Engineering)中,Few-Shot 和 Zero-Shot 学习的概念也被广泛应用。

  • 在Few-Shot学习设置中,模型会被给予几个示例,以帮助模型理解任务,并生成正确的响应。
  • 在Zero-Shot学习设置中,模型只根据任务的描述生成响应,不需要任何示例。

1 创建示例样本

samples = [
  {
    "flower_type": "玫瑰",
    "occasion": "爱情",
    "ad_copy": "玫瑰,浪漫的象征,是你向心爱的人表达爱意的最佳选择。"
  },
  {
    "flower_type": "康乃馨",
    "occasion": "母亲节",
    "ad_copy": "康乃馨代表着母爱的纯洁与伟大,是母亲节赠送给母亲的完美礼物。"
  },
  {
    "flower_type": "百合",
    "occasion": "庆祝",
    "ad_copy": "百合象征着纯洁与高雅,是你庆祝特殊时刻的理想选择。"
  },
  {
    "flower_type": "向日葵",
    "occasion": "鼓励",
    "ad_copy": "向日葵象征着坚韧和乐观,是你鼓励亲朋好友的最好方式。"
  }
]

samples这个列表,它包含了四个字典,每个字典代表了一种花的类型、适合的场合,以及对应的广告文案。 在构建FewShotPrompt时,这些示例样本会作为例子传递给模型的参考信息。

__import__("pysqlite3")
import sys

sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")

这部分代码的作用是在Python环境中解决一个特定的问题:当使用某些库或模块时,可能会遇到与SQLite数据库相关的兼容性问题。具体来说,某些库可能依赖于标准的sqlite3模块,而在某些环境中,可能安装了pysqlite3作为sqlite3的替代。代码的目的是确保在运行时使用的是pysqlite3而不是sqlite3

  • 导入 pysqlite3 模块__import__("pysqlite3")使用了Python的__import__函数来手动导入pysqlite3模块。__import__函数是Python的一个内置函数,用于动态导入模块。
  • 重命名 pysqlite3 为 sqlite3sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")通过操作Python的sys.modules字典来实现重命名。sys.modules是一个字典,它包含了当前Python解释器中已经导入的所有模块。通过将pysqlite3sys.modules中弹出并立即将其赋值给sqlite3,代码确保了后续对sqlite3的引用实际上指向的是pysqlite3。因为某些库可能在内部硬编码了对sqlite3的引用,而不是使用import sqlite3这样的导入语句。通过这种方式,代码可以在不修改这些库的情况下,让它们使用pysqlite3作为sqlite3的替代。

2 创建要一个提示模板

from langchain.prompts.prompt import PromptTemplate

template="鲜花类型: {flower_type}\n场合: {occasion}\n文案: {ad_copy}"
prompt_sample = PromptTemplate(
    input_variables=["flower_type", "occasion", "ad_copy"], 
    template=template
    )
print(prompt_sample.format(**samples[0]))
  • ** 符号用于字典解包操作,当我们在函数调用中使用 ** 时,它会将字典中的键值对作为关键字参数传递给函数。这意味着字典中的每个键都将成为函数的一个参数名,而对应的值将成为该参数的值。
  • samples[0] 是一个字典,它包含了 flower_typeoccasion 和 ad_copy 这些键值对。通过使用 **samples[0],我们实际上是在调用 prompt_sample.format() 方法,并将字典中的键值对作为参数传递给它。

例如,如果 samples[0] 是这样的:

{
    "flower_type": "玫瑰",
    "occasion": "爱情",
    "ad_copy": "玫瑰,浪漫的象征,是你向心爱的人表达爱意的最佳选择。"
}

那么 print(prompt_sample.format(**samples[0])) 就等同于:

print(prompt_sample.format(flower_type="玫瑰", occasion="爱情", ad_copy="玫瑰,浪漫的象征,是你向心爱的人表达爱意的最佳选择。"))

3 创建一个FewShotPromptTemplate对象

from langchain.prompts.few_shot import FewShotPromptTemplate
prompt = FewShotPromptTemplate(
    examples=samples,
    example_prompt=prompt_sample,
    suffix="鲜花类型: {flower_type}\n场合: {occasion}",
    input_variables=["flower_type", "occasion"]
)
print(prompt.format(flower_type="野玫瑰", occasion="爱情"))

FewShotPromptTemplate是一个更复杂的提示模板,它包含了多个示例和一个提示。这种模板可以使用多个示例来指导模型生成对应的输出。

4 将提示传递给大模型

import os
os.environ["OPENAI_API_KEY"] = '你的Open AI Key'
from langchain.llms import OpenAI
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
result = model(prompt.format(flower_type="野玫瑰", occasion="爱情"))
print(result)

5 使用示例选择器

5.1 导入需要的库

from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain_community.vectorstores import Chroma
  • SemanticSimilarityExampleSelector:这个类是LangChain库中的一个示例选择器,它基于语义相似性来选择示例。在使用Few-Shot学习时,这个选择器可以帮助从示例集中挑选出与当前输入最相关的示例,从而提高模型的性能和效果。
  • Chroma:这是一个来自 langchain_community 库的向量数据库,它用于存储和检索嵌入向量。Chroma可以与LangChain库集成,用于构建基于向量的检索系统,例如在问答系统中检索最相关的文档或示例。
  • 这两个库的结合使用,可以构建一个强大的提示工程系统,其中 SemanticSimilarityExampleSelector 可以帮助从 Chroma 向量数据库中选择最相关的示例。

5.2 初始化示例选择器

方法1: 使用DoubaoEmbeddings初始化示例选择器

初始化Embeding类

from volcenginesdkarkruntime import Ark
from typing import List, Any
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel
  • volcenginesdkarkruntime 模块导入 Ark 类。Ark 类通常用于与某个服务或API进行交互,可能是用于处理自然语言处理任务的特定服务。
  • 从 typing 模块导入了 List 和 AnyList 用于定义列表类型,而 Any 是一个泛型类型,表示可以是任何类型。
  • 从 langchain 库的 embeddings.base模块导入了 Embeddings 类。Embeddings 类是 langchain 库中用于生成文本嵌入(embeddings)的基类。
  • langchain.pydantic_v1 模块导入了 BaseModel 类。pydantic 是一个用于数据验证和设置管理的Python库,BaseModel 是 pydantic 库中的一个基础模型类,在 langchain 中,BaseModel 可能用于定义数据模型或配置对象。
class DoubaoEmbeddings(BaseModel, Embeddings):
    client: Ark = None
    api_key: str = ""
    model: str

    def __init__(self, **data: Any):
        super().__init__(**data)
        if self.api_key == "":
            self.api_key = os.environ["OPENAI_API_KEY"]
        self.client = Ark(
            base_url=os.environ["OPENAI_BASE_URL"],
            api_key=self.api_key
        )

    def embed_query(self, text: str) -> List[float]:
        embeddings = self.client.embeddings.create(model=self.model, 
        input=text)
        return embeddings.data[0].embedding

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        return [self.embed_query(text) for text in texts]

    class Config:
        arbitrary_types_allowed = True

定义DoubaoEmbedding类的方法在AI实战课程笔记-02中有具体介绍。

初始化示例选择器

example_selector = SemanticSimilarityExampleSelector.from_examples(
    samples,
    DoubaoEmbeddings(
        model=os.environ.get("EMBEDDING_MODELEND"),
    ),
    Chroma,
    k=1,
    )

使用 SemanticSimilarityExampleSelector的 from_examples 方法来创建一个示例选择器。from_examples 方法接受以下参数:

  • samples:一个包含示例的列表,每个示例是一个字典。
  • DoubaoEmbeddings:用我们上文初始化的embedding类定义的一个嵌入模型实例,用于将文本转换为嵌入向量。
  • Chroma:一个向量数据库类,用于存储和检索嵌入向量。
  • k:要选择的示例数量,这里设置为 1,表示每次只选择一个最相关的示例。

方法2:使用OpenAIEmbeddings初始化示例选择器

from langchain.embeddings import OpenAIEmbeddings

example_selector = SemanticSimilarityExampleSelector.from_examples(     
    samples,     
    OpenAIEmbeddings(),     
    Chroma,     
    k=1 
)

5.3 创建1个使用示例选择器的FewShotPromptTemplate对象

prompt = FewShotPromptTemplate(
    example_selector=example_selector, 
    example_prompt=prompt_sample, 
    suffix="鲜花类型: {flower_type}\n场合: {occasion}", 
    input_variables=["flower_type", "occasion"]
)
print(prompt.format(flower_type="红玫瑰", occasion="爱情"))

示例选择器 example_selector 会根据语义的相似度(余弦相似度)找到最相似的示例。

5.4 报错解决

直接运行课程给出的示例会出现如下报错:

TypeError: Descriptors cannot be created directly.

If this call came from a _pb2.py file, your generated code is out of date and must be regenerated with protoc >= 3.19.0.

If you cannot immediately regenerate your protos, some other possible workarounds are:

  1. Downgrade the protobuf package to 3.20.x or lower.
  2. Set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python (but this will use pure-Python parsing and will be much slower). More information: developers.google.com/protocol-bu…

参考解决方法如下:

  1. 重新生成生成代码:如果可能的话,尝试使用最新版本的 protoc 工具重新生成 proto 文件。这样可以确保生成的代码与最新的 protobuf 库兼容。

  2. 降级 protobuf 库版本:如果你无法立即重新生成 proto 文件,可以尝试降级 protobuf 库至 3.20.x 或更低的版本。我使用该方法后可运行,可以在终端使用以下命令来降级 protobuf 库:

    pip install protobuf==3.20.0
    
  3. 设置环境变量:尝试设置环境变量 PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python。但需要注意的是,这会使用纯Python解析,可能会导致解析速度变慢。