Created
May 30, 2023 13:40
-
-
Save qinyu/1852ba8a167bd9e17e41aec2caf5dced to your computer and use it in GitHub Desktop.
00-easy-learn-prompts-from-langchain.ipynb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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