【LangChain】P7 对话记忆完全指南:从原理到实战(下)

目录

本篇博文为【LangChain】系列"对话记忆完全指南:从原理到实战"第三篇博文,前两篇围绕基础介绍、简单示例以及一个酒店服务的实例展开,本篇内容将围绕进阶内容展开。

详细内容请访问:

第五部分:进阶技巧与生产实践

5.1 持久化存储:对话不丢失

问题:内存中的数据会丢失

python 复制代码
# 当前的实现
bot = HotelServiceBot(llm)
bot.chat("user_a", "我叫李明")

# 如果程序重启...
# ❌ 所有对话历史都丢失了!

解决方案:存储到数据库

python 复制代码
import json
from pathlib import Path
from datetime import datetime

class PersistentConversationManager:
    """支持持久化的对话管理器"""
    
    def __init__(self, llm, session_id: str, storage_path: str = "./conversations"):
        """
        参数说明:
        - session_id: 用户的唯一标识(如 user_123)
        - storage_path: 对话文件存储目录
        """
        self.llm = llm
        self.session_id = session_id
        self.storage_path = Path(storage_path)
        self.storage_path.mkdir(exist_ok=True)  # 创建目录
        
        self.messages = []
        
        # 程序启动时,自动加载历史对话
        self._load_history()
    
    def _get_file_path(self) -> Path:
        """获取当前用户的对话文件路径"""
        return self.storage_path / f"{self.session_id}.json"
    
    def _load_history(self):
        """从文件加载历史对话"""
        file_path = self._get_file_path()
        
        if not file_path.exists():
            print(f"[系统] 用户 {self.session_id} 是新用户,创建新会话")
            return
        
        # 读取 JSON 文件
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        # 重建消息对象
        for msg_data in data['messages']:
            msg_type = msg_data['type']
            content = msg_data['content']
            
            if msg_type == 'system':
                self.messages.append(SystemMessage(content=content))
            elif msg_type == 'human':
                self.messages.append(HumanMessage(content=content))
            elif msg_type == 'ai':
                self.messages.append(AIMessage(content=content))
        
        print(f"[系统] 已加载用户 {self.session_id} 的历史对话({len(self.messages)} 条消息)")
    
    def _save_history(self):
        """保存历史对话到文件"""
        file_path = self._get_file_path()
        
        # 序列化消息
        messages_data = []
        for msg in self.messages:
            # 判断消息类型
            if isinstance(msg, SystemMessage):
                msg_type = 'system'
            elif isinstance(msg, HumanMessage):
                msg_type = 'human'
            elif isinstance(msg, AIMessage):
                msg_type = 'ai'
            else:
                continue  # 忽略未知类型
            
            messages_data.append({
                'type': msg_type,
                'content': msg.content,
                'timestamp': datetime.now().isoformat()
            })
        
        # 写入 JSON 文件
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(
                {'messages': messages_data}, 
                f, 
                ensure_ascii=False,  # 支持中文
                indent=2  # 格式化输出
            )
        
        print(f"[系统] 已保存对话历史到 {file_path}")
    
    def chat(self, user_input: str, system_prompt: str = None) -> str:
        """发送消息并自动保存"""
        # 如果是新会话且有系统提示
        if not self.messages and system_prompt:
            self.messages.append(SystemMessage(content=system_prompt))
        
        # 添加用户消息
        self.messages.append(HumanMessage(content=user_input))
        
        # 调用模型
        response = self.llm.invoke(self.messages)
        
        # 添加 AI 回复
        self.messages.append(AIMessage(content=response.content))
        
        # 💾 关键:每次对话后自动保存
        self._save_history()
        
        return response.content
    
    def clear_history(self):
        """清空对话历史"""
        self.messages = []
        self._save_history()
        print(f"[系统] 用户 {self.session_id} 的对话已清空")

使用示例:持久化的威力

python 复制代码
print("===== 第一次运行程序 =====")
manager1 = PersistentConversationManager(
    llm=chat_model,
    session_id="user_123",
    storage_path="./hotel_conversations"
)

response1 = manager1.chat(
    "你好,我叫李明,住在 301 房间",
    system_prompt="你是酒店客服小悦"
)
print(f"小悦: {response1}")

response2 = manager1.chat("我需要一个加湿器")
print(f"小悦: {response2}")

print("\n===== 模拟程序重启 =====")
# 重新创建管理器(模拟程序重启)
manager2 = PersistentConversationManager(
    llm=chat_model,
    session_id="user_123",  # 同一个用户ID
    storage_path="./hotel_conversations"
)

# 继续之前的对话
response3 = manager2.chat("你还记得我的名字和房间号吗?")
print(f"小悦: {response3}")

模型效果:成功加载历史消息

复制代码
===== 第一次运行程序 =====
[系统] 用户 user_123 是新用户,创建新会话
[系统] 已保存对话历史到 hotel_conversations/user_123.json
小悦: 您好,李明先生!很高兴为您服务。请问有什么可以帮您的吗?
[系统] 已保存对话历史到 hotel_conversations/user_123.json
小悦: 好的,李明先生。我马上为您安排将加湿器送到301房间,预计10分钟内送达。请问您还有其他需要吗?

===== 模拟程序重启 =====
[系统] 已加载用户 user_123 的历史对话(5 条消息)
[系统] 已保存对话历史到 hotel_conversations/user_123.json
小悦: 当然记得,您是住在301房间的李明先生。请问加湿器使用起来还满意吗?

查看存储:分类存储本地

完整内容:

json 复制代码
{
  "messages": [
    {
      "type": "system",
      "content": "你是酒店客服小悦",
      "timestamp": "2025-10-02T00:06:38.343860"
    },
    {
      "type": "human",
      "content": "你好,我叫李明,住在 301 房间",
      "timestamp": "2025-10-02T00:06:38.343895"
    },
    {
      "type": "ai",
      "content": "您好,李明先生!很高兴为您服务。请问有什么可以帮您的吗?",
      "timestamp": "2025-10-02T00:06:38.343911"
    },
    {
      "type": "human",
      "content": "我需要一个加湿器",
      "timestamp": "2025-10-02T00:06:38.343920"
    },
    {
      "type": "ai",
      "content": "好的,李明先生。我马上为您安排将加湿器送到301房间,预计10分钟内送达。请问您还有其他需要吗?",
      "timestamp": "2025-10-02T00:06:38.343930"
    },
    {
      "type": "human",
      "content": "你还记得我的名字和房间号吗?",
      "timestamp": "2025-10-02T00:06:38.343938"
    },
    {
      "type": "ai",
      "content": "当然记得,您是住在301房间的李明先生。请问加湿器使用起来还满意吗?",
      "timestamp": "2025-10-02T00:06:38.343949"
    }
  ]
}

5.2 Token 管理策略:控制成本

问题:Token 消耗快速增长

python 复制代码
# 假设每条消息平均 50 tokens
第 1 轮:系统提示(100) + 用户(50) + AI(50) = 200 tokens
第 2 轮:200 + 用户(50) + AI(50) = 300 tokens
第 3 轮:300 + 用户(50) + AI(50) = 400 tokens
...
第 20 轮:2000 tokens(已经很贵了)
第 50 轮:5000 tokens(成本翻倍)

策略 1:按 Token 数量截断

python 复制代码
def trim_messages_by_token(messages: list, max_tokens: int = 4000) -> list:
    """
    根据 token 数量智能截断消息
    
    优点:精确控制成本
    缺点:需要额外的 token 计算
    """
    try:
        from tiktoken import encoding_for_model
        enc = encoding_for_model("gpt-3.5-turbo")
    except ImportError:
        print("⚠️ 需要安装 tiktoken: pip install tiktoken")
        return messages
    
    # 永远保留系统消息
    system_msgs = [m for m in messages if isinstance(m, SystemMessage)]
    other_msgs = [m for m in messages if not isinstance(m, SystemMessage)]
    
    # 计算系统消息的 token
    total_tokens = sum(len(enc.encode(m.content)) for m in system_msgs)
    
    # 从后往前添加消息,直到达到限制
    trimmed_msgs = []
    for msg in reversed(other_msgs):
        msg_tokens = len(enc.encode(msg.content))
        
        if total_tokens + msg_tokens > max_tokens:
            break  # 达到限制,停止添加
        
        trimmed_msgs.insert(0, msg)  # 插入到开头(保持顺序)
        total_tokens += msg_tokens
    
    print(f"[Token管理] 截断前: {len(other_msgs)} 条, 截断后: {len(trimmed_msgs)} 条, Token: {total_tokens}")
    
    return system_msgs + trimmed_msgs

策略 2:智能总结压缩

python 复制代码
def summarize_if_needed(llm, messages: list, threshold: int = 20) -> list:
    """
    当消息过多时,自动总结旧对话
    
    优点:保留关键信息,大幅减少 token
    缺点:总结本身需要一次 LLM 调用
    """
    if len(messages) <= threshold:
        return messages  # 还没到阈值,不需要总结
    
    # 保留系统消息和最近的对话
    system_msgs = [m for m in messages if isinstance(m, SystemMessage)]
    old_msgs = [m for m in messages[1:-10] if not isinstance(m, SystemMessage)]
    recent_msgs = messages[-10:]  # 最近 5 轮对话
    
    if not old_msgs:
        return messages  # 没有旧消息需要总结
    
    # 格式化旧消息
    history_text = "\n".join([
        f"{'用户' if isinstance(m, HumanMessage) else 'AI'}: {m.content}"
        for m in old_msgs
    ])
    
    # 调用 LLM 生成总结
    summary_prompt = f"""请简要总结以下对话的关键信息(不超过 150 字):

{history_text}

总结要点:
1. 用户的基本信息和需求
2. 讨论的主要问题
3. 达成的结论或待办事项"""
    
    summary = llm.invoke([HumanMessage(content=summary_prompt)])
    summary_msg = SystemMessage(content=f"【历史对话摘要】\n{summary.content}")
    
    print(f"[智能总结] 已将 {len(old_msgs)} 条消息压缩为摘要")
    
    return system_msgs + [summary_msg] + recent_msgs

策略 3:混合方案

python 复制代码
class TokenAwareConversationManager:
    """Token 感知的对话管理器"""
    
    def __init__(self, llm, max_tokens: int = 3000, summary_threshold: int = 20):
        self.llm = llm
        self.messages = []
        self.max_tokens = max_tokens
        self.summary_threshold = summary_threshold
    
    def chat(self, user_input: str) -> str:
        # 添加用户消息
        self.messages.append(HumanMessage(content=user_input))
        
        # 智能管理历史
        self._manage_history()
        
        # 调用模型
        response = self.llm.invoke(self.messages)
        self.messages.append(AIMessage(content=response.content))
        
        return response.content
    
    def _manage_history(self):
        """智能历史管理:先尝试总结,再按 token 截断"""
        # 步骤 1:如果消息太多,先总结
        if len(self.messages) > self.summary_threshold:
            self.messages = summarize_if_needed(
                self.llm, 
                self.messages, 
                self.summary_threshold
            )
        
        # 步骤 2:确保不超过 token 限制
        self.messages = trim_messages_by_token(
            self.messages, 
            self.max_tokens
        )

5.3 异步处理:提升并发性能

问题:同步调用效率低

python 复制代码
# 同步方式:依次处理每个用户
for user_id in ["user_1", "user_2", "user_3"]:
    response = bot.chat(user_id, "你好")
    # 每个请求需要 1-2 秒
    # 总耗时:3-6 秒

解决方案:异步并发

python 复制代码
import asyncio
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage

class AsyncConversationManager:
    """支持异步的对话管理器"""
    
    def __init__(self, llm):
        self.llm = llm
        self.sessions = {}
    
    async def chat(self, session_id: str, user_input: str, 
                   system_prompt: str = None) -> str:
        """异步处理消息"""
        # 获取或创建会话
        if session_id not in self.sessions:
            self.sessions[session_id] = []
            if system_prompt:
                self.sessions[session_id].append(
                    SystemMessage(content=system_prompt)
                )
        
        messages = self.sessions[session_id]
        messages.append(HumanMessage(content=user_input))
        
        # 🚀 关键:使用 ainvoke 进行异步调用
        response = await self.llm.ainvoke(messages)
        messages.append(AIMessage(content=response.content))
        
        return response.content

# 使用示例
async def main():
    llm = ChatOpenAI(model="gpt-3.5-turbo")
    manager = AsyncConversationManager(llm)
    
    # 🎯 并发处理多个用户请求
    tasks = [
        manager.chat("user_1", "推荐一本书", "你是图书管理员"),
        manager.chat("user_2", "今天天气怎么样", "你是气象助手"),
        manager.chat("user_3", "晚餐吃什么", "你是美食顾问")
    ]
    
    # 等待所有请求完成
    responses = await asyncio.gather(*tasks)
    
    for i, resp in enumerate(responses, 1):
        print(f"用户{i}: {resp[:50]}...")

# 运行异步任务
# asyncio.run(main())

性能对比:简单对比

复制代码
同步方式:3 个请求,每个 1.5 秒 = 总计 4.5 秒
异步方式:3 个请求,并发执行 = 总计 1.6 秒(提速 65%)

📢 注意:上述内容需要读者注重 Python 异步通信能力,包含 async 以及 ainvoke 能力。该部分能力将在 Python 系列博文中展开。


5.4 监控与日志:生产必备

python 复制代码
import logging
from datetime import datetime

# 配置日志系统
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('conversation.log'),  # 写入文件
        logging.StreamHandler()  # 同时输出到控制台
    ]
)

class MonitoredConversationManager:
    """带监控的对话管理器"""
    
    def __init__(self, llm):
        self.llm = llm
        self.messages = []
        self.logger = logging.getLogger(__name__)
        
        # 统计指标
        self.stats = {
            'total_requests': 0,
            'total_tokens': 0,
            'avg_response_time': 0
        }
    
    def chat(self, user_input: str) -> str:
        """带监控的对话"""
        start_time = datetime.now()
        
        try:
            # 记录请求
            self.logger.info(f"用户输入: {user_input[:100]}")
            
            # 添加用户消息
            self.messages.append(HumanMessage(content=user_input))
            
            # 调用模型
            response = self.llm.invoke(self.messages)
            self.messages.append(AIMessage(content=response.content))
            
            # 计算耗时
            elapsed = (datetime.now() - start_time).total_seconds()
            
            # 更新统计
            self.stats['total_requests'] += 1
            self.stats['avg_response_time'] = (
                (self.stats['avg_response_time'] * (self.stats['total_requests'] - 1) + elapsed)
                / self.stats['total_requests']
            )
            
            # 记录响应
            self.logger.info(
                f"AI回复: {response.content[:100]}, "
                f"耗时: {elapsed:.2f}秒, "
                f"总请求数: {self.stats['total_requests']}"
            )
            
            # ⚠️ 性能告警
            if elapsed > 3.0:
                self.logger.warning(f"⚠️ 响应时间过长: {elapsed:.2f}秒")
            
            return response.content
            
        except Exception as e:
            self.logger.error(f"❌ 对话出错: {str(e)}", exc_info=True)
            raise
    
    def get_stats(self):
        """获取统计信息"""
        return self.stats

日志输出示例

复制代码
2025-10-01 10:30:00,123 - __main__ - INFO - 用户输入: 你好,我叫李明
2025-10-01 10:30:01,456 - __main__ - INFO - AI回复: 你好李明!很高兴认识你, 耗时: 1.33秒, 总请求数: 1
2025-10-01 10:30:05,789 - __main__ - INFO - 用户输入: 推荐一本书
2025-10-01 10:30:09,012 - __main__ - WARNING - ⚠️ 响应时间过长: 3.22秒

📢 注意:上述内容需要读者注重 Python 监控日志能力,包含 logging 等能力。该部分能力将在 Python 系列博文中展开。


总结与思考

核心要点回顾

让我们回顾一下本文的核心知识点:

1. 大模型的记忆本质

复制代码
┌─────────────────────────────────────┐
│  错误认知:大模型有记忆    	          │
│  "它记住了我的名字!"      	          │
└─────────────────────────────────────┘
                  ❌
                  
┌─────────────────────────────────────┐
│  正确理解:外部系统管理历史     	      │
│  "我们把历史消息一起传给它"     	      │
└─────────────────────────────────────┘
                  ✅

关键结论:

  • 大模型本身无状态,每次调用都是独立的
  • "记忆"是通过显式传递历史消息实现的
  • 开发者需要自己管理消息历史和上下文

2. 技术方案演进

阶段 方案 特点 适用场景
传统 LangChain Memory 自动化程度高,但不够灵活 ❌ 已弃用
现代 手动管理消息列表 代码透明,完全可控 ✅ 新项目首选
进阶 LangGraph 复杂状态管理 🎯 复杂工作流

3. 实战的关键设计

多用户支持:

python 复制代码
sessions = {
    "user_a": [消息列表],
    "user_b": [消息列表]
}

历史管理:

python 复制代码
# 策略 1:保留最近 N 轮
messages = messages[-(max_turns * 2):]

# 策略 2:总结 + 保留
summary + recent_messages

持久化:

python 复制代码
# 保存到文件/数据库
json.dump(messages, file)

# 程序重启后加载
messages = json.load(file)

最佳实践建议

1. 从简单开始

python 复制代码
# 第一步:理解基本原理
   messages = []
   messages.append(HumanMessage(content="你好"))
   response = llm.invoke(messages)

2. 逐步增加功能

python 复制代码
# 第二步:封装成类
class SimpleChat:
    def __init__(self, llm):
        self.llm = llm
        self.messages = []
    
    def chat(self, text):
        self.messages.append(HumanMessage(content=text))
        response = self.llm.invoke(self.messages)
        self.messages.append(response)
        return response.content

3. 添加必要的管理

  • 限制历史长度(避免成本失控)
  • 添加系统提示(定义 AI 角色)
  • 实现会话隔离(多用户场景)

进阶:性能优化

  • 使用异步处理提升并发能力
  • 实现智能缓存减少重复调用
  • token 数量精确控制成本

生产级特性

✅ 持久化存储(数据库/文件)

✅ 完善的日志和监控

✅ 异常处理和降级策略

✅ Token 使用统计和告警

✅ 安全性(输入验证、敏感信息过滤)

架构设计原则

复制代码
┌─────────────────────────────────────┐
│         应用层(业务逻辑)             │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│      对话管理层(消息历史)         	  │
│      • 会话隔离              		  │
│      • 历史管理           		      │
│      • 上下文压缩       		      │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│      存储层(持久化)            	  │
│      • 数据库                        │
│      • 缓存            		      │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│      模型层(LLM API)      		  │
└─────────────────────────────────────┘

常见问题解答

Q1:为什么不直接用 ChatGPT 的网页版,而要自己管理消息?

答: 网页版确实方便,但有局限:

  • ❌ 无法集成到自己的应用中
  • ❌ 无法自定义对话逻辑
  • ❌ 无法批量处理或自动化
  • ❌ 数据不在自己手中

使用 LangChain 的优势:

  • ✅ 完全控制对话流程
  • ✅ 与业务系统深度集成
  • ✅ 自定义存储和分析
  • ✅ 支持复杂的工作流

Q2: 历史消息应该保留多少轮?

答: 取决于具体场景:

场景类型 推荐轮数 原因
简单问答 3-5 轮 问题通常独立,不需要太多上下文
客服咨询 5-10 轮 需要记住用户信息和问题细节
深度对话 10-20 轮 话题连贯性强,需要更多历史
代码助手 5-15 轮 需要记住代码上下文和需求

通用建议:

python 复制代码
# 根据 token 限制动态调整
if model == "gpt-3.5-turbo":  # 4K context
    max_history = 5-8
elif model == "gpt-4":  # 8K context
    max_history = 10-15
elif model == "claude-2":  # 100K context
    max_history = 50+

Q3: 如何判断是否需要总结历史?

判断标准:

python 复制代码
需要总结的信号:
✅ 对话超过 20 轮
✅ Token 使用接近上限(如超过 3000)
✅ 早期对话信息仍然重要(如用户资料)
✅ 响应时间变慢

不需要总结的场景:
❌ 短期对话(< 10 轮)
❌ 话题变化快(旧信息已不相关)
❌ 成本敏感(总结本身需要一次 LLM 调用)

实现策略:

python 复制代码
if len(messages) > 20 and contains_important_info(messages[:10]):
    summary = summarize(messages[:10])
    messages = [summary] + messages[10:]

Q4: 多用户场景下如何避免信息泄露?

严格的会话隔离:

python 复制代码
class SecureConversationManager:
    def __init__(self):
        self.sessions = {}
    
    def chat(self, user_id: str, message: str) -> str:
        # ✅ 正确:每个用户独立的会话
        if user_id not in self.sessions:
            self.sessions[user_id] = []
        
        messages = self.sessions[user_id]
        messages.append(HumanMessage(content=message))
        response = llm.invoke(messages)
        
        return response.content
    
    # ❌ 错误示范:共享会话
    # def chat(self, message: str):
    #     self.shared_messages.append(...)  # 所有用户共享!

安全检查清单:

python 复制代码
✅ 每个用户有唯一的 session_id
✅ 消息列表按 user_id 隔离存储
✅ 敏感信息(如用户名、手机号)需要加密存储
✅ 定期清理过期会话
✅ 实现访问控制(用户只能访问自己的历史)

大模型未来展望

短期趋势(1-2 年)

更长的上下文窗口

复制代码
当前:GPT-4 Turbo = 128K tokens
未来:1M+ tokens

影响:
- 可以保留更完整的对话历史
- 减少总结的需求
- 支持更复杂的应用场景(如分析整本书)

原生多模态支持

复制代码
# 未来的对话可能是这样的
messages = [
    HumanMessage(content="这是我的设计图", image="design.png"),
    AIMessage(content="我看到了,建议修改配色"),
    HumanMessage(content="改成这样如何?", image="design_v2.png")
]

流式对话优化

复制代码
# 实时流式响应
async for chunk in llm.astream(messages):
    print(chunk, end="", flush=True)

中期展望(3-5 年)

内置记忆机制

复制代码
# 模型 API 可能会提供原生的会话管理
response = llm.chat(
    user_id="user_123",
    message="你好",
    # 不需要手动传递历史!
)

个性化长期记忆

复制代码
模型能够:
- 记住用户的长期偏好
- 跨会话共享知识
- 自动提取和存储关键信息

智能上下文管理

复制代码
模型自动:
- 识别重要信息(无需人工标注)
- 决定何时总结、何时遗忘
- 优化 token 使用

长期愿景(5 年+)

持续学习能力

复制代码
模型可以从每次交互中学习:
- 个性化回复风格
- 记住用户的纠正和反馈
- 不断优化对话质量

情感与关系记忆

复制代码
不仅记住事实,还能记住:
- 用户的情绪状态
- 对话的情感基调
- 长期的关系建立

2025.10.02 吉林·松原

相关推荐
conkl3 小时前
Flask 与 MySQL 数据库集成:完整的 RESTful API 实现指南
数据库·mysql·flask
何中应3 小时前
MyBatis-Plus字段类型处理器使用
java·数据库·后端·mybatis
迎風吹頭髮4 小时前
UNIX下C语言编程与实践21-UNIX 文件访问权限控制:st_mode 与权限宏的解析与应用
c语言·数据库·unix
炬火初现4 小时前
SQL语句——高级字符串函数 / 正则表达式 / 子句
数据库·sql
TTGGGFF5 小时前
云端服务器使用指南:利用Python操作mysql数据库
服务器·数据库·python
编程充电站pro5 小时前
SQL 性能优化:为什么少用函数在 WHERE 条件中?
数据库·sql
无敌最俊朗@5 小时前
通过Ubuntu和i.MX 6ULL开发板实现网络共享
服务器·数据库·ubuntu
TDengine (老段)5 小时前
TDengine 时序函数 DERIVATIVE 用户手册
大数据·数据库·sql·物联网·时序数据库·iot·tdengine
TDengine (老段)6 小时前
TDengine 时序函数 STATEDURATION 用户手册
大数据·数据库·sql·物联网·时序数据库·iot·tdengine