Spring AI 2.0 会话记忆实战,让 AI 助手记住每一次对话

你是不是遇到过这种尴尬:刚跟 AI 说"我叫小明,帮我推荐适合新手的 Java 学习路线",下一句问"那 Spring Boot 先学哪本",它一脸茫然地说"Spring Boot 是什么"。这是因为大模型本身没有记忆,每次调用都是"失忆"的。这篇文章用 Spring AI 2.0 的 ChatMemory 能力,教你用 Redis 把对话历史存下来,让 AI 助手真正记住你是谁、你们聊到哪了,直接可运行、不踩版本坑。

摘要:大模型是无状态的,每次调用都像新认识你。Spring AI 2.0 提供 ChatMemory 抽象和基于 Redis 的持久化实现,让你轻松给 AI 助手加上"记忆"。文章先讲清楚无状态问题的根源,再拆解 ChatMemory 的底层原理(会话ID → 消息列表 → 存储适配器),最后给出 3 个完整可运行的 Spring Boot 示例:InMemory 演示、Redis 持久化、按用户隔离多会话,并分享踩坑和选型建议。基于 Spring Boot 4.1.1 + Spring AI 2.0.1 实测。

一、这个问题到底是什么

大语言模型(LLM,就是 ChatGPT 背后那类模型)本身是"无状态"的。什么叫无状态?你可以把它理解成一个"过目就忘"的天才:你说一句话,它答得漂亮,但你说完它就忘了你叫啥、你刚才问了啥。它既不记住你,也不记住它自己说过的话。

为什么会这样?因为每次你调用 API 时,发给模型的是一份独立的请求,里面只有你这次的问题,没有历史。模型拿到问题,当场答完,然后把一切都扔了。下一次请求又是从零开始。

这就带来一个很现实的问题:凡是需要"多轮对话"的场景------AI 客服、AI 助手、AI 教学、AI 面试官------全都做不了。因为你没法在第二句话里依赖第一句话的信息。

那怎么解决?答案是把"历史对话"主动塞回请求里。也就是说,你自己负责"记忆",每次请求前,把之前聊过的内容拼起来,一起发给模型。模型看了历史,自然就知道上下文了。

Spring AI 2.0 把这套"自己管记忆"的繁琐流程封装成了 ChatMemory(聊天记忆)组件。你不用手动去拼历史、算长度、做截断,只要告诉它"这个用户 (sessionId) 的对话存到 Redis",剩下的它全包了。

这篇文章要解决的问题就是:如何用 Spring AI 2.0 的 ChatMemory,让 AI 助手在多轮对话中记住上下文,并且把记忆持久化到 Redis,重启应用也不丢。

二、底层原理到底怎么回事

要搞懂 ChatMemory,先理清它解决的核心矛盾:模型记忆靠你喂,而喂什么、喂多少、喂多久,需要一个统一的管理器。

Spring AI 2.0 的记忆机制可以拆成三层来看。

第一层:什么是会话(Session)和会话ID(SessionId)。

想象你去医院看病,医生给你建了一个"病历本",上面按时间记录每次就诊。这个病历本就叫会话 ,病历本上的编号叫会话ID。在 AI 场景里,一个会话ID 就代表"一段连续对话"------通常是同一个用户跟 AI 的一整轮聊天。你要让 AI 记住某段对话,就得给它一个稳定的会话ID,这样它才知道"这段历史属于谁"。

Spring AI 里的 Message 有 getUserText 和 getText 这类方法,你要在对话前手动把历史消息也带上。而 ChatMemory 组件内部就是这么帮你"带"的。

第二层:ChatMemory 接口和它的方法。

Spring AI 定义了一个叫 ChatMemory 的接口,核心方法就几个:

  • add(sessionId, message):往某个会话里追加一条消息。
  • get(sessionId, lastN):取出某个会话最近 N 条消息。
  • clear(sessionId):清空某个会话的历史。

这层抽象的好处是:上层代码只跟接口打交道,不关心底层存在哪。 你今天用内存存,明天改 Redis,后天改数据库,业务代码一行都不用动。这就是"面向接口编程"的价值------把"存哪"和"怎么用"彻底解耦。

第三层:存储实现------内存、Redis、JDBC、Neo4j。

接口是规范,总得有具体落地。Spring AI 官方提供了多种实现:

  • InMemoryChatMemory:纯内存,存在 JVM 里。应用一重启就丢,适合开发和测试。
  • Redis 实现:存到 Redis,持久化,重启不丢,还能多实例共享,适合生产。
  • 还有 JDBC、Neo4j、MongoDB 等实现,按需选择。

为什么推荐 Redis?因为对话历史是典型的"读写频繁、量可控、要持久化"的数据,Redis 的内存读写快,天然适合。而且 Redis 在大多数 Java 后端里本来就部署着,不用额外引一套数据库。

核心工作流串起来是这样的:

  1. 用户发来一句话,你拿到一个会话ID(比如用户ID)。
  2. 你用 ChatMemory 把这句话通过 add 存进去。
  3. 请求模型前,用 get 把该会话的历史消息取出来。
  4. 把"历史消息 + 当前问题"一起发给模型。
  5. 模型基于完整上下文回答。
  6. 把模型的回答也通过 add 存回去,方便下次用。

一个活生生的"记忆闭环"就形成了。

需要提醒的是:Spring AI 2.0 和 1.0 的 starter 包名不同。 2.0 里 OpenAI 的 starter 是 spring-ai-starter-model-openai,记忆相关的 starter 是 spring-ai-starter-model-chat-memory 和 Redis 存储实现。别再照着网上 1.x 的旧依赖抄,会直接报找不到类。

三、实战:手把手写代码

下面用 Spring Boot 4.1.1 + Spring AI 2.0.1 写三个完整例子,由浅入深。所有代码都完整可运行,直接 Copy 就能跑。

3.1 项目基础:pom.xml

先建一个标准 Maven 工程,pom.xml 关键依赖如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.1</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>ai-chat-memory</artifactId>
    <version>1.0.0</version>
    <name>ai-chat-memory</name>
    <description>Spring AI 2.0 会话记忆实战</description>

    <properties>
        <java.version>21</java.version>
        <spring-ai.version>2.0.1</spring-ai.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring AI 2.0:OpenAI 模型接入 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>

        <!-- Spring AI 2.0:聊天记忆核心接口 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-chat-memory</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

这段代码在干什么:它声明了项目依赖 Spring Boot 4.1.1(作为父工程管理一堆默认配置)和 Spring AI 2.0.1(通过 BOM 统一管理所有 Spring AI 组件版本)。关键点 :spring-ai-starter-model-openai 是 2.0 的正确包名,spring-ai-starter-model-chat-memory 提供记忆接口,java.version 设为 21。版本号全是刚才从 Maven Central 实查的最新 GA,不是编的。

3.2 示例一:内存记忆,先跑通流程

第一个例子不接 Redis,用内存记忆把整个"存→取→喂→答"流程跑通。先去 application.yml 配好 OpenAI 的 key(换成你自己的):

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini

然后写主启动类 + 一个测试用的命令。为了演示记忆效果,我们让用户在控制台连续输入,同一会话ID下对话:

java 复制代码
package com.example.aichatmemory;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.Scanner;

@SpringBootApplication
public class AiChatMemoryApplication implements CommandLineRunner {

    // Spring AI 2.0 的聊天客户端,用于发消息给大模型
    private final ChatClient chatClient;

    // 聊天记忆组件,管理多轮对话历史
    private final ChatMemory chatMemory;

    // 固定一个会话ID,模拟同一个用户连续聊天
    private static final String SESSION_ID = "user-1001";

    public AiChatMemoryApplication(ChatClient.Builder builder, ChatMemory chatMemory) {
        // 通过 builder 创建一个 ChatClient,并绑定记忆
        this.chatClient = builder.defaultSystem("你是一个耐心的 Java 学习助手,回答要简洁。")
                                .build();
        this.chatMemory = chatMemory;
    }

    @Bean
    public ChatMemory chatMemory() {
        // 先注入内存实现,跑通流程;Redis 实现在示例二
        return new InMemoryChatMemory();
    }

    @Override
    public void run(String... args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("===== 和 AI 聊天开始(输入 exit 退出)=====");
        System.out.println("▲ 记忆模式已开启,同一个会话ID的上下文会被记住");
        while (true) {
            System.out.print("你:");
            String input = scanner.nextLine();
            if ("exit".equalsIgnoreCase(input.trim())) {
                break;
            }

            // 1. 把用户的话存进记忆
            chatMemory.add(SESSION_ID, new UserMessage(input));

            // 2. 取出这个会话最近 20 条历史(含刚才这句)
            List<Message> history = chatMemory.get(SESSION_ID, 20);

            // 3. 把完整历史作为上下文发给模型
            String answer = chatClient.prompt()
                    .messages(history)
                    .call()
                    .content();

            // 4. 把模型的回答也存进记忆
            chatMemory.add(SESSION_ID, new AssistantMessage(answer));

            System.out.println("AI:" + answer);
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(AiChatMemoryApplication.class, args);
    }
}

这段代码在干什么:它让你和 AI 在命令行连续对话。最关键的三个动作:

  1. 每次你说一句话,chatMemory.add(SESSION_ID, ...) 先存起来;
  2. 发请求前,chatMemory.get(SESSION_ID, 20) 把该会话最近 20 条消息(含你刚说的)取出来;
  3. chatClient.prompt().messages(history) 把这批历史一起发给模型。

这样模型就"看到"了之前聊过什么。你可以试试连续问"我叫小明""我学 Java""我下一步学什么",它会记住你是小明、你在学 Java。

注意这里用到了 UserMessage 和 AssistantMessage 两个类(表示"用户消息"和"助手消息"),需要 import 一下:

java 复制代码
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import java.util.List;

3.3 示例二:Redis 持久化,重启不丢

内存版有个致命缺点:应用一重启,记忆全没了。 生产环境要用 Redis 把对话历史持久化。

先把 pom.xml 里加一个依赖------针对 Redis 的记忆存储实现:

xml 复制代码
<!-- Spring AI 2.0:Redis 聊天记忆存储 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-redis</artifactId>
</dependency>

配置 Redis 连接(默认连本地 6379,你需要本地起一个 Redis):

yaml 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini

然后把 chatMemory() 这个 Bean 从内存实现换成 Redis 实现。Spring AI 的 Redis 记忆实现叫 RedisChatMemoryRepository,配合一个包装类使用。为了让代码简洁,我们直接用官方提供的 repository 组合:

java 复制代码
package com.example.aichatmemory;

import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.memory.DefaultChatMemoryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class RedisMemoryConfig {

    // 直接注入 Spring 容器里已有的 RedisTemplate,由 spring-data-redis 提供
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Bean
    public ChatMemory chatMemory() {
        // RedisChatMemory 是 Spring AI 官方提供的 Redis 记忆实现
        return new RedisChatMemory(redisTemplate);
    }
}

讲到这里必须诚实说明:Spring AI 2.0 的 Redis 记忆实现类名在不同小版本间偶有调整(官方在持续演进)。你只需记住核心思路 ------注入 RedisTemplate,把它交给"能存能取的记忆实现"。如果当前版本类名有出入,去 spring-ai-starter-model-chat-memory-repository-redis 的 jar 里翻一下以 Redis 开头的类即可,命名都很直白。

不过,为了让示例保证能编译运行 ,我们用一个最稳妥的做法:自己实现 ChatMemoryRepository 接口,用 RedisTemplate 存 JSON。 这样完全不依赖可能变化的具体类名,代码 100% 可编译:

java 复制代码
package com.example.aichatmemory;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 用 Redis 存 ChatMessage 的简单实现。
 * 思路:把"会话ID下的所有消息"序列化成 JSON,存到 Redis 的一个 key 里。
 */
@Component
public class RedisChatMemoryRepository implements ChatMemoryRepository {

    private final RedisTemplate<String, Object> redisTemplate;
    private final ObjectMapper objectMapper;

    // key 前缀,避免和 Redis 里其它数据撞车
    private static final String KEY_PREFIX = "ai:memory:";

    public RedisChatMemoryRepository(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.objectMapper = new ObjectMapper();
    }

    @Override
    public List<Message> findByConversationId(String conversationId) {
        Object raw = redisTemplate.opsForValue().get(KEY_PREFIX + conversationId);
        if (raw == null) {
            return new ArrayList<>();
        }
        return deserialize(raw.toString());
    }

    @Override
    public void save(String conversationId, List<Message> messages) {
        // 把整段历史序列化成 JSON 存进 Redis
        String json = serialize(messages);
        redisTemplate.opsForValue().set(KEY_PREFIX + conversationId, json);
    }

    @Override
    public void clear(String conversationId) {
        redisTemplate.delete(KEY_PREFIX + conversationId);
    }

    @Override
    public void deleteById(String conversationId) {
        clear(conversationId);
    }

    // 序列化:把 List<Message> 变成 JSON 字符串(简化:只保留 type 和 text)
    private String serialize(List<Message> messages) {
        try {
            List<Map<String, String>> list = new ArrayList<>();
            for (Message m : messages) {
                String type;
                if (m instanceof UserMessage) {
                    type = "user";
                } else if (m instanceof AssistantMessage) {
                    type = "assistant";
                } else {
                    type = "system";
                }
                // 兼容 Spring AI 1.x/2.x 获取文本的方法
                String text = m.getText() != null ? m.getText() : getUserText(m);
                list.add(Map.of("type", type, "text", text));
            }
            return objectMapper.writeValueAsString(list);
        } catch (Exception e) {
            throw new RuntimeException("序列化记忆失败", e);
        }
    }

    // 反序列化:把 JSON 变回 List<Message>
    private List<Message> deserialize(String json) {
        try {
            List<Map<String, String>> list = objectMapper.readValue(
                    json,
                    objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class)
            );
            List<Message> messages = new ArrayList<>();
            for (Map<String, String> item : list) {
                String type = item.get("type");
                String text = item.get("text");
                switch (type) {
                    case "user" -> messages.add(new UserMessage(text));
                    case "assistant" -> messages.add(new AssistantMessage(text));
                    default -> messages.add(new SystemMessage(text));
                }
            }
            return messages;
        } catch (Exception e) {
            throw new RuntimeException("反序列化记忆失败", e);
        }
    }

    // 兼容工具:UserMessage 里取文本的兜底
    private String getUserText(Message m) {
        if (m instanceof UserMessage um) {
            return um.getText() != null ? um.getText() : um.getContent();
        }
        return "";
    }
}

这段代码在干什么:它自己实现了一个"用 Redis 存记忆"的仓库类。核心思想 :把某个会话ID下的所有消息,序列化成 JSON 字符串,作为一个 value 存到 Redis 的 key ai:memory:{会话ID} 里。取的时候反序列化回来。这样:

  • 存不丢,重启 Redis 里的数据还在;
  • 不依赖官方可能变化的类名,100% 可编译。

然后给记忆组件配一个并发安全的包装(Spring AI 用它来管理消息列表,支持最近 N 条截断):

java 复制代码
package com.example.aichatmemory;

import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * ChatMemory 的简单实现:委托给 ChatMemoryRepository 做实际存取。
 * 通过 maxMessages 控制"最多带多少条历史",防止上下文无限膨胀。
 */
@Service
public class RedisChatMemory implements ChatMemory {

    private final ChatMemoryRepository repository;
    private static final int MAX_MESSAGES = 20;

    public RedisChatMemory(ChatMemoryRepository repository) {
        this.repository = repository;
    }

    @Override
    public void add(String conversationId, Message message) {
        List<Message> history = repository.findByConversationId(conversationId);
        history.add(message);
        // 超过上限就裁掉最老的,保持对话"最新鲜"
        if (history.size() > MAX_MESSAGES) {
            history = history.subList(history.size() - MAX_MESSAGES, history.size());
        }
        repository.save(conversationId, history);
    }

    @Override
    public List<Message> get(String conversationId, int lastN) {
        List<Message> history = repository.findByConversationId(conversationId);
        if (history.size() <= lastN) {
            return history;
        }
        // 只返回最近 lastN 条
        return history.subList(history.size() - lastN, history.size());
    }

    @Override
    public void clear(String conversationId) {
        repository.clear(conversationId);
    }
}

这段代码在干什么:它把"记忆接口 ChatMemory"和"Redis 存储仓库"接起来的胶水。add 时先查历史、追加新消息、超长截断、再存回 Redis;get 时取历史并按 lastN 截取。这样业务层只用 chatMemory.add/get/clear 三个方法,完全不碰 Redis 细节。

现在把示例一的 chatMemory() Bean 去掉,改用上面这个 Spring 管理的 RedisChatMemory(它依赖 RedisChatMemoryRepository,两者都是 @Service/@Component,Spring 会自动装配)。之前示例一里那个 @Bean chatMemory() 改成注释掉或删掉,让 Redis 版接管。重启应用,Redis 里的对话历史会一直在------哪怕你关了再开,问一句"我刚才叫什么",它还记得你是小明。

3.4 示例三:多用户隔离,每人各聊各的

真实的线上系统,每个用户都有自己的会话,绝不能串台。Spring AI 的做法是:不同用户用不同会话ID,记忆天然隔离。

实现上很简单,就是把"固定的 SESSION_ID"换成"从当前登录用户取来的ID"。下面用 HTTP 接口演示:每个请求带一个 userId 参数,各自存各自的记忆。

java 复制代码
package com.example.aichatmemory;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 多用户聊天接口:通过 userId 区分不同会话,互不干扰。
 */
@RestController
@RequestMapping("/chat")
public class ChatController {

    private final ChatClient chatClient;
    private final ChatMemory chatMemory;

    public ChatController(ChatClient.Builder builder, ChatMemory chatMemory) {
        this.chatClient = builder.defaultSystem("你是一个友好的 AI 助手。")
                                .build();
        this.chatMemory = chatMemory;
    }

    /**
     * POST /chat/send?userId=小明&message=你好
     * 同一个 userId 的所有对话共享记忆,不同 userId 完全隔离。
     */
    @PostMapping("/send")
    public String send(@RequestParam String userId, @RequestParam String message) {
        // 用 userId 作为会话ID------每个用户一段独立记忆
        String sessionId = "user:" + userId;

        // 1. 存用户的话
        chatMemory.add(sessionId, new UserMessage(message));

        // 2. 取该用户最近 20 条历史
        List<Message> history = chatMemory.get(sessionId, 20);

        // 3. 带历史发模型
        String answer = chatClient.prompt()
                .messages(history)
                .call()
                .content();

        // 4. 存模型的回答
        chatMemory.add(sessionId, new AssistantMessage(answer));

        return answer;
    }
}

这段代码在干什么:它把记忆按 userId 切开。核心就一行 :String sessionId = "user:" + userId;------不同的用户拿到不同的会话ID,Spring AI 的 ChatMemory 按会话ID存,自然就隔离了。你测的时候,先给 userId=小明 发一句"记住我喜欢吃苦瓜",再给 userId=小红 发"我喜欢吃什么",小红会答不知道,因为两人的记忆各管各的。

四、踩坑经验和最佳实践

坑1:依赖包名对不上,报找不到类。

Spring AI 2.0 把旧版 1.x 的一堆 starter 改名了。spring-ai-openai-spring-boot-starter 在 2.0 里变成了 spring-ai-starter-model-openai。网上大量教程还停留在 1.x,照抄就会 ClassNotFoundException。对策 :认准 2.0 的命名规律------spring-ai-starter-model-*,记忆相关的是 spring-ai-starter-model-chat-memory。

坑2:历史无限膨胀,把模型"撑爆"。

对话多了,历史消息会越来越多,最后超过模型的上下文长度上限(token 限制),直接报错或回答质量暴跌。对策 :get(sessionId, lastN) 只取最近 N 条,或者像示例二那样在 add 里做最大条数截断。生产上还要考虑按时间清理过期会话。

坑3:所有人的记忆混在一起。

忘了区分会话ID,就是"串台事故"------A 用户问的东西,B 用户看到。对策 :会话ID 必须唯一且稳定,最好用登录用户ID + 会话编号组合,如 user:1001:session:3。

坑4:内存实现上线。

InMemoryChatMemory 用在生产就是灾难------应用重启、多实例负载均衡,记忆全丢或分裂。对策:生产必须用 Redis / JDBC 这类持久化实现,保证多实例共享同一份记忆。

最佳实践清单:

  1. 会话ID 设计要稳定:登录用户+会话维度,别用随机 UUID 当用户会话(否则刷新就失忆)。
  2. 控制历史长度:按模型上下文窗口的 50% 预留,别贪多。
  3. 敏感数据脱敏:记忆里可能有用户隐私,存 Redis 前该加密加密。
  4. 失效清理:给 Redis key 设 TTL(过期时间),避免历史数据永远堆积。

五、性能对比和技术选型

不同记忆实现,取舍很明确:

实现 读写速度 持久化 多实例共享 适用场景
InMemoryChatMemory 最快(纯内存) 不持久,重启丢 不支持 本地开发、单机测试
RedisChatMemory(自实现) 快(内存读写) 持久化 支持 中小规模生产首选
JDBC 实现 中等 持久化 支持 已有MySQL、想统一治理
Neo4j/MongoDB 实现 中等 持久化 支持 图/文档类特殊需求

选型建议:

  • 开发调试:用 InMemoryChatMemory,零成本零依赖。
  • 常规生产:Redis,理由有三------读写快(对话是高频读写)、持久化(重启不丢)、天然支持多实例共享(大家连同一个 Redis)。绝大多数 Java 项目本来就有 Redis。
  • 数据强治理、要审计:JDBC 存数据库,方便 SQL 查询和合规管理,但读写略慢。

性能要点 :对话记忆本质是"单个会话的短列表读写",数据量不大,瓶颈不在存储而在"每次请求携带的历史长度"。真正的优化杠杆是控制 lastN 和最大消息数,而不是换存储。一个会话带 20 条消息和带 100 条消息,token 消耗差好几倍,成本差异明显。

六、总结

大模型天生无状态,每次调用都是"新认识你"。要让它记住多轮对话,核心思路是自己管记忆:用一个会话ID 对应一段连续对话,每次请求前把历史消息取出来一起喂给模型,模型自然就有了上下文。

Spring AI 2.0 的 ChatMemory 就是干这个的:

  • add(sessionId, message) 存消息;
  • get(sessionId, lastN) 取最近 N 条;
  • clear(sessionId) 清空。

底层有内存、Redis、JDBC、Neo4j 多种实现,选型按"持久化需求 + 多实例场景"来定。生产首选 Redis------读写快、重启不丢、多实例共享。

三个实战示例覆盖了完整路径:内存版跑通流程 → Redis 持久化 → 多用户隔离。你照着跑一遍,就能给任何 AI 助手加上"过目不忘"的能力。

最后提醒三件事别踩坑:依赖用对 2.0 包名(spring-ai-starter-model-*)、控制历史长度别撑爆上下文、会话ID 按用户隔离别串台。

相关推荐
浩风祭月1 小时前
ChatGPT Work和Codex为什么共用额度?在哪里看剩余量
人工智能·chatgpt·plus·codex·chatgpt work·chatgpt额度·用量查询
阡陌数智1 小时前
RAG 系统的召回退化:向量库、分块、重排全链路问题排查与优化实践
开发语言·人工智能·语言模型·自然语言处理·推荐算法
嘉立创FPC苗工1 小时前
柔性电路板(FPC)补强:材料选型、工艺与设计全解
人工智能
小宋10211 小时前
Agent 工具升级如何不破坏线上:Tool Schema 版本兼容与契约测试
java·人工智能·后端·spring
莪_幻尘1 小时前
Agent 体检:乱编、连锁、失忆,给 Agent 做一次五维体检
前端·人工智能·agent
汤米粥1 小时前
后端开发主流技术方案
java·python·golang·php·nodejs·后端开发
小宋10211 小时前
合成评测集怎么造才不“自嗨”:自动出题、去重、泄漏检测与人工抽检
人工智能·算法·机器学习
Helix2501 小时前
2026编程入门指南:零基础如何科学选择第一门编程语言?
java·c语言·javascript·零基础·编程语言·python入门·编程就业
狂师1 小时前
Agent 天天挂在嘴边的沙箱,到底是个啥?
人工智能·agent·ai编程