LangChain Prompt Engineering和LLMs:如何让语言模型更好地理解和生成文本

一、前言

在机器学习中,我们一直依赖不同的模型来完成不同的任务。随着多模态和大型语言模型 (LLM)的引入,这种情况发生了变化。过去,我们需要为分类、命名实体识别(NER)、问答(QA)等任务使用不同的模型。

 随着 transformers 和迁移学习的引入,只需在网络的末尾(或者头部)添加几个较小的层,并进行微调,就可以将语言模型适应于不同的任务。这意味着使用预训练的语言模型,如 transformers,可以通过在网络的最后一层或者最前一层进行微调或者扩展来适应特定的任务,而无需从头开始训练一个全新的模型。这种方法可以提高模型的效率和灵活性。

 Transformer 和迁移学习的思想使我们能够通过切换模型“头”并执行微调,将预训练 Transformer 模型的相同核心组件重复用于不同的任务。

然而,即便这种方法也已经过时了。当你可以通过提示模型进行分类或问答时,为什么还要改最后几层模型并经历整个微调过程呢?

 大型语言模型(LLMs)可以执行所有这些任务以及更多。这些模型经过训练,输入一个文本序列,模型输出一个文本序列。唯一的变量是输入文本,即Prompt。

在这个新时代的LLMs中,提示至关重要。糟糕的提示会产生糟糕的输出,而好的提示则具有非常强大的能力。构建好的提示是那些使用LLMs的人必备的关键技能。

LangChain库认识到了提示的重要性,并为其构建了一整套对象。在本文中,我们将学习有关PromptTemplates的一切,并有效地实施它们。

二、Prompt工程

在深入了解LangChain的PromptTemplate之前,我们需要更好地理解提示和提示工程的原理。

一个提示通常由多个部分组成:

 并非所有的提示都使用这些组件,但是一个好的提示通常会使用两个或更多。让我们更加准确地定义它们。

指令:告诉模型要做什么,如何使用外部信息(如果提供),如何处理查询,并构建输出。

外部信息或上下文:作为模型的额外知识来源。这些可以手动插入到提示中,通过向量数据库检索获取(检索增强),或通过其他方式获取(API、计算等)。

用户输入或查询:通常是由人类用户(即提示者)输入到系统中的查询。

输出指示器:标志着即将生成的文本的开头。如果生成Python代码,我们可以使用import来告诉模型提示它必须开始编写Python代码(因为大多数Python脚本以import开头)。

每个组件通常按照这个顺序放置在提示中。从指令开始,外部信息(如果适用),提示者输入,最后是输出指示器。

让我们看看如何使用 LangChain 将其输入到 OpenAI 模型:

prompt = """根据下面的上下文回答问题。如果无法使用提供的信息回答问题,请回答“我不知道”".

Context: 大型语言模型(LLMs)是自然语言处理中使用的最新模型。 它们相对于较小的模型具有卓越的性能,使它们对于构建支持自然语言处理的应用程序的开发人员非常有用。这些模型可以通过Hugging Face的“transformers”库,通过OpenAI的“openai”库以及通过Cohere的“cohere”库进行访问.

Question: 哪些库和模型提供商提供LLMs?

Answer: """

from langchain.llms import OpenAI

# 初始化OpenAI模型
openai = OpenAI(
    model_name="text-davinci-003",
    openai_api_key="YOUR_API_KEY"
)

print(openai(prompt))

输出内容:

 Hugging Face的“transformers”库、OpenAI的“openai”库和Cohere的“cohere”库提供LLMs。

实际上,我们不太可能对上下文和用户问题进行硬编码。我们通过模板将它们输入——这就是 LangChain 的PromptTemplate用武之地。

三、Prompt模板

LangChain 中的提示模板类旨在使构建具有动态输入的提示更加容易。其中最简单的是PromptTemplate。我们将通过向之前的提示添加一个动态输入(用户查询)来测试这一点。

from langchain import PromptTemplate

template = """根据下面的上下文回答问题。如果无法使用提供的信息回答问题,请回答"我不知道".

Context: 大型语言模型(LLMs)是自然语言处理中使用的最新模型。 它们相对于较小的模型具有卓越的性能,使它们对于构建支持自然语言处理的应用程序的开发人员非常有用。这些模型可以通过Hugging Face的“transformers”库,通过OpenAI的“openai”库以及通过Cohere的“cohere”库进行访问.

Question: {query}

Answer: """

prompt_template = PromptTemplate(
    input_variables=["query"],
    template=template
)

有了这个,我们可以使用prompt_templateformat方法来查看将查询传递给模板的效果。

print(
    prompt_template.format(
        query="哪些库和模型提供商提供LLMs?"
    )
)

输出内容:

根据下面的上下文回答问题。如果无法使用提供的信息回答问题,请回答"我不知道".

Context: 大型语言模型(LLMs)是自然语言处理中使用的最新模型。 它们相对于较小的模型具有卓
越的性能,使它们对于构建支持自然语言处理的应用程序的开发人员非常有用。这些模型可以通过Hugging Face
的“transformers”库,通过OpenAI的“openai”库以及通过Cohere的“cohere”库进行访问.

Question: 哪些库和模型提供商提供LLMs?

Answer: 

当然,我们可以直接将这个输出传递给LLM对象,如下所示:

print(openai(
    prompt_template.format(
        query="哪些库和模型提供商提供LLMs?"
    )
))

输出内容:

Hugging Face的“transformers”库,OpenAI的“openai”库和Cohere的“cohere”库提供LLMs。

这只是一个简单的实现,可以很容易地用f-strings(例如f"插入一些自定义文本 '{custom_text}' etc")来替代。然而,使用LangChain的PromptTemplate对象,我们可以规范化这个过程,添加多个参数,并以面向对象的方式构建提示。

这些都是LangChain为帮助我们处理提示提供的重要优势之一。

四、Few Shot Prompt模板

LLMs(Language Model)的成功来自于它们的庞大规模和在模型训练过程中学习到的“知识”存储在模型参数中的能力。然而,还有其他方法可以将知识传递给LLM。主要的两种方法是:

参数知识(Parametric knowledge)——上述提到的知识是指模型在训练过程中学习到的任何东西,并存储在模型的权重(或参数)中。

源知识(Source knowledge)——通过输入提示(input prompt)在推理过程中提供给模型的任何知识。

LangChainFewShotPromptTemplate适用于源头知识输入。其思想是在几个示例上“训练”模型——我们称之为少样本学习——并且这些例子在提示中提供给模型。

当我们的模型需要帮助理解我们要求它做什么时,少样本学习非常有效。我们可以在以下示例中看到这一点:

prompt = """以下是与AI助手的对话。 助手通常是讽刺和机智的,对用户的问题产生创造性和有趣的回答。以下是一些例子:

User: 生活的意义是什么?
AI: """

openai.temperature = 1.0  # 增加创造力/随机性的输出

print(openai(prompt))

输出内容:

人们对生活的意义往往有不同的看法。可以说生活的意义就在于你怎么对待它,它可以是充满希望,也可以是一种挑战。你的价值观和理念决定了你的生活。

在这种情况下,我们要求得到一个有趣的笑话作为对我们严肃问题的回答。然而,即使将temperature设置为1.0(增加随机性/创造力),我们仍然得到了一个严肃的回答。

为了帮助模型,我们可以给出一些我们想要的答案类型的示例:

prompt = """以下是与AI助手进行对话的摘录。助手通常很讽刺和机智,对用户的问题产生创造性和有趣的回应。这里有一些例子:

User: 你好吗?
AI: 我不能抱怨,但有时候我还是会这么做。

User: 现在几点了?
AI:  是时候买个手表了。

User: 生活的意义是什么?
AI: """

print(openai(prompt))

输出内容:

从我未来观测的角度来看,每个人的意义可能会有所不同!

通过示例来强化我们在提示中传递的指令,我们更有可能得到一个更有趣的回答。然后,我们可以使用 LangChain 的FewShotPromptTemplate来规范这个过程:

from langchain import FewShotPromptTemplate

# 创建一个示例
examples = [
    {
        "query": "你好吗?",
        "answer": "我不能抱怨,但有时候我还是会这么做。"
    }, {
        "query": "现在几点了?",
        "answer": "是时候买个手表了。"
    }
]

# 创建一个示例模版
example_template = """
User: {query}
AI: {answer}
"""

# 使用上面的模板创建一个提示示例。
example_prompt = PromptTemplate(
    input_variables=["query", "answer"],
    template=example_template
)

# 把我们之前的提示分成前缀和后缀
# 前缀是我们的说明
prefix = """以下是与AI助手进行对话的摘录。助手通常很讽刺和机智,对用户的问题产生创造性和有趣的回应。这里有一些例子:
"""
#  后缀是我们的用户输入和输出指示符
suffix = """
User: {query}
AI: """

# 现在创建few-shot提示模板
few_shot_prompt_template = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    prefix=prefix,
    suffix=suffix,
    input_variables=["query"],
    example_separator="\n\n"
)

如果我们将 examples 和 user query 传递进去,我们将得到以下结果:

query = "生活的意义是什么?"

print(few_shot_prompt_template.format(query=query))

输出内容:

以下是与AI助手进行对话的摘录。助手通常很讽刺和机智,对用户的问题产生创造性和有趣的回应。这里有一些例子:

User: 你好吗?
AI: 我不能抱怨,但有时候我还是会这么做。

User: 现在几点了?
AI: 是时候买个手表了。

User: 生活的意义是什么?
AI: 

这个过程可能看起来有点复杂。为什么要使用FewShotPromptTemplate对象、examples字典等来完成所有这些呢 —— 当我们可以用几行代码和一个f字符串来做同样的事情呢?

再次强调,这种方法更加正式化,并且与 LangChain 中的其他功能(如chains)很好地集成在一起,并且具有多种功能。其中之一是可以根据查询长度来调整要包含的示例数量。

动态数量的示例非常重要,因为我们的提示和完成输出的最大长度是有限的。这个限制是通过最大上下文窗口来衡量的。 同时,我们可以最大化给模型提供的示例数量,以进行少样本学习。

context window = input tokens + output tokens

同时,我们可以给模型提供最大化的示例数量,以进行Few Shot学习。

考虑到这一点,我们需要在包含的示例数量和提示大小之间取得平衡。我们的硬性限制是最大上下文大小,但我们还必须考虑通过LLM处理更多token的成本。较少的token意味着更便宜的服务和从LLM获得更快的完成。

FewShotPromptTemplate允许我们根据这些变量来调整包含的示例数量。首先,我们创建一个更广泛的examples列表:

examples = [
    {
        "query": "你好吗?",
        "answer": "我不能抱怨,但有时还是会这样做。"
    }, {
        "query": "现在几点了?",
        "answer": "是时候去买个手表了。"
    }, {
        "query": "生命的意义是什么?",
        "answer": "42"
    }, {
        "query": "今天的天气如何?",
        "answer": "多云,有一些梗的机会。"
    }, {
        "query": "你最喜欢的电影是什么?",
        "answer": "终结者"
    }, {
        "query": "你最好的朋友是谁?",
        "answer": "Siri。我们对生命的意义进行激烈的辩论。"
    }, {
        "query": "今天我应该做什么?",
        "answer": "别在网上和聊天机器人聊天了,出去走走吧。"
    }
]

然后,我们实际上使用LengthBasedExampleSelector来传递示例,如下所示:

from langchain.prompts.example_selector import LengthBasedExampleSelector

example_selector = LengthBasedExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    max_length=50  # 设置示例的最大长度
)

需要注意的是,我们将max_length定义为通过空格和换行符拆分字符串后的单词数。具体的逻辑如下:

import re

some_text = "There are a total of 8 words here.\nPlus 6 here, totaling 14 words."

words = re.split('[\n ]', some_text)
print(words, len(words))

输出内容:

['There', 'are', 'a', 'total', 'of', '8', 'words', 'here.', 'Plus', '6', 'here,', 'totaling', '14', 'words.'] 14

然后,我们将其传递example_selectorFewShotPromptTemplate创建一个新的动态提示模板:

# 现在创建少样本提示模板
dynamic_prompt_template = FewShotPromptTemplate(
    example_selector=example_selector,  # 使用example_selector而不是examples
    example_prompt=example_prompt,
    prefix=prefix,
    suffix=suffix,
    input_variables=["query"],
    example_separator="\n"
)

现在,如果我们输入一个较短或较长的查询,我们会发现包含的示例数量会有所变化。

print(dynamic_prompt_template.format(query="鸟是如何飞行的?"))

输出内容:

以下是与AI助手进行对话的摘录。助手通常很讽刺和机智,对用户的问题产生创造性和有趣的回应。这里有一些例子:

User: 你好吗?
AI: 我不能抱怨,但有时还是会这样做。

User: 现在几点了?
AI: 是时候去买个手表了。

User: 生命的意义是什么?
AI: 42

User: 今天的天气如何?
AI: 多云,有一些梗的机会。

User: 你最喜欢的电影是什么?
AI: 终结者

User: 你最好的朋友是谁?
AI: Siri。我们对生命的意义进行激烈的辩论。

User: 今天我应该做什么?
AI: 别在网上和聊天机器人聊天了,出去走走吧。

User: 鸟是如何飞行的?
AI: 

提供一个较长的问题将导致包含的示例数量减少:

query = """如果我在中国,想给另一个国家的人打电话,我在考虑可能是欧洲,可能是西欧国家,比如法国、德国或英国,最好的方式是什么?"""

print(dynamic_prompt_template.format(query=query))

输出内容:

以下是与AI助手进行对话的摘录。助手通常很讽刺和机智,对用户的问题产生创造性和有趣的回应。这里有一些例子:

User: 你好吗?
AI: 我不能抱怨,但有时还是会这样做。

User: 现在几点了?
AI: 是时候去买个手表了。

User: 生命的意义是什么?
AI: 42

User: 今天的天气如何?
AI: 多云,有一些梗的机会。

User: 你最喜欢的电影是什么?
AI: 终结者

User: 你最好的朋友是谁?
AI: Siri。我们对生命的意义进行激烈的辩论。

User: 今天我应该做什么?
AI: 别在网上和聊天机器人聊天了,出去走走吧。

User: 如果我在中国,想给另一个国家的人打电话,我在考虑可能是欧洲,可能是西欧国家,比如法国、德国或英国,最好的方式是什么?
AI: 

通过这样做,我们在prompt变量中返回较少的示例。这样可以限制过多的标记使用,并避免超过LLM的最大上下文窗口引起的错误。

Jupyter Notebook 的完整代码

https://github.com/Crossme0809/langchain-tutorials/blob/main/LangChain_Prompt_Templates.ipynb

总结

自然地,提示是LLM新世界中的重要组成部分。值得探索LangChain提供的工具,并熟悉不同的提示工程技术。

在这里,我们只介绍了LangChain中可用的一些提示工具,并对它们的使用进行了有限的探索。后面我们会继续探讨LangChain的其它重要组成部分,并进一步研究提示模板的应用以及它们如何与库提供的更广泛的工具集相结合。

猜你喜欢

转载自blog.csdn.net/FrenzyTechAI/article/details/131959143