概览
我们将介绍一个如何设计和实现LLM驱动的聊天机器人的示例。这个聊天机器人将能够进行对话并记住以前的交互。
请注意,我们构建的聊天机器人只使用语言模型进行对话。您可能还在寻找其他几个相关概念:
- • 对话式RAG:在外部数据源上实现聊天机器人体验
- • 代理:构建可以执行操作的聊天机器人
本教程将介绍基础知识,对于这两个更高级的主题将会有所帮助。
概念
以下是我们将要使用的一些高级组件:
-
• Chat Models[1] : 聊天机器人界面是基于消息而不是原始文本的,因此最适合使用 Chat Models[2] 而不是文本LLMs。
-
• Prompt Templates[3] : 简化了组装提示的过程,包括默认消息、用户输入、聊天历史和(可选)额外的检索上下文。
-
• Chat History[4] : 允许聊天机器人“记住”过去的交互,并在回答后续问题时考虑它们。
-
• 使用 LangSmith 调试和跟踪应用程序。
我们将介绍如何将上述组件组合在一起,创建一个强大的对话式聊天机器人。
安装
- 安装 Python
- 安装 langchain (版本 v0.2):
pip install langchain
快速入门
首先,让我们直接使用模型。ChatModels 是LangChain 的“可运行”实例,这意味着它们提供了一个与之交互的标准接口。为了简单调用模型,我们可以将一个消息列表传递给 .invoke 方法。
API Reference:HumanMessage[5]
运行结果:
image.png
但是模型本身没有任何状态概念。例如,如果您提出一个后续问题:
image.png
image.png
可以看出这时大模型完全没法正常对话,明明前一句告诉它我是小明,后一句它说我是李华。
我们可以看到它没有将之前的对话转化为上下文,并且无法回答这个问题。这样会导致糟糕的聊天机器人体验!
让我们来看一下 LangSmith 跟踪的示例 : 对话1[6] 对话2[7]
为了解决这个问题,我们需要将整个对话历史传递给模型。让我们看看当我们这样做时会发生什么:
API Reference:AIMessage[8]
运行结果:
image.png
LangSmith 跟踪的示例:对话日志[9]
现在我们可以看到我们得到了一个很好的回答!
这是支持聊天机器人进行对话互动能力的基本思想。那么我们如何最好地实现呢?
历史对话
我们可以使用一个消息历史类来封装我们的模型,使其具有状态。这将跟踪模型的输入和输出,并将它们存储在某个数据存储中。未来的交互将加载这些消息,并作为输入的一部分传递给链条。让我们看看如何使用它!
首先,让我们确保安装了langchain-community,因为我们将在其中使用一个集成来存储消息历史记录。
pip install langchain_community
然后,我们可以导入相关的类并设置我们的链,它将包装模型并添加这个消息历史记录。这里的一个关键部分是我们传递给 get_session_history
的函数。这个函数应该接受一个session_id并返回一个消息历史记录对象。这个session_id用于区分不同的对话,并应该作为调用新链条时的配置的一部分传递进去(我们将展示如何做到这一点)。
API Reference: ChatMessageHistory[10] | BaseChatMessageHistory[11] | RunnableWithMessageHistory[12]
现在我们需要创建一个配置,每次传递给可运行的时候使用。这个配置包含的信息不是直接的输入的一部分,但仍然很有用。在这种情况下,我们想要包含一个session_id。它应该是这样的:
config = {"configurable": {"session_id": "abc2"}}
完整代码:
from langchain_community.llms import Tongyi
from langchain_core.messages import HumanMessage
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 使用 Tongyi LLM,并设置温度为 1,代表模型会更加随机,但也会更加不确定
llm = Tongyi(temperature=1)
# 历史消息
with_message_history = RunnableWithMessageHistory(llm, get_session_history)
# 对话1
config = {"configurable": {"session_id": "abc2"}}
# 将一个消息列表传递给 .invoke 方法
response1 = with_message_history.invoke([HumanMessage(content="你好,我是小明")], config=config)print(response1)
# 模型本身没有任何状态记录,再次提问
response2 = with_message_history.invoke([HumanMessage(content="我的姓名是什么?")], config=config)
print(response2)
太棒了!我们的聊天机器人现在可以记住我们的信息了。但是笔者亲测,目前有报错:Error in RootListenersTracer.on_llm_end callback: KeyError('message')
。
官方文档也有人提出 issues:https://github.com/langchain-ai/langchain/issues/22060 期待后续解决。然后在下面的【提示模版】的完整代码就不会报该错误了,也算一种解法。
image.png
如果我们更改配置以引用不同的session_id,我们可以看到它会重新开始对话。
但是,我们总是可以回到最初的对话(因为我们将其持久化在数据库中)
这就是我们如何支持聊天机器人与许多用户进行对话!
目前,我们所做的只是在模型周围添加了一个简单的持久化层。我们可以通过添加提示模板来使其更复杂和个性化。
提示模板
提示模板有助于将原始用户信息转换为LLM可以处理的格式。在这种情况下,原始用户输入只是一个消息,我们将其传递给LLM。现在让我们稍微复杂一些。首先,让我们添加一个带有一些自定义指示的系统消息(但仍然以消息作为输入)。接下来,我们将添加除了消息之外的更多输入。
首先,让我们添加一个系统消息。为此,我们将创建一个ChatPromptTemplate。我们将利用MessagesPlaceholder 来传递所有的消息。
API Reference: ChatPromptTemplate[13] | MessagesPlaceholder[14]
请注意,这稍微改变了输入类型-我们不再传递一个消息列表,而是传递一个字典,其中包含一个名为messages的键,其值是一个消息列表。
# 将一个消息列表传递给 .invoke 方法
response1 = with_message_history.invoke({"messages": [HumanMessage(content="你好,我是小明")]}, config=config)
print(response1)
打印输出:
image.png
现在我们可以将其与之前相同的消息历史对象包装在一起:
完整代码:
from langchain_community.llms import Tongyi
from langchain_core.messages import HumanMessage
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 提示模板
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一个有用的助手。尽你所能回答所有问题。",
),
MessagesPlaceholder(variable_name="messages"),
]
)
# 历史会话存储
store = {}
# 获取会话历史
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 使用 Tongyi LLM,并设置温度为 1,代表模型会更加随机,但也会更加不确定
llm = Tongyi(temperature=1)
# 构建链式调用
chain = prompt | llm
# 历史消息
with_message_history = RunnableWithMessageHistory(chain, get_session_history)
# 对话1
config = {"configurable": {"session_id": "abc2"}}
# 将一个消息列表传递给 .invoke 方法
response1 = with_message_history.invoke({"messages": [HumanMessage(content="你好,我是小明")]}, config=config)print(response1)
# 模型有历史聊天记录,再次提问
response2 = with_message_history.invoke({"messages":[HumanMessage(content="我的姓名是什么?")]}, config=config)
print(response2)
LangSmith 跟踪的示例:对话日志[15]
image.png
太棒了!现在让我们把提示变得更复杂一点。让我们假设提示模板现在看起来像这样:
请注意,我们在提示符中添加了一个新的 language
输入。我们现在可以调用链并传入我们选择的语言。并且让我们将这个更复杂的链包装在一个消息历史类中。这次,因为输入中有多个键,我们需要指定正确的键来保存聊天记录。
为了帮助您了解内部发生了什么,请查看LangSmith跟踪[16](第二句回复又变回了中文,呵呵)
image.png
管理对话历史
构建聊天机器人时要理解的一个重要概念是如何管理对话历史。如果不加管理,消息列表将无限增长,并可能溢出LLM的上下文窗口。因此,添加一个限制您传入的消息大小的步骤非常重要。
重要的是,您将希望在提示模板之前但在从消息历史记录加载以前的消息之后执行此操作。
我们可以通过在提示符前面添加一个简单的步骤来适当地修改messages键,然后将新链包装在消息历史类中。首先,让我们定义一个函数来修改传入的消息。让我们使它选择k最近的消息。然后我们可以通过在开始时添加它来创建一个新链。
API Reference: RunnablePassthrough[17]
现在让我们试试看!如果我们创建一个超过10条消息的消息列表,我们可以看到它不再记得早期消息中的信息。
# 模型有历史聊天记录,再次提问
response = with_message_history.invoke(
{"messages": messagesList + [HumanMessage(content="我的姓名是什么?")], "language":"english"},
config=config
)
print(response)
打印输出:
image.png
但如果我们询问最近10条信息中的信息,它仍然记得。
打印输出:
image.png
现在让我们将其包装在消息历史记录中,完整代码:
# 模型有历史聊天记录,再次提问
response1 = with_message_history.invoke(
{"messages": messagesList + [HumanMessage(content="我的姓名是什么?")], "language":"english"},
config=config
)
print(response1)
response1 = with_message_history.invoke(
{"messages": messagesList + [HumanMessage(content="我喜欢的冰淇淋是什么?")], "language":"english"},
config=config
)
print(response1)
如果你看一下LangSmith,你可以在LangSmith跟踪中看到到底发生了什么 LangSmith trace[18]
image.png
响应流
现在我们有了一个功能聊天机器人。然而,聊天机器人应用程序的一个真正重要的用户体验考虑是流式传输。LLM有时需要一段时间才能响应,因此为了改善用户体验,大多数应用程序都会在生成每个令牌时流式传输。这允许用户看到进度。
这其实超级容易做到!
所有链都公开了一个.stream方法,使用消息历史记录的链也不例外。我们可以简单地使用该方法来获取流响应。
image.png
如果你看一下LangSmith,你可以在LangSmith跟踪中看到到底发生了什么 LangSmith trace[19]
image.png
小结
本节我们学习了 Chat Models[20] 、Prompt Templates[21]、Chat History[22] ,是非常有趣和值得去扩展的,快去实践一下吧,创造自己的聊天机器人搭子。
后续学习 如果你想深入了解细节,一些值得学习的内容是:
- • 对话式RAG[23]:通过外部数据源启用聊天机器人体验
- • 代理[24]:构建一个可以采取行动的聊天机器人
- • 流媒体[25]:流媒体对于聊天应用程序至关重要
- • 如何添加消息历史[26]:深入了解与消息历史相关的所有内容
如何学习大模型
现在社会上大模型越来越普及了,已经有很多人都想往这里面扎,但是却找不到适合的方法去学习。
作为一名资深码农,初入大模型时也吃了很多亏,踩了无数坑。现在我想把我的经验和知识分享给你们,帮助你们学习AI大模型,能够解决你们学习中的困难。
我已将重要的AI大模型资料包括市面上AI大模型各大白皮书、AGI大模型系统学习路线、AI大模型视频教程、实战学习,等录播视频免费分享出来,需要的小伙伴可以扫取。
一、AGI大模型系统学习路线
很多人学习大模型的时候没有方向,东学一点西学一点,像只无头苍蝇乱撞,我下面分享的这个学习路线希望能够帮助到你们学习AI大模型。
二、AI大模型视频教程
三、AI大模型各大学习书籍
四、AI大模型各大场景实战案例
五、结束语
学习AI大模型是当前科技发展的趋势,它不仅能够为我们提供更多的机会和挑战,还能够让我们更好地理解和应用人工智能技术。通过学习AI大模型,我们可以深入了解深度学习、神经网络等核心概念,并将其应用于自然语言处理、计算机视觉、语音识别等领域。同时,掌握AI大模型还能够为我们的职业发展增添竞争力,成为未来技术领域的领导者。
再者,学习AI大模型也能为我们自己创造更多的价值,提供更多的岗位以及副业创收,让自己的生活更上一层楼。
因此,学习AI大模型是一项有前景且值得投入的时间和精力的重要选择。