7个提示技巧,LangChain和Python示例代码
这是系列文章中的第四篇,讨论如何在实践中使用大型语言模型(LLM)。在这里,我将讨论提示工程(PE)以及如何使用它来构建LLM应用程序。我首先回顾了关键的PE技术,然后通过Python示例代码演示了如何使用LangChain构建基于LLM的应用程序。
当第一次听说提示工程时,许多技术人员(包括我自己)往往会对这个想法嗤之以鼻。我们可能会想:“提示工程?噗,那很无聊。告诉我如何从头开始构建LLM。”
然而,深入研究后,我要告诫开发人员不要自动排除提示工程。我甚至要进一步说,提示工程可以实现大多数LLM用例的80%价值,而付出的努力相对较低。
我在这篇文章中的目标是通过实际回顾提示工程和举例说明,传达这一观点。虽然提示工程可能还存在一些不足之处,但它为我们解决问题提供了简单而巧妙的解决方案。
什么是提示工程?
在本系列文章的第一篇中,我将提示工程定义为直接使用LLM(即不训练任何内部模型参数)。然而,关于提示工程还有更多可以说的。
- 提示工程是“使用提示来编程LLMs的手段。”[1]
- 提示工程是“通过组合和格式化提示来最大化模型在所需任务上的性能的经验性艺术。”[2]
- “语言模型…希望完成文档,因此您可以通过安排虚假文档来欺骗它们执行任务。”[3]
第一个定义传达了LLMs的关键创新,即计算机现在可以使用简单的英语进行编程。第二点将提示工程框架化为一种主要是经验性的努力,其中从业者、调试者和开发者是这种新编程方式的关键探索者。
Andrei Karpathy提到的第三点提醒我们,LLMs并没有被明确训练来做我们要求的任何事情。因此,在某种程度上,我们是在“欺骗”这些语言模型来解决问题。我觉得这捕捉到了提示工程的本质,它更多地依赖于你的创造力,而不是技术技能。
两个级别的提示工程
可以通过两种不同的方式进行提示工程,我在本系列文章的第一篇中称之为“简单方式”和“不那么简单的方式”。
简单方式
这是大多数人进行提示工程的方式,通过ChatGPT(或类似的工具)进行。这是一种直观、无代码和免费的与LLM交互的方式。
虽然这是解决一些快速和简单问题的好方法,例如总结一篇文本、重写邮件、帮助你构思生日派对计划等,但它也有缺点。其中一个很大的问题是很难将这种方法集成到更大的自动化流程或软件系统中。要做到这一点,我们需要更进一步。
不那么简单的方式
通过使用Python以编程方式与LLMs进行交互,可以解决“简单方式”的许多缺点。在本系列文章的前两篇中,我们了解了如何使用OpenAI的Python API和Hugging Face Transformers库。
虽然这需要更多的技术知识,这正是提示工程的真正力量所在,因为它允许开发人员将基于LLM的模块集成到更大的软件系统中。
这是一个很好(也许有点讽刺)的例子,ChatGPT就是一个很好的例子。该产品的核心是使用一个预训练模型(即GPT-3.5-turbo)作为聊天机器人,并将其封装在一个易于使用的Web界面中。
当然,开发GPT-3.5-turbo是困难的,但这不是我们需要担心的事情。凭借我们掌握的所有预训练LLM(语言模型),几乎任何具备基本编程技能的人都可以创建一个像ChatGPT这样功能强大的人工智能应用,而无需成为人工智能研究人员或机器学习博士。
使用提示工程构建人工智能应用
较为困难的方法开启了一种新的编程和软件开发范式。开发人员不再需要在软件系统中定义每一个细节逻辑,而是可以将非平凡的部分转交给LLM。让我们看一个具体的例子,看看这可能是什么样子。
假设你想创建一个高中历史课的自动评分系统。然而,问题在于所有的问题都是文字回答,所以正确答案可能有多个版本。例如,对于“美国第35任总统是谁?”这个问题,以下回答可能都是正确的。
- 约翰·F·肯尼迪
- JFK
- 杰克·肯尼迪(常用昵称)
- 约翰·菲茨杰拉德·肯尼迪(可能试图获得额外学分)
- 约翰·F·肯尼迪(姓氏拼写错误)
在传统的编程范式中,开发人员需要想办法考虑所有这些变化。为此,他们可能会列出所有可能的正确答案,并使用精确的字符串匹配算法,甚至可能使用模糊匹配来帮助处理拼写错误的单词。
然而,在这种新的LLM-enabled范式中,这个问题可以通过简单的提示工程来解决。例如,我们可以使用以下提示来评估学生的答案。
你是一位高中历史老师,正在批改作业。根据“问题:”后面的作业问题和“答案:”后面的正确答案,你的任务是确定学生的答案是否正确。评分是二进制的,因此学生的答案可以是正确或错误的。简单的拼写错误可以接受。问题:{question}答案:{correct_answer} 学生答案:{student_answer}
我们可以将这个提示看作一个函数,给定一个问题、正确答案和学生答案,它生成学生的分数。然后,这可以集成到一个更大的软件中,实现自动评分。
从节省时间的角度来看,我只用了大约2分钟来编写这个提示,而如果我试图开发一个做同样事情的算法,可能需要几个小时(甚至几天),而且可能性能更差。因此,像这样的任务节省的时间是100-1000倍。
当然,有很多任务LLM并不能提供任何实质性的好处,其他现有的方法更加适用(例如预测明天的天气)。LLM并不是解决每个问题的答案,但它们确实为需要有效处理自然语言的任务提供了一套新的解决方案,而这在计算机历史上一直是困难的。
提示工程的7个技巧
虽然前面的提示示例可能看起来像是自动评分任务的一种自然和明显的方式,但它有意地采用了特定的提示工程启发式方法(或者可以称之为“技巧”)。这些(以及其他)技巧已经被证明是提高LLM响应质量的可靠方法。
尽管有很多编写良好提示的技巧和窍门,但在这里我只讨论那些基于少数参考资料[1,3-5]的最基本(依我之见)的方法。如果读者想要更深入地了解,我建议参考这里引用的资料。
技巧1:描述性(越详细越好)
LLM的一个显著特点是它们是在大规模文本语料库上训练的。这使它们具备了丰富的世界知识和执行各种任务的能力。然而,如果没有提供正确的上下文,这种令人印象深刻的通用性可能会影响特定任务的性能。
例如,让我们比较两个生成给我爸爸生日祝福信息的提示。
无诡计
给我爸爸写一段生日祝福信息。
有诡计
给我爸爸写一段不超过200个字符的生日祝福信息。这是一个重要的生日,因为他要五十岁了。为了庆祝,我已经给我们预订了一次男孩之旅去坎昆。请确保包含一些幽默,他很喜欢。
诡计2:给出例子
下一个诡计是给LLM示例响应以提高其在特定任务上的性能。这个技术术语叫做少样本学习,已经被证明可以显著提高LLM的性能[6]。
让我们看一个具体的例子。假设我们想为一个《Towards Data Science》文章写一个副标题。我们可以使用现有的例子来帮助指导LLM的完成。
无诡计
给定一个《Towards Data Science》博客文章的标题,为其写一个副标题。标题:Prompt Engineering—如何愚弄AI解决您的问题副标题:
有诡计
给定一个《Towards Data Science》博客文章的标题,为其写一个副标题。标题:LLMs在实践中的三个层次副标题:具有示例代码的完整入门介绍标题:破解OpenAI(Python)API副标题:一份完全适合初学者的介绍,附有示例代码标题:Prompt Engineering-如何愚弄AI解决您的问题副标题:
诡计3:使用结构化文本
确保提示遵循有组织的结构不仅使其更易于阅读和编写,而且还有助于模型生成良好的补全。我们在诡计2的示例中使用了这种技术,其中我们明确标记了每个示例的标题和副标题。
然而,我们可以以无数种方式给我们的提示加上结构。以下是一些例子:使用全大写字母强调,使用“`等分隔符突出显示一段文本,使用Markdown或HTML等标记语言格式化文本,使用JSON组织信息等。现在,让我们看看它在实际中的应用。
无诡计
给我一份巧克力曲奇饼的食谱。
有诡计
创建一份组织良好的巧克力曲奇饼食谱。使用以下格式化元素:**标题**:经典巧克力曲奇饼**配料**:列出具体的配料和测量单位。**步骤**:提供逐步指导的编号格式,详细说明烘焙过程。**提示**:包含一个单独的部分,提供有用的烘焙技巧和可能的变化。
诡计4:思维链
这个诡计是由Wei等人提出的[7]。基本思想是引导LLM“逐步思考”。这有助于将复杂的问题分解为易于处理的子问题,为LLM提供“思考的时间”[3,5]。Zhang等人表明,这可以简单地在提示中包含文本“让我们逐步思考”[8]。
这个概念可以扩展到任何类似食谱的过程。例如,如果我想根据我最新的VoAGI博客创建一个LinkedIn帖子,我可以引导LLM模仿我遵循的逐步过程。
无诡计
根据以下VoAGI博客,给我写一个LinkedIn帖子。VoAGI博客:{VoAGI博客内容}
有诡计
根据以下逐步过程和VoAGI博客,给我写一个LinkedIn帖子。步骤1:想出与博客相关的一句话悬念。步骤2:从文章中提取3个关键点。步骤3:将步骤2中的每个关键点压缩为不超过50个字符。步骤4:结合悬念、步骤3中的压缩关键点和一项行动呼吁生成最终输出。VoAGI博客:{VoAGI博客内容}
技巧5:聊天机器人角色
一种令人惊讶的技巧,通常可以提高LLM的性能,是提示它扮演特定的角色,例如“你是专家”。这很有帮助,因为您可能不知道如何最好地向LLM描述您的问题,但您可能知道谁能帮助您解决该问题[1]。以下是实际操作中可能出现的情况。
没有技巧
给我制定一个在纽约市度过周末的旅行行程。
使用技巧
扮演一位了解纽约市的当地人和出租车司机,了解城市的一切。根据您的经验,为我制定一个在纽约市度过周末的旅行行程。在回答中别忘了带上您迷人的纽约口音。
技巧6:翻转方法
当我们不知道LLM知道什么或者它如何思考时,要最优地提示LLM可能会有困难。这就是“翻转方法”有帮助的地方。您可以提示LLM向您提问,直到它对您要解决的问题有足够的理解(即上下文)。
没有技巧
有没有一个基于LLM的应用的创意?
使用技巧
我希望您问我问题,帮助我想出一个基于LLM的应用创意。每次只问一个问题,保持对话的进行。
技巧7:反思、回顾和改进
这个最后的技巧促使模型反思其过去的回答以改进它们。常见的用例是让模型通过询问它是否“完成了任务”或要求它“解释回答背后的推理和假设”来对自己的工作进行批判性评估[1, 3]。
此外,您还可以要求LLM改进不仅是其回答,而且是您的提示。这是一种简单的方式,可以自动重写提示,使其更容易被模型“理解”。
使用技巧
回顾您之前的回答,找出需要改进的地方,并提供一个改进的版本。然后解释您改进回答的推理。
示例代码:使用LangChain的自动评分程序
既然我们已经回顾了几个提示启发法,现在让我们看看如何将它们应用于特定的用例。为此,我们将回到之前的自动评分程序示例。
您是一名高中历史老师,负责批改作业。根据由“Q:”指示的作业问题和由“A:”指示的正确答案,您的任务是确定学生的答案是否正确。评分是二进制的,因此学生的答案可以正确或错误。简单的拼写错误是可以接受的。问题:{question}答案:{correct_answer}学生答案:{student_answer}
仔细看看,之前提到的一些技巧应该很明显,即技巧6:聊天机器人角色,技巧3:使用结构化文本,以及技巧1:具体描述。这通常是实践中良好提示的典型样式,即将多种技巧结合在一个提示中。
虽然我们可以将此提示模板复制粘贴到ChatGPT中,并替换问题、correct_answer和student_answer字段,但这不是实现自动评分的可扩展方式。相反,我们希望将此提示集成到一个更大的软件系统中,以便构建一个用户友好的应用程序,供人类使用。
LangChain
我们可以通过LangChain来实现这一点,它是一种帮助简化在大型语言模型之上构建应用程序的Python库。它通过提供各种方便的抽象来以编程方式使用LLM。
做到这一点的核心类名为chain(因此得名)。它抽象了生成提示、将其发送到LLM并解析输出的过程,使其可以轻松地调用并集成到更大的脚本中。
让我们看看如何在自动评分的情况下使用LangChain。示例代码可在本文的GitHub存储库中找到。
导入
我们首先导入必要的库模块。
from langchain.chat_models import ChatOpenAIfrom langchain.prompts import PromptTemplatefrom langchain.chains import LLMChainfrom langchain.schema import BaseOutputParser
在这里,我们将使用gpt-3.5-turbo,它需要一个OpenAI的API密钥。如果您没有密钥,我在本系列的一篇过去的文章中给出了一个逐步指南。我喜欢将密钥存储在一个单独的Python文件(sk.py)中,并使用以下代码行进行导入。
from sk import my_sk #从另一个Python文件导入密钥
我们的第一个链
为了定义我们的链,我们需要两个核心元素:LLM和prompt。我们首先创建一个LLM对象。
# 定义LLM对象chat_model = ChatOpenAI(openai_api_key=my_sk, temperature=0)
LangChain有一个专门用于OpenAI(和许多其他)聊天模型的类。我传入了我的秘密API密钥,并将温度设置为0。这里的默认模型是gpt-3.5-turbo,但您也可以使用“model_name”输入参数选择使用gpt-4。您还可以通过设置其他输入参数来进一步自定义聊天模型。
接下来,我们定义我们的prompt模板。该对象允许我们通过输入字符串动态生成prompt,自动更新基础模板。以下是它的样子。
# 定义prompt模板prompt_template_text = """You are a high school history teacher grading homework assignments. \Based on the homework question indicated by “**Q:**” and the correct answer indicated by “**A:**”, your task is to determine whether the student's answer is correct. \Grading is binary; therefore, student answers can be correct or wrong. \Simple misspellings are okay.**Q:** {question}**A:** {correct_answer}**Student's Answer:** {student_answer}"""prompt = PromptTemplate(input_variables=["question", "correct_answer", "student_answer"], \ template = prompt_template_text)
有了LLM和prompt,我们现在可以定义我们的链。
# 定义chainchain = LLMChain(llm=chat_model, prompt=prompt)
接下来,我们可以一行代码中将输入传递给链并获得一个分数。
# 定义输入question = "Who was the 35th president of the United States of America?"correct_answer = "John F. Kennedy"student_answer = "FDR"# 运行chainchain.run({'question':question, 'correct_answer':correct_answer, \ 'student_answer':student_answer})# 输出:学生的答案是错误的。
虽然这个链可以有效地执行评分任务,但它的输出可能不适合自动化处理。例如,在上面的代码块中,LLM正确地指出学生的答案“FDR”是错误的,但如果LLM以一种可在下游处理中使用的标准格式输出结果会更好。
输出解析器
这就是输出解析器派上用场的地方。这些是我们可以集成到链中的函数,用于将LLM的输出转换为标准格式。让我们看看如何创建一个将LLM响应转换为布尔值(即True或False)输出的输出解析器。
# 定义输出解析器class GradeOutputParser(BaseOutputParser): """确定分数是否正确或错误""" def parse(self, text: str): """解析LLM调用的输出。""" return "wrong" not in text.lower()
在这里,我们创建了一个简单的输出解析器,检查LLM的输出中是否存在单词“wrong”。如果不存在,则返回True,表示学生的答案正确。否则,返回False,表示学生的答案错误。
然后,我们可以将此输出解析器无缝地整合到链中,以在运行链时自动解析文本。
# 更新chainchain = LLMChain( llm=chat_model, prompt=prompt, output_parser=GradeOutputParser())
最后,我们可以运行整个学生答案列表的链并打印输出。
# 在循环中运行链
student_answer_list = ["约翰·F·肯尼迪", "JFK", "FDR", "约翰·F·肯尼迪", \
"约翰·肯尼迪", "杰克·肯尼迪", "杰奎琳·肯尼迪", "罗伯特·F·肯尼迪"]
for student_answer in student_answer_list:
print(student_answer + " - " + str(chain.run({'question':question, 'correct_answer':correct_answer, 'student_answer':student_answer})))
print('\n')
# 输出:
# 约翰·F·肯尼迪 - True
# JFK - True
# FDR - False
# 约翰·F·肯尼迪 - True
# 约翰·肯尼迪 - True
# 杰克·肯尼迪 - True
# 杰奎琳·肯尼迪 - False
# 罗伯特·F·肯尼迪 - False
YouTube-Blog/LLMs/langchain-example at main · ShawhinT/YouTube-Blog
Codes to complement YouTube videos and blog posts on VoAGI. – YouTube-Blog/LLMs/langchain-example at main ·…
github.com
限制
提示工程不仅仅是向ChatGPT寻求帮助编写电子邮件或了解量子计算。它是一种改变开发者构建应用程序方式的新的编程范式。
虽然这是一项强大的创新,但它也有其局限性。首先,最佳提示策略依赖于LLM。例如,提示GPT-3“逐步思考”在简单的数学推理任务上产生了显著的性能提升[8]。然而,对于最新版本的ChatGPT,这种策略似乎并不有用(它已经可以逐步思考)。
提示工程的另一个局限性是它需要大规模通用语言模型(如ChatGPT),这会带来显著的计算和财务成本。这对于许多更窄定义的用例来说可能过于复杂,例如字符串匹配、情感分析或文本摘要。
我们可以通过微调预训练语言模型来克服这些限制。这就是我们采用现有语言模型并针对特定用例进行调整的地方。在本系列的下一篇文章中,我们将探讨流行的微调技术,并附有示例Python代码。
👉 关于LLMs的更多信息:Introduction | OpenAI API | Hugging Face Transformers
资源
Connect:我的网站 | 预约通话 | 向我提问
Socials:YouTube 🎥 | LinkedIn | Twitter
Support:给我买杯咖啡 ☕️
数据创业者
一个面向数据领域创业者的社区。👉 加入Discord!
VoAGI.com
[1] arXiv:2302.11382 [cs.SE]
[2] arXiv:2106.09685 [cs.CL]
[3] Andrej Karpathy 在 Microsoft Build 2023 上的 GPT 状态报告
[4] arXiv:2206.07682 [cs.CL]
[5] deeplearning.ai 的开发者 ChatGPT 提示工程
[6] arXiv:2005.14165 [cs.CL]
[7] arXiv:2201.11903 [cs.CL]
[8] arXiv:2210.03493 [cs.CL]