消息存储概述
在构建基于LLM的对话应用时,管理对话历史记录是至关重要的一环。LangChain提供了多种方式来存储和管理对话历史,从简单的内存存储到持久化的数据库存储。本文将介绍几种常见的消息存储方法及其使用场景。
1. 消息存储在内存
最简单的方式是将聊天历史保存在内存中。下面展示一个简单示例,通过全局Python字典实现内存中的消息存储。
python
from typing import Dict, List, Optional
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import BaseMessage
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 全局字典存储会话历史
memory_store: Dict[str, BaseChatMessageHistory] = {}
# 创建一个函数,用于获取或创建会话历史
def get_session_history(session_id: str) -> BaseChatMessageHistory:
from langchain_core.chat_history import ChatMessageHistory
if session_id not in memory_store:
memory_store[session_id] = ChatMessageHistory()
return memory_store[session_id]
# 创建一个简单的聊天链
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个友好的AI助手。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = prompt | ChatOpenAI(model="gpt-3.5-turbo")
# 包装链以包含消息历史
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
使用单参数默认值:
python
# 调用链并指定会话ID
response = chain_with_history.invoke(
{"input": "你好!"},
config={"configurable": {"session_id": "user_123"}}
)
print(response)
# 再次调用,历史记录会被保留
response = chain_with_history.invoke(
{"input": "我的名字是小明"},
config={"configurable": {"session_id": "user_123"}}
)
print(response)
# 查看存储的消息
print(memory_store["user_123"].messages)
实际项目中的完整示例
下面是一个更完整的示例,展示如何在实际项目中使用内存存储来创建一个简单的聊天机器人:
python
import os
from typing import Dict
from langchain_core.chat_history import BaseChatMessageHistory, ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 设置OpenAI API密钥
os.environ["OPENAI_API_KEY"] = "你的OpenAI API密钥"
# 全局字典存储会话历史
memory_store: Dict[str, BaseChatMessageHistory] = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
"""获取或创建会话历史"""
if session_id not in memory_store:
memory_store[session_id] = ChatMessageHistory()
return memory_store[session_id]
def create_chat_chain():
"""创建聊天链"""
# 定义系统提示和聊天模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个友好、有帮助的AI助手。你的回答应该简洁、准确、用中文回复。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
# 创建LLM
model = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0.7,
)
# 创建基本链
chain = prompt | model
# 包装链以包含消息历史
return RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
def chat_with_bot(chain, user_input: str, session_id: str = "default"):
"""与聊天机器人交互"""
response = chain.invoke(
{"input": user_input},
config={"configurable": {"session_id": session_id}}
)
return response.content
def main():
"""主函数"""
# 创建聊天链
chat_chain = create_chat_chain()
# 用户ID
user_id = "user_123"
print("欢迎使用聊天机器人!输入'退出'结束对话。")
while True:
# 获取用户输入
user_input = input("你: ")
# 检查是否退出
if user_input.lower() in ["退出", "quit", "exit"]:
break
# 获取机器人响应
bot_response = chat_with_bot(chat_chain, user_input, user_id)
# 打印机器人响应
print(f"机器人: {bot_response}")
# 打印当前历史消息数量
history = get_session_history(user_id)
print(f"[调试信息] 当前历史消息数: {len(history.messages)}")
if __name__ == "__main__":
main()
运行这个脚本将启动一个交互式聊天机器人,它会记住对话历史并在回复中考虑之前的交流。
配置会话唯一键
我们可以通过向history_factory_config
参数传递ConfigurableFieldSpec
对象列表来自定义跟踪消息历史的配置参数:
python
from langchain_core.runnables import ConfigurableFieldSpec
# 使用两个参数:user_id和conversation_id
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
history_factory_config=[
ConfigurableFieldSpec(
id="user_id",
annotation=str,
name="User ID",
description="唯一用户标识符",
default="",
is_shared=True,
),
ConfigurableFieldSpec(
id="conversation_id",
annotation=str,
name="Conversation ID",
description="特定对话的标识符",
default="",
is_shared=True,
),
],
)
# 定义获取会话历史的函数
def get_session_history(user_id: str, conversation_id: str) -> BaseChatMessageHistory:
from langchain_core.chat_history import ChatMessageHistory
session_id = f"{user_id}_{conversation_id}"
if session_id not in memory_store:
memory_store[session_id] = ChatMessageHistory()
return memory_store[session_id]
# 调用链条
response = chain_with_history.invoke(
{"input": "你好!"},
config={
"configurable": {
"user_id": "user_123",
"conversation_id": "conv_456"
}
}
)
print(response)
2. 消息持久化到Redis
在许多情况下,持久化对话历史是必要的。RunnableWithMessageHistory
对于get_session_history
可调用如何检索其聊天消息历史是中立的。下面我们演示如何使用Redis进行消息持久化。
配置Redis环境
首先需要安装Redis相关依赖:
bash
pip install "langchain-redis"
如果没有现有的Redis部署,可以启动本地Redis Stack服务器:
bash
docker run -d -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
使用Redis存储消息历史
python
from langchain_redis import RedisChatMessageHistory
def get_redis_history(session_id: str) -> BaseChatMessageHistory:
# 使用Redis存储聊天历史
return RedisChatMessageHistory(
session_id=session_id,
url="redis://localhost:6379"
)
# 包装链以包含Redis消息历史
redis_chain_with_history = RunnableWithMessageHistory(
chain,
get_redis_history,
input_messages_key="input",
history_messages_key="history",
)
# 调用聊天接口,看Redis是否存储历史记录
response = redis_chain_with_history.invoke(
{"input": "你好!我是来自中国的用户"},
config={"configurable": {"session_id": "redis_user_123"}}
)
print(response)
# 再次调用
response = redis_chain_with_history.invoke(
{"input": "你能记住我之前说过什么吗?"},
config={"configurable": {"session_id": "redis_user_123"}}
)
print(response)
Redis历史记录查询
可以直接查询Redis中存储的历史记录:
python
# 获取Redis中存储的历史记录
redis_history = get_redis_history("redis_user_123")
print("Redis中存储的消息历史:")
for message in redis_history.messages:
print(f"{message.type}: {message.content}")
3. 修改聊天历史
修改存储的聊天消息可以帮助聊天机器人处理各种情况,如上下文窗口限制或提供更好的对话体验。
3.1 裁剪消息
LLM和聊天模型有限的上下文窗口,即使没有直接达到限制,也可能希望限制模型处理的信息量。一种解决方案是只加载和存储最近的n条消息:
python
from langchain_core.chat_history import ChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
# 创建一个带有预加载消息的历史记录
history = ChatMessageHistory()
history.add_message(HumanMessage(content="我的名字是小明"))
history.add_message(AIMessage(content="你好小明,很高兴认识你!"))
history.add_message(HumanMessage(content="我喜欢编程"))
history.add_message(AIMessage(content="那太棒了!编程是一项很有价值的技能。"))
# 定义一个函数来裁剪消息
def trim_messages(messages: List[BaseMessage], max_messages: int = 2) -> List[BaseMessage]:
"""只保留最近的max_messages条消息"""
return messages[-max_messages:] if len(messages) > max_messages else messages
# 创建一个新的链,包含裁剪功能
def get_trimmed_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in memory_store:
memory_store[session_id] = ChatMessageHistory()
# 裁剪历史记录,只保留最近的2条消息
current_history = memory_store[session_id]
trimmed_messages = trim_messages(current_history.messages, max_messages=2)
# 清除并重新添加裁剪后的消息
current_history.clear()
for message in trimmed_messages:
if isinstance(message, HumanMessage):
current_history.add_user_message(message.content)
elif isinstance(message, AIMessage):
current_history.add_ai_message(message.content)
return current_history
# 使用裁剪功能的链
trim_chain_with_history = RunnableWithMessageHistory(
chain,
get_trimmed_history,
input_messages_key="input",
history_messages_key="history",
)
调用这个新链并检查消息:
python
# 初始化会话
memory_store["trim_session"] = history
# 调用链
response = trim_chain_with_history.invoke(
{"input": "我最喜欢的编程语言是Python"},
config={"configurable": {"session_id": "trim_session"}}
)
print(response)
# 检查裁剪后的历史记录
print("裁剪后的历史记录:")
for message in memory_store["trim_session"].messages:
print(f"{message.type}: {message.content}")
可以看到历史记录已经删除了两条最旧的消息,同时在末尾添加了最近的对话。下次调用链时,trim_messages
将再次被调用,只有最近的两条消息将被传递给模型。
4. 总结记忆
另一种管理长对话的方法是使用额外的LLM调用来生成对话摘要,而不是保留完整的历史记录:
python
from langchain_core.prompts import PromptTemplate
from langchain_core.messages import SystemMessage
# 创建一个新的聊天历史
summary_history = ChatMessageHistory()
summary_history.add_message(HumanMessage(content="我的名字是小红"))
summary_history.add_message(AIMessage(content="你好小红,很高兴认识你!"))
summary_history.add_message(HumanMessage(content="我是一名医生"))
summary_history.add_message(AIMessage(content="医生是一个非常崇高的职业!您在哪个领域专长呢?"))
# 创建一个修改后的提示,让LLM意识到它将收到一个摘要
summary_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个友好的AI助手。下面是之前对话的摘要:{summary}"),
("human", "{input}")
])
summary_chain = summary_prompt | ChatOpenAI(model="gpt-3.5-turbo")
# 创建一个函数来总结对话历史
def summarize_history(messages: List[BaseMessage]) -> str:
"""总结对话历史"""
if not messages:
return "这是对话的开始。"
# 创建总结提示
summarize_prompt = PromptTemplate.from_template(
"以下是一段对话历史,请用一到两句话总结重要信息:\n{chat_history}"
)
# 格式化对话历史
chat_history_str = "\n".join(
[f"{m.type}: {m.content}" for m in messages]
)
# 使用LLM生成摘要
summarizer = ChatOpenAI(model="gpt-3.5-turbo")
summary = summarizer.invoke(summarize_prompt.format(chat_history=chat_history_str))
return summary.content
# 定义一个函数来获取带摘要的会话历史
def get_summarized_history(session_id: str) -> tuple[BaseChatMessageHistory, str]:
if session_id not in memory_store:
memory_store[session_id] = ChatMessageHistory()
return memory_store[session_id], "这是对话的开始。"
# 获取当前历史并生成摘要
current_history = memory_store[session_id]
summary = summarize_history(current_history.messages)
return current_history, summary
# 创建一个包装函数,用于RunnableWithMessageHistory
def get_history_with_summary(session_id: str) -> BaseChatMessageHistory:
history, _ = get_summarized_history(session_id)
return history
# 修改链以使用摘要
def run_with_summary(inputs: dict, session_id: str) -> dict:
history, summary = get_summarized_history(session_id)
# 添加摘要到输入
inputs_with_summary = inputs.copy()
inputs_with_summary["summary"] = summary
# 调用链
response = summary_chain.invoke(inputs_with_summary)
# 更新历史
history.add_message(HumanMessage(content=inputs["input"]))
history.add_message(AIMessage(content=response.content))
return response
# 初始化会话
memory_store["summary_session"] = summary_history
# 使用摘要运行对话
response = run_with_summary(
{"input": "你能告诉我你记得关于我的什么信息?"},
"summary_session"
)
print(response.content)
# 查看聊天历史记录
print("\n聊天历史记录:")
for message in memory_store["summary_session"].messages:
print(f"{message.type}: {message.content}")
# 获取生成的摘要
_, summary = get_summarized_history("summary_session")
print("\n生成的摘要:")
print(summary)
请注意,再次调用链式模型会生成一个新的摘要,该摘要包括初始摘要以及新的消息等。您还可以设计一种混合方法,其中一定数量的消息保留在聊天历史记录中,而其他消息则被摘要。
结论
LangChain提供了多种灵活的方式来管理聊天历史记录,从简单的内存存储到持久化的数据库存储,以及各种修改和优化历史记录的方法。根据应用场景的不同,可以选择最适合的方法来提高对话体验和性能。
- 对于简单的应用,内存存储是最直接的选择
- 对于需要持久化的应用,可以选择Redis或其他数据库存储
- 对于长对话,可以使用裁剪或摘要方法来管理上下文窗口
通过合理管理聊天历史记录,可以构建出更加智能、自然的对话应用。
章节总结
在本章中,我们深入探讨了LangChain中的消息存储与管理机制,包括:
- 内存存储 - 使用Python字典在内存中存储会话历史,适合简单应用和原型开发
- Redis持久化 - 将对话历史持久化到Redis数据库,适合需要长期保存会话的应用
- 消息管理技术 - 包括裁剪消息和总结记忆两种主要方法,用于处理长对话和上下文窗口限制
这些技术可以帮助开发者构建更加智能、自然的对话应用,提高用户体验。通过合理选择和组合这些方法,可以根据具体应用场景优化LLM的对话能力。
下一章:06_LangChain高级应用 - 我们将探索LangChain的更高级应用,包括代理、工具使用和复杂工作流的构建。