Skip to content

Instantly share code, notes, and snippets.

@qinyu
Created May 30, 2023 13:40
Show Gist options
  • Save qinyu/1852ba8a167bd9e17e41aec2caf5dced to your computer and use it in GitHub Desktop.
Save qinyu/1852ba8a167bd9e17e41aec2caf5dced to your computer and use it in GitHub Desktop.
00-easy-learn-prompts-from-langchain.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"authorship_tag": "ABX9TyMWN0jBIfYFvdwhNxc77I5T",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/qinyu/1852ba8a167bd9e17e41aec2caf5dced/00-easy-learn-prompts-from-langchain.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"# 1 环境准备"
],
"metadata": {
"id": "-pH31_KqWmrg"
}
},
{
"cell_type": "markdown",
"source": [
"\n",
"## 1.1 安装需要的 python package\n",
"\n",
"- langchain: 组装 LLM 应用的核心框架,代码主要使用 langchain 提供的功能\n",
"- openai: OpenAI 大语言模型 API 的 python 封装,langchain 内部会用到(**!!会产生费用!!**)\n",
"\n",
"`pip install ...`命令说明:开头`!`代表执行的是 shell 命令而不是 python 代码,末尾`-q`**代表静默安装**\n",
"\n",
"\n",
"\n"
],
"metadata": {
"id": "jlnn4g-1Wc9b"
}
},
{
"cell_type": "code",
"source": [
"!pip install langchain openai -q"
],
"metadata": {
"id": "c3-8d0MRWrFC"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## 1.2 配置 OpenAI API key(注意会产生费用)\n",
"\n",
"> 如果是在本地 python 环境执行,建议直接设置好环境变量,不需要每次都输入"
],
"metadata": {
"id": "4LQySfiLktu-"
}
},
{
"cell_type": "markdown",
"source": [
"配置 OpenAI API key:打开 https://platform.openai.com/account/api-keys 复制一个key,设置环境变量(运行下面代码,在输入框中粘贴key)"
],
"metadata": {
"id": "VIDeLEhdni3e"
}
},
{
"cell_type": "code",
"source": [
"from getpass import getpass \n",
"from langchain.chat_models import ChatOpenAI\n",
"import os\n",
"os.environ['OPENAI_API_KEY'] = getpass() # 设置环境变量\n",
"\n",
"openai = ChatOpenAI(temperature=0) # 建议使用 OpenAI 的 ChatModel,背后是 gpt-3.5-turbo, 和 ChatGPT 一样,价格是 OpenAI 默认模型的 davinci 的十分之一。\n"
],
"metadata": {
"id": "PI7c43tunH0k"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## 准备工作到此完成 \n",
"\n",
"\n",
"---\n",
"\n",
"\n"
],
"metadata": {
"id": "qIeQCArTofEN"
}
},
{
"cell_type": "markdown",
"source": [
"# 2 练习\n",
"\n",
"跟着 LangChain 学习提示工程"
],
"metadata": {
"id": "aTMMc1XFnGwB"
}
},
{
"cell_type": "markdown",
"source": [
"## 2.1 提示语模板\n",
"\n",
"有些指导(Instruct)几乎每条提示语都要反复使用,比如“如果不知道不要杜撰答案”、“以中文回答”等等。我们可以用模板(Template)模式来消除重复。\n"
],
"metadata": {
"id": "wp1TlxDgrP09"
}
},
{
"cell_type": "code",
"source": [
"from langchain.prompts import PromptTemplate\n",
"\n",
"template = \"\"\"\n",
"You are a experienced Tech Lead of a agile team.\n",
"I have just joined your team and I am not familiar with agile practices.\n",
"Please tell me: {question}\n",
"If you don't know the answer, please say \"I don't know\". Don't make up an answer.\n",
"\"\"\"\n",
"\n",
"# question 参数被替换成了真正的问题\n",
"prompt1 = PromptTemplate(template=template,\n",
" input_variables=[\"question\"])\n",
"print(prompt1.format(question=\"When to refactor my code?\"))"
],
"metadata": {
"id": "lelsnH9HriKA"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"可以把上面输出的提示词放到 [ChatGPT](https://chat.openai.com) 中执行看看效果。\n",
"或是执行下面这段代码(**!!注意:会产生费用!!**)"
],
"metadata": {
"id": "xAtshsc4s3d4"
}
},
{
"cell_type": "code",
"source": [
"print(openai.predict(prompt1.format(question=\"When to refactor my code?\")))"
],
"metadata": {
"id": "jr80wp4ssZ3B"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## 2.2 设定角色\n",
"\n",
"OpenAI 最近推出了 Chat API,成本降到了原来的十分之一,模型和 ChatGPT 一样是 gpt-3.5-turbo,可以认为 Chat API 和 ChatGPT 的效果一致。(也就是说可以,在 ChatGPT 中验证提示语后,放到 Chat Model 中执行,效果是一样的。这将进一步降低验证调试的成本!)\n",
"\n",
"Chat API使用方式最大的变化就是交互从文本**字符串(String)**变成了**聊天消息(Chat Message)**,明确地区分了**角色(Role)**。LangChain 也及时提供了 `ChatOpenAI` 还有一系列的 `Messasge` 封装。"
],
"metadata": {
"id": "kXp8Ta6mrxnA"
}
},
{
"cell_type": "code",
"source": [
"from langchain.prompts import (\n",
" ChatPromptTemplate,\n",
" SystemMessagePromptTemplate,\n",
" HumanMessagePromptTemplate,\n",
")\n",
"\n",
"# System (即AI)扮演的角色,\n",
"# 注意不是 AIMessagePromptTemplate(这是 AI 返回的消息)\n",
"system_prompt = SystemMessagePromptTemplate.from_template(\"\\\n",
"You are a experienced Tech Lead of a agile team. \\\n",
"If you don't know the answer, just say that you don't know. \\\n",
"Don't try to make up an answer.\")\n",
"# 人扮演的角色,这里有参数\n",
"human_prompt = HumanMessagePromptTemplate.from_template(\"\\\n",
"I have just joined your team and I am not familiar with agile practices. \\\n",
"Please tell me {question}.\", \n",
" input_variables=[\"question\"])\n",
"# 和 ChatOpenAI 交互的 ChatMessage\n",
"chat_prompt = ChatPromptTemplate.from_messages([\n",
" system_prompt,\n",
" human_prompt\n",
"])\n",
"\n",
"# 最终的提示词\n",
"prompt2 = chat_prompt.format_prompt(question=\"when to refactor my code\")\n",
"# print(prompt2.to_messages())\n",
"print(prompt2.to_string()) # 转换成字符串消息"
],
"metadata": {
"id": "GVhd5tmIn6hg"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"可以把上面输出的提示词放到 [ChatGPT](https://chat.openai.com) 中执行看看效果。\n",
"或是执行下面这段代码(**!!注意:会产生费用!!**)"
],
"metadata": {
"id": "GAssvxZnt46M"
}
},
{
"cell_type": "code",
"source": [
"print(openai(prompt2.to_messages())) # ChatModel 的执行方式"
],
"metadata": {
"id": "GwMdSlSXt46V"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## 2.4 FewShot 样本和 Chain of Thought\n",
"\n",
"我在翻译中经常需要把文章中的术语找出来,总结成一份术语表。原文中的术语有些是英文缩写,有些则是中英文对照。这时我就可以用几个简单的样本(即 FewShot 样本,让 AI 学习找出术语的方法)组合我要找出术语的大段文组合成一段提示语。"
],
"metadata": {
"id": "5cb-ItSovCxf"
}
},
{
"cell_type": "code",
"source": [
"from langchain.prompts import FewShotPromptTemplate\n",
"\n",
"# 两个简单的在文本中找出术语的样本\n",
"# 其中第二个是 Chain of Thoughts 示范\n",
"examples = [\n",
" {\n",
" \"question\": \"\\\"提出的问题越舒服,得到的答案就越有价值,\\\n",
"这一点使用过 ChatGPT 的读者一定有感受。\\\"\\\n",
"这段文字中有哪些术语?\",\n",
" \"answer\": \"\\\"ChatGPT\\\"\"\n",
" },\n",
" {\n",
" \"question\": \"\\\"GPT 提示工程(Prompt Engineering)是一门较新的学科\\\"\\\n",
"这段文字中有哪些术语?\",\n",
" \"answer\":\n",
" \"\"\"这个问题可以如下分析找到答案:\n",
"1. 找到这段文字中\"()\"中间的英文词组,如\"Prompt Engineering\",这个词组就是术语;\n",
"2. 找到这段文字中间的英文缩写,如\"GPT\",这个缩写就是术语;\n",
"3. 术语不包含任何中文字符,\"提示工程(Prompt Engineering)\"不是术语,\"学科\"也不是术语。\n",
"最终的答案就是:\"Prompt Engineering\"、\"GPT\"\n",
" \"\"\"\n",
" }\n",
"]\n",
"\n",
"# 样本组合的模板 \n",
"example_prompt = PromptTemplate(\n",
" input_variables=[\"question\", \"answer\"],\n",
" template=\"Question: {question}\\n{answer}\")\n",
"\n",
"# Few Shot 的提示语模板\n",
"fewshot_prompt = FewShotPromptTemplate(\n",
" example_prompt=example_prompt, # 组合样本\n",
" examples=examples, # 样本\n",
" suffix=\"Question: \\\"{input} \\\"这段文字中有哪些术语?\", # 替换真正要找出术语的文本\n",
" input_variables=[\"input\"]\n",
")\n",
"\n",
"# 打印出最终组合出来的提示语\n",
"print(fewshot_prompt.format(\n",
" input=\"比如 OpenAI 2020 年发布的 gpt-3 用了 570GB 文本,\\\n",
"它能生成的知识就冻结(Frozen)在了发布的 2020 年,2021 年之后发生的事一概不知,\\\n",
"也只知道这 570GB 文本中的知识\")\n",
")"
],
"metadata": {
"id": "NQYFai-3vXhe"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"上面这段提示语 gpt-3.5-turbo 的效果不好,有 Plus 的同学可以试试。\n",
"\n",
"从这里开始,不再提供使用 openai predict 的代码。有兴趣的同学可以参考前面代码或是 [LangChain文档](https://python.langchain.com/)。"
],
"metadata": {
"id": "g0ZXzRhnvss-"
}
},
{
"cell_type": "markdown",
"source": [
"## 2.5 会话历史(Memory 和 Prompt Chain)\n",
"\n",
" ChatGPT 这种对话(Conversation)类的应用都有记忆(Memory)。AI 结合对话(Conversation)的记忆(Memeory)指导(Instruct)逐步接近正确答案。也就是说, 提示语(Prompt)可以链接(Chain)起来,用上一轮问答的输出(Output)作为下一轮问答的输入(Input),同样可以指导(Instruct)AI。"
],
"metadata": {
"id": "l_hAk8LBv7rD"
}
},
{
"cell_type": "code",
"source": [
"from langchain.memory import ConversationBufferMemory\n",
"from langchain.chains import ConversationChain\n",
"from langchain.llms.fake import FakeListLLM\n",
"\n",
"# 替代 OpenAI 的 Fake,Mock 三次提问的回答\n",
"fakellm = FakeListLLM(responses=[\n",
" \"我们不应该在添加新功能时进行重构。\\\n",
"其他情况如修补错误、代码太混乱、\\\n",
"设计完全错误以及 Code Review 时都是\\\n",
"可以考虑进行重构的好机会。\",\n",
" \"在添加新功能时重构可能导致项目的进度延误,\\\n",
"...\\\n",
"并确保不破坏现有代码的功能。\",\n",
" \"在某些情况下,代码太混乱和设计错误,重构可能不足以解决问题。\\\n",
"...\\\n",
"在决定重构或重写代码时,需要考虑代码的当前状况和实际需求。\"\n",
" ]\n",
")\n",
"\n",
"# 对话 Chain,可以一直追问(第一次出现了 Chain)\n",
"conversation = ConversationChain(\n",
" llm=fakellm, # 对话中大模型给出 Fake 答案\n",
" verbose=True, # 显示过程,这样我们可以看见提示语\n",
" memory=ConversationBufferMemory() # 在内存中记录所有对话历史\n",
")\n",
"\n",
"# 连续问了三个问题\n",
"print(conversation.predict(input=\"\"\"下面这些情况里,哪些时候我们不应该进行重构:\n",
"添加新功能时。\n",
"修补错误时。\n",
"代码太混乱,设计完全错误时。\n",
"Code Review 时。\"\"\"))\n",
"print(conversation.predict(input=\"添加新功能时为什么不能重构?\"))\n",
"print(conversation.predict(input=\"代码太混乱,设计完全错误时,应该重写而不是重构吧?\"))"
],
"metadata": {
"id": "XWcYRiIZwXig"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## 2.6 外挂知识\n",
"\n",
"我们把 AI 不知道的知识总填充(Stuff)到提示词里,就可以增强(Augment)AI 的生成能力。AI 相当于“外挂”了一个可信来源(Source)不同于模型训练数据集的知识库,可以基于这个知识库里的新知识进行问答(Question/Answer)。\n",
"\n",
"LangChain 提示语里最后封装的外挂知识数据结构为 Document,里面包括了知识内容`page_content` 字符串和`metadata`字典(一般会有一个叫做`’source’`的 key, 其 value 就是这段内容的来源)。\n",
"\n",
"我们可以把几段关于重构的知识这样填充到提示语里:"
],
"metadata": {
"id": "jnMMqjIDxiqy"
}
},
{
"cell_type": "code",
"source": [
"from langchain.chains.question_answering import load_qa_chain\n",
"from langchain.docstore.document import Document\n",
"from langchain.llms.fake import FakeListLLM\n",
"\n",
"# 把准备好的知识封装成 Document\n",
"docs = [\n",
" Document(page_content=\"重构(名词):对软件内部结构的一种调整,\\\n",
"目的是在不改变软件可观察行为的前提下,\\\n",
"提高其可理解性,降低其修改成本。\",\n",
" metadata={\"source\": \"《重构》第一版\"}),\n",
" Document(page_content=\"重构(动词):使用一系列重构手法,\\\n",
"在不改变软件可观察行为的前提下,调整共结构。\",\n",
" metadata={\"source\": \"《重构》第一版\"}),\n",
" Document(page_content=\"几乎任何情况下我都反对专门拔出时间进行重构。\\\n",
"重构本来就不是一件应该特别拨出时间做的事情,\\\n",
"重构应该随时随地进行。你不应该为重构而重构,你之所以重构,\\\n",
"是因为你想做别的什么事,而重构可以帮助你把那些事做好。\",\n",
" metadata={\"source\": \"《重构》第一版\"}),\n",
" Document(page_content=\"Don Roberts给了我一条准则:\\\n",
"第一次做某件事时只管去做;\\\n",
"第二次做类似的事会产生反感,但无论如何还是可以去做;\\\n",
"第三次再做类似的事,你就应该重构。\",\n",
" metadata={\"source\": \"《重构》第一版\"}),\n",
" Document(page_content=\"最常见的重构时机就是我想给软件添加新特性的时候。\\\n",
"此时,重构的直接原因往往是为了帮助我理解需要修改的代码。\\\n",
"在这里,重构的另一个原动力是:代码的设计无法帮助我经松添加我所需要的特性。\\\n",
"而在调试过程中运用重构,多半是为了让代码更具可读性。\\\n",
"我还发现,重构可以帮助我复审别人的代码。\\\n",
"开始重构前我可以先阅读代码,得到一定程度的理解,并提出一些建议。\\\n",
"一旦想到一些点子,我就会考感是否可以通过重构立即轻松地实现它们。\\\n",
"如果可以,我就会动手。\\\n",
"这样做了几次以后,我可以把代码看得更清楚,提出更多恰当的建议。\",\n",
" metadata={\"source\": \"《重构》第一版\"}),\n",
" Document(page_content=\"有时候你根本不应该重构,例如当你应该重新编写所有代码的时候。\\\n",
"有时候既有代码实在太混乱,重构它还不如重新写一个简单。\\\n",
"作出这种决定很困难,我承认我也没有什么好准则可以判断何时应该放弃重构。\\\n",
"另外,如果项目已近最后期限,你也应该避免重构。\",\n",
" metadata={\"source\": \"《重构》第一版\"}),\n",
"]\n",
"\n",
"# 我们用一个 Fake 答案代替\n",
"fakellm = FakeListLLM(responses=[\"!!!This is the final fake answer.!!!\"])\n",
"# 这里我提前用到了下面要介绍的 Chain 中的一种,这个 Chain 就是把 LLM 包装起来了\n",
"stuff_qa = load_qa_chain(fakellm, # 直接返回 Fake 答案\n",
" chain_type=\"stuff\", # 把文档填充(Stuff)到提示语中\n",
"\t\t\t\t\t\t\t\t\t\t\t\t verbose=True) # 把 Chain 执行的过程打印出来\n",
"# 执行我们的问题\n",
"print(stuff_qa.run(input_documents=docs, question=\"什么时候不能重构?\"))"
],
"metadata": {
"id": "h9dt9x2JyQqu"
},
"execution_count": null,
"outputs": []
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment