【SpringAIAlibaba新手村系列】(8)持久化会话与 Redis 内存管理

28 阅读7分钟

第八章 持久化会话与 Redis 内存管理

版本标注

  • Spring AI: 1.1.2
  • Spring AI Alibaba: 1.1.2.0

s01 > s02 > s03 > s04 > s05 > s06 > s07 > [ s08 ] s09 > s10 > s11 > s12 > s13 > s14 > s15 > s16 > s17 > s18

"会话一旦能记住历史, 聊天才真正像聊天" -- Redis 负责的不是回答, 而是上下文连续性。

⚠️ 阅读提示:本章中提到的短期记忆,应直接理解为 Agent 状态持久化。重点概念是 ReactAgentMemorySaverRedisSaverthreadId


一、为什么需要会话记忆?

1.1 无状态的问题

在之前的例子中,每次调用 AI 都是独立的

第一次调用:
我:你叫什么?
AI:我叫小爱

第二次调用(AI已经完全忘了之前的事):
我:我叫什么?
AI:抱歉,我不知道

AI 没有"记忆",每次对话都是全新的。

1.2 人类对话 vs AI 对话

人类对话(有记忆)

我:我叫张三
AI:你好张三,我记住了
我:我叫什么?
AI:你叫张三啊

普通 AI 对话(无记忆)

我:我叫张三
AI:你好张三
我:我叫什么?   ← 忘记之前说的了
AI:抱歉,我不知道

1.3 解决方案:ChatMemory

Spring AI Alibaba 1.1.2.0 的理解方式里,短期记忆本质上已经可以看成 Agent 状态的一部分

简单说:

  • 用户每说一句话,都会进入当前会话线程的状态中
  • 模型每回复一句话,也会写回这个状态中
  • 这些状态可以通过 MemorySaverRedisSaver 持久化

所以,本章的核心不再只是"把聊天记录丢进 Redis",而是:

让 Agent 在一个 thread(会话线程)中持续记住上下文。


二、会话记忆的实现方式

2.1 内存版 ChatMemory

最基本的实现,是把 Agent 的短期状态保存在内存里:

ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .saver(new MemorySaver())
    .build();

这种方式的特点是:

  • 简单,适合开发和演示
  • 服务一重启,记忆就没了
  • 不适合多实例部署

2.2 Redis 版 ChatMemory(本章重点)

把 Agent 状态持久化到 Redis 中,优点:

  • 持久化:服务重启,对话历史还在
  • 多实例共享:多个服务实例可以共享同一个 Redis
  • 性能好:Redis 读取速度很快
RedisSaver redisSaver = new RedisSaver(redissonClient);

ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .saver(redisSaver)
    .build();

2.3 实现原理

┌─────────────────────────────────────────────────────────┐
│           Redis 持久化短期记忆原理                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  用户A 发起 thread-001 会话                              │
│  ┌──────┐                                              │
│  │用户A │ ──→ 写入状态 messages/thread-001              │
│  └──────┘                                              │
│                                                         │
│  用户A 再次发言                                         │
│  ┌──────┐       从 Redis 读取 thread-001 状态             │
│  │用户A │ ──→ 恢复历史 messages ──→ 合并当前输入           │
│  └──────┘                              ↓                 │
│                                  发送给 Agent/模型:      │
│                                  [系统消息][历史消息][新问题] │
│                                                         │
└─────────────────────────────────────────────────────────┘

三、项目配置详解

3.1 pom.xml 依赖

<!-- Spring AI Alibaba -->
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>

<!-- Redis 作为短期记忆持久化介质时需要的依赖,请结合官方文档选择对应 saver 实现 -->

这里也要提醒一下理解重点:

  • 记忆章节重点关注 MemorySaver / RedisSaver / threadId
  • Redis 在这里主要扮演 状态持久化介质
  • 会话记忆属于 Agent 状态的一部分

所以学习时要抓住主线:

  • 会话记忆属于 Agent 状态
  • 状态通过 Saver 持久化
  • Redis 只是 Saver 的底层存储之一

3.2 application.yml 配置

server:
  port: 8008

spring:
  application:
    name: SAA-08Persistent

  # Redis 配置(本地的 Redis)
  data:
    redis:
      host: localhost
      port: 6379
      # 可以用密码(如果设置了的话)
      # password: 123456

3.3 配置类

package com.atguigu.study.config;

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.redisson.api.RedissonClient;

/**
 * Agent 记忆配置类
 */
@Configuration
public class SaaLLMConfig
{
    /**
     * 开发环境:使用 MemorySaver
     */
    @Bean
    public ReactAgent memoryAgent(ChatModel chatModel)
    {
        return ReactAgent.builder()
                .name("memory-agent")
                .model(chatModel)
                .saver(new MemorySaver())
                .build();
    }

    /**
     * 生产环境:使用 RedisSaver
     */
    @Bean
    public ReactAgent redisMemoryAgent(ChatModel chatModel, RedissonClient redissonClient)
    {
        return ReactAgent.builder()
                .name("redis-memory-agent")
                .model(chatModel)
                .saver(new RedisSaver(redissonClient))
                .build();
    }
}

四、控制器代码

package com.atguigu.study.controller;

import jakarta.annotation.Resource;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 持久化会话控制器
 * 展示如何使用 Redis 保存对话历史
 */
@RestController
public class ChatMemory4RedisController
{
    @Resource(name = "redisMemoryAgent")
    private ReactAgent redisMemoryAgent;

    /**
     * 带会话记忆的对话
     * 
     * 核心点:使用 threadId 指定会话线程
     * 
     * 接口:http://localhost:8008/chatmemory/chat?msg=你好&userId=user001
     * 
     * 参数说明:
     * - msg:用户的消息
     * - userId:用户ID(用于区分不同用户的会话)
     * 
     * 测试步骤:
     * 1. msg=你好&userId=user001
     * 2. msg=你还记得我刚才说什么吗?&userId=user001
     * 
     * 注意:两次请求使用同一个 userId,底层会映射到同一个 thread,AI 就能"记住"之前的对话内容!
     */
    @GetMapping("/chatmemory/chat")
    public String chat(String msg, String userId)
    {
        RunnableConfig config = RunnableConfig.builder()
                .threadId(userId)
                .build();

        return redisMemoryAgent.call(msg, config).getText();
    }
}

五、底层原理深入

5.1 threadId 的作用

在 Agent Framework 中,短期记忆主要通过 threadId 来区分不同会话线程。

你可以把它理解为:

  • 一个 threadId 对应一段连续对话
  • 同一个 threadId 下,历史消息会被持续追加
  • 不同 threadId 之间彼此隔离

示例:

RunnableConfig config = RunnableConfig.builder()
    .threadId("user001")
    .build();

如果用户两次请求都使用 threadId("user001"),那么 Agent 就会自动读取同一条会话线程里的历史内容。

5.2 Saver 是什么?

Saver 可以理解成"状态保存器",它负责把 Agent 的短期记忆保存下来,并在下次请求时恢复。

常见的两种 Saver:

  • MemorySaver:保存在内存中,适合开发和测试
  • RedisSaver:保存在 Redis 中,适合生产环境
ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .saver(new MemorySaver())
    .build();

5.3 数据在 Redis 中的理解方式

更准确的说法不是"Redis 中只保存聊天记录列表",而是:

Redis 中保存的是某个 threadId 对应的 Agent 状态,其中 messages 是状态中的一个重要字段。

所以理解重点应该放在:

  • Redis 保存的是会话线程状态
  • 消息历史只是这个状态的一部分
  • 后续如果需要,还可以在状态中加入其他上下文信息

5.4 为什么这里几乎没有使用 RedisTemplate?

很多学过传统 Spring Boot + Redis 的同学,看到这个项目时会有一个疑问:

"以前操作 Redis 时,不是经常用 RedisTemplate 吗?为什么这里几乎没看到它?"

这是因为:本章的重点不是手写 Redis 增删改查,而是使用框架提供的 Saver 机制来持久化 Agent 状态

如果按传统方式写,你可能要自己做这些事情:

  • 自己设计 key
  • 自己管理消息列表
  • 自己做序列化和反序列化
  • 自己控制会话隔离
  • 自己处理历史上下文恢复

而在 Spring AI Alibaba 1.1.2.0 的推荐思路里,这些底层细节尽量交给框架处理:

  • 你关注的是 threadId
  • 你关注的是 Saver
  • 你关注的是 Agent 是否能恢复历史上下文

也就是说,代码里没有显式写 RedisTemplate,不代表没有用 Redis,而是因为 Redis 的读写细节被框架封装起来了

什么时候仍然需要 RedisTemplate?

下面这些业务场景,依然很适合自己使用 RedisTemplate

  • 商品缓存
  • 验证码缓存
  • 排行榜
  • 令牌管理
  • 复杂自定义 Redis 结构

简单总结:

  • RedisTemplate:通用 Redis 操作工具
  • RedisSaver:AI Agent 短期记忆持久化工具
RunnableConfig config = RunnableConfig.builder()
    .threadId("user001")
    .build();

agent.call("你好", config);

六、本章小结

6.1 核心概念

概念说明
ReactAgentAgent Framework 的核心执行入口
MemorySaver内存版短期记忆保存器,适合开发测试
RedisSaverRedis 版短期记忆保存器,适合生产环境
threadIdAgent Framework 中更推荐的会话线程标识
messagesAgent 状态中的消息历史,是短期记忆的重要组成部分

6.2 使用流程

1. 配置 Redis(application.yml)
2. 创建 ReactAgent
2. 配置 MemorySaver 或 RedisSaver
3. 调用时通过 RunnableConfig.threadId(...) 指定会话
4. Agent 自动从状态中恢复历史消息
5. 如有需要,再通过 Hook 做裁剪、删除、总结

6.3 注意事项

  • 不同用户使用不同的 userId:这样每个用户的对话是独立存储的
  • Redis 需要提前启动:确保本地 Redis 在运行 redis-server
  • 会话ID可以是用户ID或会话ID:根据业务需求决定
  • 特别注意版本号:本章按 Spring AI Alibaba 1.1.2.0 的官方文档思路整理,学习时请重点关注 ReactAgentMemorySaverRedisSaverthreadId 等概念

本章重点

  1. 理解为什么需要会话记忆
  2. 掌握 ReactAgent + Saver 的短期记忆主线
  3. 学会通过 threadId 区分不同用户会话

下章剧透(s09):

学会了对话记忆后,下一章我们将开启多模态的学习——让 AI 根据文字描述生成图片(Text to Image)!


📝 编辑者:Flittly
📅 更新时间:2026年3月
🔗 相关资源Spring AI ChatMemory | Redis 官方文档