跳到主要内容
基于 LangChain 构建具备记忆功能的聊天机器人 | 极客日志
Python AI 算法
基于 LangChain 构建具备记忆功能的聊天机器人 综述由AI生成 如何使用 LangChain 框架开发一个支持多轮对话记忆的聊天机器人。内容涵盖环境搭建、基础模型调用、消息历史管理、提示词模板设计、上下文窗口优化以及流式输出实现。通过引入 RunnableWithMessageHistory 和自定义过滤函数,解决了模型无状态问题并有效控制了 Token 消耗,同时提供了持久化存储和错误处理的扩展建议,帮助开发者快速构建生产级对话应用。
KernelLab 发布于 2025/2/7 更新于 2026/6/2 21 浏览基于 LangChain 构建具备记忆功能的聊天机器人
概述
本文将详细介绍如何设计和实现一个由大语言模型(LLM)驱动的聊天机器人示例。这个聊天机器人将能够进行多轮对话,并记住先前的交互内容,从而提供更自然、连贯的用户体验。
核心概念
在开始编码之前,我们需要理解几个关键的高级组件:
Chat Models(聊天模型) :这是大语言模型的接口封装,专门用于处理对话格式的消息输入和输出。
Prompt Templates(提示词模板) :简化了组装提示的过程。它可以组合默认的系统消息、用户输入、聊天历史以及可选的额外检索上下文。
Chat History(聊天历史) :允许聊天机器人'记住'过去的对话。通过存储历史消息,模型可以在回答最新问题时参考之前的上下文。
调试与跟踪 :使用工具对应用程序进行调试和跟踪,确保链条或代理内部逻辑的正确性。
我们将介绍如何将上述组件结合在一起,创建一个功能强大且可扩展的对话式聊天机器人。
环境搭建
环境要求
Python 版本需大于等于 3.8.5,但小于 3.12。
langchain 库版本建议为 0.2.x。
langchain-openai 库版本建议为 0.1.x。
依赖包安装
首先,安装核心依赖包:
pip install langchain langchain-openai langchain-community
对于生产环境,建议使用虚拟环境(如 venv 或 conda)来隔离依赖,避免版本冲突。
配置环境变量
为了使用 OpenAI 模型,需要设置 API Key。同时,为了后续可能的调试需求,建议配置 LangSmith 环境变量。
import getpass
import os
os.environ["OPENAI_API_KEY" ] = getpass.getpass("Enter your OpenAI API Key: " )
os.environ["LANGCHAIN_TRACING_V2" ] = "true"
os.environ["LANGCHAIN_API_KEY" ] = getpass.getpass("Enter your LangChain API Key: " )
快速入门:基础模型调用
首先,尝试单独使用语言模型。LangChain 支持许多不同的语言模型,您可以随意选择要使用的语言模型!这里我们以 OpenAI 为例。
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-3.5-turbo" , temperature=0.7 )
API 调用示例
from langchain_core.messages HumanMessage
response = model.invoke([HumanMessage(content= )])
(response.content)
import
"Hi! I'm Bob"
print
Hello Bob! How can you assist me today?
注意 :模型本身并没有任何状态的概念。例如,如果你问一个后续问题:
response = model.invoke([HumanMessage(content="What's my name?" )])
print (response.content)
I 'm sorry, as an AI assistant, I do not have the capability to know your name unless you provide it to me.
我们可以看到它并没有将之前的对话内容纳入上下文,并且无法回答问题。这是因为每次调用都是独立的,模型不知道之前的交互。
为了解决这个问题,我们需要将整个对话历史传递给模型:
from langchain_core.messages import AIMessage
response = model.invoke([
HumanMessage(content="Hi! I'm Bob" ),
AIMessage(content="Hello Bob! How can you assist me today?" ),
HumanMessage(content="What's my name?" )
])
print (response.content)
但是我们不能每次都把历史对话手动放进去。那么我们如何最好地实现它呢?这就是引入消息历史记录类的原因。
历史消息管理 我们可以使用一个 Message History 类来包装我们的模型,使其具有状态。这样可以跟踪模型的输入和输出,并把它们存储起来。未来的交互将加载这些消息,并将它们作为输入的一部分传递给模型。
实现持久化存储 首先,确保安装了 langchain-community 包,因为我们将使用它来存储消息历史。
pip install langchain-community
这里的关键部分是定义 get_session_history 函数。这个函数应该接受一个 session_id,并返回一个消息记录对象。session_id 用于区分不同的对话,并在调用模型时作为 config 的一部分传入。
为了演示,我们使用一个简单的内存字典作为存储后端(生产环境建议使用 Redis 或数据库):
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]
with_message_history = RunnableWithMessageHistory(
model,
get_session_history,
input_messages_key="messages" ,
output_messages_key="output"
)
API 调用测试 创建一个 config,每次都要将其传递给可运行的对象。这个配置应该包含的信息包括 session_id:
config = {"configurable" : {"session_id" : "abc2" }}
response = with_message_history.invoke(
[HumanMessage(content="Hi! I'm Bob" )],
config=config,
)
print (response.content)
response = with_message_history.invoke(
[HumanMessage(content="What's my name?" )],
config=config,
)
print (response.content)
我们的聊天机器人拥有了记忆。如果更改配置使用不同的 session_id,我们会发现它会重新开始对话,也就是说这里的上下文记忆是根据 session_id 进行隔离的。
config = {"configurable" : {"session_id" : "abc3" }}
response = with_message_history.invoke(
[HumanMessage(content="What's my name?" )],
config=config,
)
print (response.content)
然而,因为我们将历史对话保存在数据库中,我们可以随时回到原始的对话。
config = {"configurable" : {"session_id" : "abc2" }}
response = with_message_history.invoke(
[HumanMessage(content="What's my name?" )],
config=config,
)
print (response.content)
这样我们的聊天机器人可以与许多用户进行多轮对话了!
提示词模板设计 现在,我们所做的只是在模型交互前后添加了一个简单的持久化层。我们可以通过添加提示模板来使其变得更复杂和个性化。
系统消息注入 提示模板有助于将输入信息转化为 LLM 可以处理的格式。在这种情况下,原始用户输入只是一条消息,我们将其传递给 LLM。如果我们希望添加一条带有一些自定义说明的系统消息,应该怎么做?
首先,让我们添加一个系统消息。为此,我们将创建一个 ChatPromptTemplate。我们将利用 MessagesPlaceholder 来传递所有的消息。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
(
"system" ,
"You are a helpful assistant. Answer all questions to the best of your ability."
),
MessagesPlaceholder(variable_name="messages" )
])
chain = prompt | model
API 调用 请注意,这里略微改变了输入类型 - 我们现在不再通过消息列表进行传递,而是通过一个包含消息列表的 messages 字典进行传递。
response = chain.invoke({"messages" : [HumanMessage(content="hi! I'm bob" )]})
print (response.content)
现在我们可以将这个 chain 放在之前的消息历史对象中:
with_message_history = RunnableWithMessageHistory(chain, get_session_history)
config = {"configurable" : {"session_id" : "abc5" }}
response = with_message_history.invoke(
[HumanMessage(content="Hi! I'm Jim" )],
config=config,
)
print (response.content)
多语言支持 现在只能使用英文对话,如果我们需要大模型返回指定语言中文,我们可以把提示模板稍微复杂化一点。
prompt = ChatPromptTemplate.from_messages([
(
"system" ,
"You are a helpful assistant. Answer all questions to the best of your ability in {language}."
),
MessagesPlaceholder(variable_name="messages" )
])
chain = prompt | model
我们需要在模板中加入一个新的输入 language。现在就可以传入选择到语言去执行链。
response = chain.invoke(
{"messages" : [HumanMessage(content="hi! I'm bob" )], "language" : "Spanish" }
)
print (response.content)
这将返回西班牙语的回答。现在将这个上面定义的链封装在一个消息历史记录类中。这一次,因为输入中需要传入多个值,我们需要指定 input_messages_key 的名称。
with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="messages" ,
input_variables=["language" ]
)
管理对话历史与上下文窗口 现在有一个问题,如果我们对历史对话不加以管理,消息列表将不断增长,可能会超出 LLM 的上下文窗口限制,导致成本增加或请求失败。因此,我们需要管理对话历史,限制传递的消息大小。
消息过滤策略 我们可以在加载历史消息之后,但在填充提示模板之前,加入这个限制。可以通过在提示词的前面添加一个简单的限制,然后将这个新的链包装在消息历史类中。
from langchain_core.runnables import RunnablePassthrough
def filter_messages (messages, k=10 ):
return messages[-k:]
chain = (
RunnablePassthrough.assign(messages=lambda x: filter_messages(x["messages" ]))
| prompt
| model
)
API 调用验证 创建一个超过 10 条消息的列表,看看它是否不再记得前面消息中的信息。
messages = [
HumanMessage(content="hi! I'm bob" ),
AIMessage(content="hi!" ),
HumanMessage(content="I like vanilla ice cream" ),
AIMessage(content="nice" ),
HumanMessage(content="whats 2 + 2" ),
AIMessage(content="4" ),
HumanMessage(content="thanks" ),
AIMessage(content="no problem!" ),
HumanMessage(content="having fun?" ),
AIMessage(content="yes!" ),
]
response = chain.invoke(
{
"messages" : messages + [HumanMessage(content="what's my name?" )],
"language" : "English" ,
}
)
print (response.content)
由于只保留了最近 10 条,早期的 "hi! I'm bob" 可能被过滤掉,导致模型忘记名字。
response = chain.invoke(
{
"messages" : messages + [HumanMessage(content="what's my fav ice cream" )],
"language" : "English" ,
}
)
print (response.content)
You mentioned that you like vanilla ice cream.
生产环境优化建议 在实际生产中,除了简单的截断,还可以考虑以下策略:
摘要历史 :使用另一个小模型将早期对话总结成一段文本,保留关键信息但减少 Token 消耗。
向量检索 :将历史消息向量化,根据当前问题检索最相关的历史片段,而非仅按时间顺序截取。
动态阈值 :根据模型的最大上下文窗口动态调整 k 值,确保不超过限制。
流式输出 现在我们有了一个聊天机器人功能。然而,对于聊天机器人应用程序来说,一个非常重要的用户体验考虑因素是流式传输。LLMs 有时需要一段时间才能做出回应,因此为了改进用户体验,大多数应用程序会将生成的每个令牌都流式传输回去。这可以让用户实时看到回复的生成过程。
所有的链都提供了一个 .stream 方法。我们可以简单地使用该方法来获取一个流式响应。
config = {"configurable" : {"session_id" : "abc15" }}
for r in with_message_history.stream(
{
"messages" : [HumanMessage(content="hi! I'm todd. tell me a joke" )],
"language" : "English" ,
},
config=config,
):
print (r.content, end="|" , flush=True )
|Sure|,| Todd|!| Here|'s| a| joke| for| you|:
|Why| don|' t| scientists| trust| atoms|?
|Because| they| make | up| everything|!||
这种流式处理方式显著降低了用户的等待焦虑感,特别是在长文本生成场景下。
最佳实践与扩展
错误处理 在生产环境中,网络波动或 API 超时是常见现象。建议在调用链外层包裹 try-except 块,并实现重试机制。
import time
from langchain_core.exceptions import OutputParserException
try :
response = chain.invoke(input_data)
except Exception as e:
print (f"Error occurred: {e} " )
time.sleep(2 ** attempt)
安全性考量
输入过滤 :对用户输入进行敏感词过滤,防止注入攻击。
输出审核 :对模型输出进行审核,确保不包含不当内容。
密钥管理 :不要将 API Key 硬编码在代码中,务必使用环境变量或密钥管理服务。
性能监控 使用 LangSmith 等工具监控 Token 消耗、延迟和错误率。通过分析日志,可以发现哪些 Prompt 模板效率低下,或者哪些会话导致了异常高的 Token 使用量。
总结 本文详细介绍了如何在 LangChain 中创建聊天机器人的基础知识。通过调用 Chat Models 大语言模型,使用 Prompt Templates 提示词模板和 Chat History 管理对话历史,一步一步地实现了一个可以记忆历史对话,支持流式输出的聊天机器人。
我们探讨了从基础的状态无模型到引入消息历史记录,再到提示词工程优化和上下文窗口管理的完整流程。此外,还补充了关于生产环境部署的最佳实践,包括错误处理、安全性和性能监控。掌握这些技能,开发者可以快速构建出既智能又稳健的对话应用。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online