前言
在构建聊天机器人或对话系统时,记住对话历史是一个基本需求。用户不希望每次与机器人交互时都要重新介绍自己或重复之前说过的话。LangGraph的Checkpoint功能为我们提供了一个优雅的解决方案,可以轻松实现多轮对话的持久化。
对话持久化的重要性
多轮对话持久化对于构建自然、连贯的对话体验至关重要:
- 它允许对话系统记住用户的上下文和历史交互
- 提供更加个性化和连续的用户体验
- 支持复杂的任务导向对话流程
- 使系统能够引用之前的对话内容
在这篇文章中,我们将学习如何使用LangGraph的Checkpoint功能来实现多轮对话的持久化。
示例功能概述
我们将创建一个简单的对话代理,展示如何保存和恢复对话历史:
- 使用消息列表来存储对话历史
- 实现基本的对话处理逻辑
- 通过多个参数标识不同的用户和对话
- 展示如何在多次交互之间保存和恢复对话历史
完整代码实现
让我们先来看完整的代码实现:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
LangGraph Checkpoint 示例3: 多轮对话持久化
这个示例展示了如何在多轮对话应用中使用Checkpoint功能来保存和恢复对话历史。
应用功能:创建一个简单的对话代理,能够记住之前的对话内容,并通过checkpoint保存对话历史。
"""
from typing_extensions import TypedDict, List
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
print("======= Checkpoint示例3: 多轮对话持久化 =======")
# 定义状态类型
class ConversationState(TypedDict):
messages: List[dict]
# 定义节点函数
def process_message(state: ConversationState) -> ConversationState:
"""处理用户消息并生成回复"""
# 获取最新的用户消息
latest_message = state["messages"][-1]
# 简单的回复逻辑
if "你好" in latest_message["content"] or "hello" in latest_message["content"].lower():
response = "你好!很高兴见到你!有什么我可以帮助你的吗?"
elif "你是谁" in latest_message["content"] or "who are you" in latest_message["content"].lower():
response = "我是一个使用LangGraph构建的对话助手。"
elif "再见" in latest_message["content"] or "bye" in latest_message["content"].lower():
response = "再见!祝你有美好的一天!"
else:
# 在实际应用中,这里可以使用LLM生成更智能的回复
response = f"我收到了你的消息: {latest_message['content']}"
# 将回复添加到消息列表中
new_message = {"role": "assistant", "content": response}
updated_messages = state["messages"] + [new_message]
return {"messages": updated_messages}
# 创建StateGraph
conversation_graph = StateGraph(ConversationState)
# 添加节点
conversation_graph.add_node("process", process_message)
# 设置入口点和出口点
conversation_graph.add_edge(START, "process")
conversation_graph.add_edge("process", END)
# 创建checkpoint saver
checkpointer = InMemorySaver()
# 编译图并添加checkpointer
compiled_graph = conversation_graph.compile(checkpointer=checkpointer)
# 创建会话配置 - 使用多个configurable参数
def create_session_config(user_id: str, conversation_id: str):
return {
"configurable": {
"thread_id": f"{user_id}_{conversation_id}", # 唯一标识会话
"user_id": user_id,
"conversation_id": conversation_id
}
}
# 模拟用户Alice的对话
alice_config = create_session_config("alice", "chat-1")
print("--- Alice的对话 ---")
# 第一轮对话 - 传入初始消息
alice_message1 = [{"role": "user", "content": "你好!"}]
alice_result1 = compiled_graph.invoke({"messages": alice_message1}, config=alice_config)
print(f"用户: {alice_message1[0]['content']}")
print(f"助手: {alice_result1['messages'][-1]['content']}")
# 第二轮对话 - 这次我们需要传递完整的历史消息,否则会覆盖之前的历史
current_messages = alice_result1["messages"] + [{"role": "user", "content": "你是谁?"}]
alice_result2 = compiled_graph.invoke({"messages": current_messages}, config=alice_config)
print(f"用户: {current_messages[-1]['content']}")
print(f"助手: {alice_result2['messages'][-1]['content']}")
# 模拟用户Bob的对话 - 展示不同用户间的会话隔离
bob_config = create_session_config("bob", "chat-1")
print("--- Bob的对话 ---")
bob_message1 = [{"role": "user", "content": "Hello!"}]
bob_result1 = compiled_graph.invoke({"messages": bob_message1}, config=bob_config)
print(f"用户: {bob_message1[0]['content']}")
print(f"助手: {bob_result1['messages'][-1]['content']}")
# Alice的第三轮对话 - 继续她的对话
alice_message3 = alice_result2["messages"] + [{"role": "user", "content": "再见!"}]
alice_result3 = compiled_graph.invoke({"messages": alice_message3}, config=alice_config)
print("--- Alice的继续对话 ---")
print(f"用户: {alice_message3[-1]['content']}")
print(f"助手: {alice_result3['messages'][-1]['content']}")
# 检查Alice的完整对话历史 - 从checkpoint获取
print("--- Alice的完整对话历史(从Checkpoint获取) ---")
alice_state = compiled_graph.get_state(alice_config)
if alice_state and "messages" in alice_state.values:
for message in alice_state.values["messages"]:
print(f"{message['role']}: {message['content']}")
# 额外实验:使用不同的conversation_id - 展示同一用户的多会话隔离
print("======= 不同会话ID的实验 =======")
# Alice的另一个对话(使用不同的conversation_id)
alice_new_chat_config = create_session_config("alice", "chat-2")
alice_new_message = [{"role": "user", "content": "你好!这是我的新对话。"}]
alice_new_result = compiled_graph.invoke({"messages": alice_new_message}, config=alice_new_chat_config)
print(f"Alice的新对话 - 用户: {alice_new_message[0]['content']}")
print(f"Alice的新对话 - 助手: {alice_new_result['messages'][-1]['content']}")
print("注意:即使是同一个用户,使用不同的conversation_id也会创建新的对话历史。")
# 验证两个会话是独立的
print("--- 验证会话独立性 ---")
# 检查第一个会话的状态
session1_state = compiled_graph.get_state(alice_config)
print(f"会话1消息数量: {len(session1_state.values['messages'])}")
# 检查第二个会话的状态
session2_state = compiled_graph.get_state(alice_new_chat_config)
print(f"会话2消息数量: {len(session2_state.values['messages'])}")
执行后你会看到类似以下的输出:
======= Checkpoint示例3: 多轮对话持久化 =======
--- Alice的对话 ---
用户: 你好!
助手: 你好!很高兴见到你!有什么我可以帮助你的吗?
用户: 你是谁?
助手: 我是一个使用LangGraph构建的对话助手。
--- Bob的对话 ---
用户: Hello!
助手: 你好!很高兴见到你!有什么我可以帮助你的吗?
--- Alice的继续对话 ---
用户: 再见!
助手: 再见!祝你有美好的一天!
--- Alice的完整对话历史(从Checkpoint获取) ---
user: 你好!
assistant: 你好!很高兴见到你!有什么我可以帮助你的吗?
user: 你是谁?
assistant: 我是一个使用LangGraph构建的对话助手。
user: 再见!
assistant: 再见!祝你有美好的一天!
======= 不同会话ID的实验 =======
assistant: 再见!祝你有美好的一天!
assistant: 再见!祝你有美好的一天!
======= 不同会话ID的实验 =======
Alice的新对话 - 用户: 你好!这是我的新对话。
Alice的新对话 - 助手: 你好!很高兴见到你!有什么我可以帮助你的吗?
注意:即使是同一个用户,使用不同的conversation_id也会创建新的对话历史。
--- 验证会话独立性 ---
会话1消息数量: 6
会话2消息数量: 2
核心实现要点
-
状态定义:使用TypedDict定义对话状态,包含messages列表用于存储对话历史
-
节点函数:处理用户消息并生成助手回复,保持简单直接的实现方式
-
会话标识:使用
user_id和conversation_id组合创建唯一的会话标识,便于会话隔离和管理 -
消息传递:每次交互都传递完整的消息历史,确保状态正确更新
-
状态获取:使用
compiled_graph.get_state()方法获取和验证保存的会话状态
实际应用场景
多轮对话持久化在很多实际应用场景中都非常有用:
- 聊天机器人:保存用户与机器人的对话历史,提供连续的对话体验
- 客户服务系统:记住客户的问题和之前的解决方案,提高服务效率
- 个人助手:记住用户的偏好和历史交互,提供个性化的服务
- 教育应用:跟踪学生的学习进度和历史表现
- 协作工具:保存团队成员之间的对话历史和协作状态
总结
在本文中,我们学习了如何使用LangGraph的Checkpoint功能来实现多轮对话的持久化。通过定义合适的状态类型、使用多个参数标识会话、以及正确地管理对话历史,我们可以构建出能够记住用户上下文的对话系统。
多轮对话持久化是构建自然、连贯的对话体验的关键,它使我们的应用能够提供更加个性化和连续的用户体验。在实际应用中,我们需要根据具体需求选择合适的Checkpoint实现,并注意相关的优化和最佳实践。")