前几天的实战中,我们的对话记录都保存在内存(RAM)中,程序重启即丢失。今天是 Day 6,我们将引入工业级缓存数据库 Redis,配合 LangChain Community 的 RedisChatMessageHistory 组件,彻底解决记忆丢失问题。本文将手把手教你使用 Docker 快速部署 Redis,并重构代码实现 Session 级别的持久化存储,让 Project Echo 真正拥有“长期记忆”。

一、 项目进度:Day 6 启动

根据 Project Echo 路线图,今天是 Phase 2 的最后一天。
搞定持久化后,我们的基础对话系统就彻底闭环了。

二、 核心原理:Redis 持久化存储全流程

在引入 Redis 后,我们的对话不再是简单的内存读写,而是一个标准的 “读档 -> 生成 -> 存档” 闭环。这一过程完全由 LangChain 的 RunnableWithMessageHistory 和 RedisChatMessageHistory 组件接管。

1. 数据流转机制 (The Data Flow)

整个持久化流程基于 Session ID (会话唯一标识) 运转:

  1. 🔍 读档 (Load History)

    • 当用户发出消息时,系统首先根据 session_id(例如 "user_001")去 Redis 数据库中查询对应的 Key。

    • Redis 将存储的序列化数据(通常是 JSON 格式的历史记录)返回给系统。

    • LangChain 将其反序列化为 Python 对象 (HumanMessage, AIMessage),恢复上下文。

  2. 🧠 生成 (Generate)

    • 系统将恢复的上下文 + 当前用户输入,打包发送给 通义千问 (LLM)

    • LLM 生成回复。

  3. 💾 存档 (Save History)

    • 系统将 [当前用户输入] 和 [AI 最新回复] 追加到历史对象中。

    • LangChain 将更新后的完整历史记录序列化,并写回 Redis,覆盖或追加旧数据。

2. 持久化全链路序列图 (Sequence Diagram)

这张图展示了从用户按下回车,到数据落地 Redis 的完整时序:

3. 底层存储结构揭秘

在 Redis 内部,LangChain 通常会将聊天记录存储为 List 或 JSON String
假设你的 session_id 是 user_001,Redis 中的数据结构大致如下:

  • Key: message_store:user_001 (前缀可配置)

  • Value:

    [
      {"type": "human", "data": {"content": "你好,我叫阿强"}},
      {"type": "ai", "data": {"content": "你好阿强,我是傲娇酱。"}},
      {"type": "human", "data": {"content": "我叫什么?"}},
      {"type": "ai", "data": {"content": "你叫阿强。"}}
    ]

正是这种结构化的存储,保证了无论程序重启多少次,只要 Key 还在,记忆就在。

三、 实战:环境准备

1. 安装 Redis (推荐 Docker)

这里使用docker来安装redis,如果你装了 Docker,在终端运行这一行命令即可启动 Redis:

docker run -d --name echo-redis -p 6379:6379 redis:latest
  • 如果没有 Docker:Windows 用户可以去 Redis 官网 下载安装包,或者使用 Memurai (Redis 的 Windows 兼容版)。

2. 安装 Python 依赖

我们需要安装 redis 驱动库。

pip install redis

3. 更新配置 (src/config/settings.py)

在 .env 文件中添加 Redis 地址(通常是 redis://localhost:6379/0),并在 settings.py 中读取。

修改 src/config/settings.py:

# ... 之前的代码 ...
class Config:
    # ... 其他配置 ...
    # 新增 Redis 配置
    REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")

settings = Config()

四、 实战:代码改造 (LCEL 架构)

我们需要修改 main.py,将内存存储替换为 Redis 存储。

修改文件:main.py

# ==========================================
# Day 6: Persistent Memory with Redis (LCEL)
# ==========================================

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
# 【关键修改】引入 Redis 记忆组件
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory

from src.core.llm import LLMClient
from src.core.prompts import PROMPTS
from src.utils.logger import logger
from src.core.emotion import EmotionEngine
from src.config.settings import settings # 导入配置

# --- 【关键修改】废弃全局变量 store = {} ---
# 我们不再需要在内存里存这个字典了

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    """
    工厂函数:根据 session_id 返回对应的 Redis 历史记录对象
    LangChain 会自动处理连接和读写
    """
    return RedisChatMessageHistory(
        session_id=session_id,
        url=settings.REDIS_URL, # 从配置读取连接串
        ttl=3600 * 24 * 7       # (可选) 设置记忆有效期为 7 天
    )

def main():
    logger.info("--- Project Echo: Day 6 Persistence (Redis) ---")
    
    # 1. 初始化组件 (保持不变)
    client = LLMClient()
    llm = client.get_client()
    emotion_engine = EmotionEngine()
    
    # 2. 构建 Prompt (保持不变)
    sys_prompt_base = PROMPTS["tsundere"]
    prompt = ChatPromptTemplate.from_messages([
        ("system", sys_prompt_base),
        ("system", "{emotion_context}"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])

    # 3. 组装基础链 (保持不变)
    chain = prompt | llm

    # 4. 挂载持久化记忆 (保持不变,只是 get_session_history 变了)
    with_message_history = RunnableWithMessageHistory(
        chain,
        get_session_history, # 这里传入的是连 Redis 的函数
        input_messages_key="input",
        history_messages_key="history",
    )

    print("\n💡 Tip: 即使现在关闭程序,下次回来我也记得你!\n")
    
    # 使用固定的 Session ID 来测试持久化效果
    session_id = "user_permanent_001"

    while True:
        user_input = input("You: ")
        if user_input.lower() in ["quit", "exit"]:
            break
            
        if user_input.strip():
            # --- 情绪分析 & 策略 (保持不变) ---
            current_emotion = emotion_engine.analyze(user_input)
            emotion_instruction = "用户情绪平稳,正常交流。"
            
            if "[愤怒]" in current_emotion:
                emotion_instruction = "⚠️ 警告:用户生气了!请示弱、道歉。"
            # ... 其他情绪逻辑 ...

            try:
                # --- 执行对话 ---
                response = with_message_history.invoke(
                    {
                        "input": user_input, 
                        "emotion_context": emotion_instruction
                    },
                    config={"configurable": {"session_id": session_id}}
                )
                print(f"Bot ({current_emotion}): {response.content}\n")
                
            except Exception as e:
                logger.error(f"调用失败 (请检查Redis是否启动): {e}")

if __name__ == "__main__":
    main()

五、 运行与验证 (见证奇迹)

这是今天最激动人心的时刻。我们要验证 “重启不失忆”

Step 1: 第一次运行

  1. 启动程序:python main.py

  2. You: "我叫阿强,我的密码是123456(开玩笑的)。"

  3. Bot: "哼,谁稀罕知道你的密码... 阿强是吧,记住了。"

  4. 操作:输入 quit 退出程序,或者直接关掉终端。

Step 2: 模拟“断电”
此时程序已经完全停止,内存已被清空。

Step 3: 第二次运行 (验证持久化)

  1. 重新启动程序:python main.py

  2. You: "还记得我叫什么吗?"

  3. Bot: "阿强!你是不是觉得我很闲?这种问题要问几遍?"

🎉 成功!
即使程序重启了,它依然从 Redis 里读出了之前的对话记录。此时如果你用 Redis 客户端工具(如 Redis Desktop Manager)查看,会发现多了一个 message_store:user_permanent_001 的 Key,里面存着刚才的对话。

六、 总结与预告

今天我们完成了 Phase 2 (记忆与情绪) 的最后一块拼图——持久化
现在,已经是一个具备长期记忆高情商的成熟对话机器人了。

Phase 3 预告 (Day 7)
虽然它记得你说过的话,但它对你这个人还是一无所知(比如你的日记、你的文档)。
明天我们将进入硬核的 Phase 3:知识库 RAG。我们将引入 ChromaDB 向量数据库,让 AI 阅读你的本地文档,变成真正懂你的私人助理。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐