prompt ="My name is Andrea"
response = chatgpt_call(prompt)print(response)# Output: Nice to meet you, Andrea! How can I assist you today?技术支持
一次性交互的调用
您是否注意到上述实现中有什么隐患?
提示一下,让我们做一次跟进交互:
prompt ="Do you remember my name?"
response = chatgpt_call(prompt)print(response)# Output: I'm sorry, as an AI language model, I don't have the ability to remember specific information about individual users.技术支持
没错,可以看到,每次 API 调用都是一次独立的交互。这意味着模型不会记住之前的输入提示或已给出的回复。每次调用 chatgpt_call() 函数,都会开启一段全新的对话。
我们可以在 messages 列表中使用 user、assistant 和 system 三种角色来构建对话历史。
根据我们的测试交互,使用上述三种角色的规范化对话历史可如下所示:
messages =[{'role':'system','content':'You are friendly chatbot.'},{'role':'user','content':'Hi, my name is Andrea'},{'role':'assistant','content':"Nice to meet you, Andrea! How can I assist you today?"},{'role':'user','content':'Do you remember my name?'}]技术支持
messages =[{'role':'system','content':'You are friendly chatbot.'},{'role':'user','content':'Hi, my name is Andrea'},{'role':'assistant','content':"Nice to meet you, Andrea! How can I assist you today?"},{'role':'user','content':'Do you remember my name?'}]
response = call_chatgpt_with_memory(messages)print(response)# Output: Yes, your name is Andrea.技术支持
print(messages)# Output:[{'role':'user','content':'Hello, my name is Andrea'},{'role':'assistant','content':'Nice to meet you, Andrea! How can I assist you today?'},{'role':'user','content':'Do you remember my name?'},{'role':'assistant','content':'Yes, your name is Andrea.'}]技术支持
from langchain.chains import ConversationChain
conversation = ConversationChain(
llm=llm,
memory=memory
)技术支持
最后,要向 ChatGPT 提交用户提示词,我们可以使用如下的 .predict 方法:
conversation.predict(input="Hello, my name is Andrea")# Output: "Hi Andrea, I'm an AI created to help you with whatever you may need. What can I do for you today?"技术支持
schedule = "There is a meeting at 8am with your product team. \
You will need your powerpoint presentation prepared. \
9am-12pm have time to work on your LangChain \
project which will go quickly because Langchain is such a powerful tool. \
At Noon, lunch at the italian resturant with a customer who is driving \
from over an hour away to meet you to understand the latest in AI. \
Be sure to bring your laptop to show the latest LLM demo."
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
memory.save_context({"input":"Hello"},{"output":"What's up"})
memory.save_context({"input":"Not much, just hanging"},{"output":"Cool"})
memory.save_context({"input":"What is on the schedule today?"},{"output":f"{schedule}"})技术支持
在当今快速发展的数字环境中,随着大型语言模型(LLMs)的兴起,会话式应用变得极为流行。聊天机器人已经改变了我们在日常生活中与应用、网站乃至客户服务渠道的交互方式。
构建您自己的聊天机器人的能力已成为游戏规则的改变者,使企业和个人能够根据特定需求创建个性化、高效且更具吸引力的对话界面。
本文旨在拆解如何在 Python 中利用 LLMs 的能力来构建聊天机器人。我们将探索如何使用 ChatGPT 创建自定义聊天机器人,以及如何使用流行的 LangChain 框架进一步优化它。
如果您对 ChatGPT 和 LangChain 还不熟悉,请查看下方我们的基于 GPT 与 LangChain 的大型语言模型入门代码演练:
本文涵盖了使用 ChatGPT 作为底层模型来开发优化聊天机器人的所有关键环节。我们将在已安装
openai和langchain库的 Jupyter Notebook 中操作。您可以在此 DataLab 工作簿中跟进。接下来,我们将探讨如何使用 OpenAI API 对 ChatGPT 进行基本 API 调用,作为我们对话的构建基块。此外,我们将使用基础的 Python 结构为聊天机器人实现上下文感知。最后,我们将使用 LangChain 优化聊天机器人的实现与对话历史管理,并探索其记忆功能。
基础 ChatGPT API 调用
我们将使用 ChatGPT 作为聊天机器人的底层模型。因此,我们的第一个动手任务是编写一个简单且完整的 ChatGPT API 调用,以便建立首次交互。
下面的 Python 函数仅使用
openai库封装了我们所需的 ChatGPT API 调用。请注意,这里我们使用的是
gpt-3.5-turbo模型,因为它是最强大的 GPT-3.5 系列聊天优化模型之一,同时也是最便宜的模型之一。关于可用不同模型的更多信息,请参见 OpenAI 的文档。需要获取 OpenAI API 密钥的帮助吗?那么这门培训课程 Getting Started with the OpenAI API and ChatGPT 适合您。
让我们一步步分析上述实现!
函数
chatgpt_call()是对 OpenAI 原始函数ChatCompletion.create的封装,用于创建一次聊天补全请求。给定所需的提示词(prompt),该函数会返回相应的 ChatGPT 补全结果。成功调用只需提供要使用的模型 ID(
model)和所需的提示词(prompt)。ChatGPT 返回的补全结果包含元数据以及回复本身。不过,回复文本始终位于
response.choices[0].message[“content”]字段中。有关完整补全内容的更多信息,请参见 OpenAI 的文档。基于该实现,我们可以用一行代码向 ChatGPT 发送提示:
一次性交互的调用
您是否注意到上述实现中有什么隐患?
提示一下,让我们做一次跟进交互:
没错,可以看到,每次 API 调用都是一次独立的交互。这意味着模型不会记住之前的输入提示或已给出的回复。每次调用
chatgpt_call()函数,都会开启一段全新的对话。在构建聊天机器人时,这无疑是个问题,因为如果模型无法记住我们已讨论的内容,就无法进行连续对话。因此,下一步是让模型感知我们之前的交互。
上下文感知
上下文感知使聊天机器人能够基于正在进行的对话理解并做出恰当回应。
通过构建上下文,后续交互可以基于以往内容进行个性化,保持对话连贯并避免重复。此外,具有上下文感知的聊天机器人还能让用户主动提出澄清性问题或给出建议以纠正误解。
总体而言,上下文感知增强了聊天机器人的自然性、有效性和用户体验。接下来,我们将把既往交互的集合称为对话历史或记忆(memory)。
用于 ChatGPT 的原始 OpenAI API 已提供了一种方式,可在每个新提示词中一并向模型发送我们的对话历史。此外,LangChain 框架也提供了更高效、成本更优的记忆管理方式。
理解 System、Assistant 与 User 消息
ChatGPT 的对话历史结构
目前,我们只需提供所需提示词即可直接使用
chatgpt_call()方法。如果更仔细地查看实现,会发现输入提示词被格式化为messages列表的一部分。该列表实际上用于记录对话历史,因此必须采用非常特定的格式。让我们仔细看看!
到目前为止,我们在
chatgpt_call()中对 messages 列表的格式如下:messages列表以一组字典的形式存储对话历史。列表中的每个字典使用两个键值对表示对话中的一条消息。role的值为user,表示该消息由用户发送。content的值为prompt,包含实际的用户提示词。这是一个仅含一次交互的简单示例。尽管
content键始终存储用户提示或聊天机器人的回复,但role键则更为多样。System、Assistant 与 User 角色
键
role表示content的消息归属。在使用 ChatGPT 时,我们最多可以有三种角色:user角色对应与聊天机器人交互的实际用户。assistant角色对应聊天机器人本身。system角色用于向聊天机器人提供指令、指导和上下文。系统消息帮助引导用户交互、设定机器人行为、提供上下文信息,并处理对话中的特定交互。系统消息可以视为对聊天机器人的高层指令,对用户透明。自制示意图。使用 chatGPT 构建聊天机器人时对话链中不同角色的交互。
构建对话历史
我们可以在
messages列表中使用user、assistant和system三种角色来构建对话历史。根据我们的测试交互,使用上述三种角色的规范化对话历史可如下所示:
我们可以稍微修改
call_chatgpt()函数,使其将对话历史作为输入参数:来试试这个新实现吧!
太好了!我们已经让模型能够感知过往交互,从而真正与之对话。不过,我们还需要找到一种自动更新
messages列表的方法。自动化对话历史
我们可以对 API 调用函数稍作扩展,即可实现消息的自动收集:
现在来试试!
再来一次跟进交互:
现在,对话历史会自动更新。最后需要注意的是,随时打印
messages列表即可查看对话历史及其结构。LangChain 框架
到目前为止,我们可以认为当前实现是一个有用的聊天机器人,但可能还不是最优版本。
持续地传入所有以往的提示词和回复,会很快导致令牌数量激增,而 ChatGPT 需要先处理它们,才能返回最新回复。
目前我们使用
.append操作将过往交互串联在一起,以便让聊天机器人具备某种记忆。然而,记忆管理可以通过 LangChain 这类专用包更高效地完成。LangChain 是一个用于开发由语言模型驱动的应用的框架。鉴于聊天机器人是当前最流行的应用之一,LangChain 为构建聊天机器人的对话历史提供了更简洁的实现,以及更高效的替代处理方式。
关于 LangChain 框架本身的更多信息,强烈推荐阅读入门文章Introduction to LangChain for Data Engineering & Data Applications。
使用 LangChain 构建简单对话历史
本节我们将探索如何改用 LangChain 框架来构建聊天机器人。
langchain库可替代上文所用的openai库,因为langchain.llms模块已经允许您用两行代码与 ChatGPT 交互:加载好
llm模型后,让我们导入用于存储对话历史的记忆对象。为存储与聊天机器人的每次过往交互,LangChain 提供了ConversationBufferMemory:现在,终于可以导入
ConversationChain作为封装器,它将使用llm和memory把用户提示词传给 ChatGPT 并返回补全结果:最后,要向 ChatGPT 提交用户提示词,我们可以使用如下的
.predict方法:让我们设置
verbose=True,以查看模型在生成补全时参考的信息。在一次跟进交互中试试:创建补全时 ChatGPT 可用上下文的截图。绿色为详细输出。
最后,让我们验证它能准确记住先前的交互:
创建补全时 ChatGPT 可用上下文的截图。绿色为详细输出。
填充对话历史
我们可以使用
memory.save_context()方法向聊天机器人注入额外上下文。这样无需通过对话提供信息,也能让聊天机器人感知所需内容。在 LangChain 中,使用的关键字是input和output。例如,此外,我们也可以随时运行
print(memory.buffer)或memory.load_memory_variables({})查询记忆内容。LangChain 记忆类型
LangChain 为 ChatGPT 实现了不同类型的记忆。上文使用的
ConversationBufferMemory会记录与 ChatGPT 的每一次交互。然而,我们已经讨论过,在每次新交互中传入整个对话历史会迅速增加需要处理的令牌数。请记住,除了 ChatGPT 每次交互的令牌上限外,其使用成本也与令牌数量相关。在每次新交互中处理完整对话历史,长期来看可能会很昂贵。
为避免持续处理全部对话历史,LangChain 实现了其他优化的记忆管理方式。我们将在下一节探索其中一种更高级的类型。
使用 LangChain 的高级对话历史
一种避免存储每次过往交互的高级记忆管理方式,是只存储交互摘要。
这是我最喜欢的记忆类型,因为它在不牺牲任何过往交互信息的前提下,能够实现真正优化的记忆管理。
让我们开始吧!
请注意,这种记忆类型会使用已定义的
llm来生成以往交互的摘要。此外,该记忆可以在一定最大令牌数(
max_token_limit)内存储完整交互。超过该上限时,会将这些消息的信息纳入摘要。为测试它,让我们创建一次令牌数量较大的交互。考虑以下
schedule:现在,我们只需像之前的示例一样,基于
llm和memory声明一个新的会话链。我们可以使用
.predict来向 ChatGPT 发送提示:创建补全时 ChatGPT 可用上下文的截图。绿色为详细输出。
可以看到,聊天机器人会保留原始对话历史的摘要,从而在每次后续交互中减少 ChatGPT 需要处理的令牌数量。
就是这样!现在我们可以和这个优化过的聊天机器人聊上好几个小时了!
将
verbose=False,即可在笔记本中获得更清爽的问答体验。结语
本文演示了如何使用广为人知的 ChatGPT 模型构建一个具备上下文感知的聊天机器人。
我们看到,构建自有的上下文感知聊天机器人可带来持续的交互。这种迭代方式促成持续的反馈循环,使您能够在对话过程中始终把握用户需求与偏好。
我们介绍了两种管理聊天机器人对话历史的主要方法。其一,使用原生 ChatGPT API,构建可用的原生聊天机器人。其二,使用专用的 LangChain 框架与优化的记忆类型实现。
现在轮到您了!请用本文介绍的构建模块,定制一个符合您特定用例的聊天机器人!
如果您有兴趣同时使用 LangChain 和 ChatGPT 构建其他应用,欢迎观看网络研讨会 Building AI Applications with LangChain and GPT。想快速掌握 NLP?那么请查看关于Python 自然语言处理的模块!