引言:还记得第一次使用 ChatGPT 时,它能记住我们之前对话的惊喜吗?这种“记忆”能力,对于打造一个真正智能的应用至关重要。今天,我将带你深入 Spring AI 的内部,手把手教你如何为你的 AI 应用赋予“持久化记忆”,让每一次对话都充满上下文的情感与智慧。

一、故事开始:为什么我们需要“记忆”?

想象一下这个场景:你正在和一个智能客服聊天。

  • 第一次:你问:“我想买一台笔记本电脑。”

  • AI 回答:推荐了几款产品。

  • 第二次:你接着问:“第一款有什么颜色?”

这时,一个没有记忆的 AI 会困惑:“嗯?您说的‘第一款’是指什么?” 而一个有记忆的 AI 会流畅地回答:“您刚才看到的联想 Yoga 有灰色和银色两种配色。”

这就是会话记忆的价值:它让对话变得连续、自然、有温度。而 Spring AI 要做的,就是帮我们优雅地实现这一点。

二、揭秘:Spring AI 的记忆是如何设计的?

Spring AI 采用了一种非常聪明的“两层架构”,把复杂问题简单化:

🧠 上层:记忆管家(ChatMemory)

  • 角色:就像人类的短期记忆

  • 职责:决定每次和 AI 模型聊天时,要“想起”之前的哪几句话。太多了会乱(Token 超限),太少了会断片。

  • 实现MessageWindowChatMemory,像一个滑动窗口,只保留最近的 N 条对话。

💾 下层:记忆仓库(ChatMemoryRepository)

  • 角色:就像人类的长期记忆日记本

  • 职责:负责把所有的对话原原本本地“写”进数据库(如 MySQL),需要时再完整地“读”出来。

  • 实现JdbcChatMemoryRepository,是连接 Spring AI 和 MySQL 的那座坚固的桥梁。

简单来说:记忆管家(ChatMemory)负责“灵活动脑”,记忆仓库(ChatMemoryRepository)负责“可靠记录”。两者配合,才造就了一个既聪明又靠谱的 AI 助手。

三、动手实战:三步搭建“记忆系统”

理论听起来很美妙,现在我们来真刀真枪地配置一下。整个过程清晰简单,就像搭积木一样。

第 1 步:引入“积木块”(Maven 依赖)

首先,我们需要告诉项目,我们要使用 Spring AI 官方提供的与 MySQL 交互的记忆组件。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

小贴士:这个 starter包就像是一个智能工具箱,只要引入它,Spring AI 就会自动帮我们准备好后面要用到的 JdbcChatMemoryRepository这个核心零件。

第 2 步:配置“连接器”(application.yml)

接下来,我们需要进行一些配置,让工具箱知道如何工作。

spring:
  ai:
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always        # 告诉Spring AI:请帮我自动创建数据库表
            schema: classpath:sql/schema-mysql.sql  # 创建表所需要的SQL脚本文件在这里
  datasource:
    url: jdbc:mysql://localhost:3306/your_database
    username: your_username
    password: your_password

第 3 步:设计“日记本”(数据库表结构)

最后,我们需要设计一下“日记本”的格式,也就是数据库表的结构。创建一个 schema-mysql.sql文件:

CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
    id BIGINT NOT NULL AUTO_INCREMENT,
    conversation_id VARCHAR(36) NOT NULL, -- 会话ID:区分不同聊天
    content TEXT NOT NULL,                -- 消息内容:完整记录说了什么
    type VARCHAR(10) NOT NULL,             -- 消息类型:用户/助手/系统
    timestamp TIMESTAMP NOT NULL,         -- 时间戳:保证对话顺序
    PRIMARY KEY (id),
    INDEX CONVERSATION_ID_IDX (conversation_id, timestamp) -- 索引:加速查询
);

解读:这张表就像一本按不同 conversation_id分册的日记。每次对话,Spring AI 都会自动帮你按时间顺序记好每一笔,完全不用你手动写 SQL 干预!

四、灵魂配置:让记忆系统“活”起来

配置好了基础设施,现在需要创建一个核心的 Bean,让记忆管家和记忆仓库协同工作。

@Configuration
public class AiConfig {
    
    @Bean
    public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(chatMemoryRepository) // 注入仓库
                .maxMessages(50) // 设置记忆窗口:只记得最近50轮对话
                .build();
    }
}

这个 Bean 是整个记忆系统的灵魂

  • 它告诉 MessageWindowChatMemory(记忆管家):“你的记忆数据都存在那个 chatMemoryRepository(记忆仓库)里。”

  • maxMessages(50)是一个黄金平衡点:既保证了对话上下文的连贯性,又防止了因记忆过长导致的 API 令牌消耗过大和模型响应变慢。

五、业务实现:轻松管理聊天历史

最棒的部分来了!由于 Spring AI 已经把脏活累活都干完了,我们在业务层(如 Controller)调用记忆功能变得异常简单。

@RestController
@RequestMapping("/chat/history")
@RequiredArgsConstructor
public class ChatHistoryController {

    // 直接注入Spring AI为我们准备好的“记忆仓库”
    private final ChatMemoryRepository chatMemoryRepository;

    /**
     * 获取某个会话的完整聊天历史
     * 比如:用户想回顾之前和AI的整个聊天过程
     */
    @GetMapping("/{chatId}")
    public List<MessageVO> getChatHistory(@PathVariable String chatId) {
        // 一行代码搞定!底层复杂的数据序列化、SQL查询都由框架完成
        List<Message> messages = chatMemoryRepository.findByConversationId(chatId);
        return messages.stream().map(MessageVO::new).toList();
    }

    /**
     * 删除某个会话的所有记录
     * 比如:用户想清空某次聊天,重新开始
     */
    @DeleteMapping("/{chatId}")
    public void deleteChatHistory(@PathVariable String chatId) {
        // 同样是一行代码,清晰易懂
        chatMemoryRepository.deleteByConversationId(chatId);
    }
}

看到这里的优雅了吗?​ 我们不需要编写任何 SQL 语句,也不需要理解消息是如何被序列化成 JSON 存入 content字段的。Spring AI 的封装,让我们可以像使用普通 Java 集合一样操作数据库中的聊天记录。

六、全景图:一次用户请求的完整旅程

让我们通过一个生动的例子,把上面的知识点串联起来,看看从用户提问到收到回答,记忆系统是如何全程参与的:

  1. 用户提问:在聊天界面输入:“这款电脑的续航怎么样?” 并点击发送。前端的请求中包含了 chatId(例如 "chat_001")。

  2. 记忆唤醒ChatMemory(记忆管家)收到请求,它转身对 ChatMemoryRepository(记忆仓库)说:“嘿,帮我把 chat_001这个会话的日记本拿给我看看。”

  3. 检索记忆:记忆仓库(JdbcChatMemoryRepository)熟练地打开 MySQL 数据库,执行查询,将之前所有关于 chat_001的对话记录按时间顺序整理好,交还给记忆管家。

  4. 智能裁剪:记忆管家发现日记本太厚了(超过了50条),它遵循“最近50条”的原则,只摘取了最新的相关对话(比如用户之前问过“联想Yoga”),准备作为上下文。

  5. 寻求答案:记忆管家将“精简版”的对话历史和新问题一起发送给大语言模型(如 GPT-4)。模型因为有上下文,能精准理解“这款电脑”指的是之前讨论的“联想Yoga”,然后生成关于其续航能力的专业回答。

  6. 珍藏记忆:收到模型的回答后,记忆管家会将用户的新问题AI的新回答作为新的一页,郑重地交给记忆仓库,由它存入数据库的 SPRING_AI_CHAT_MEMORY表中。这样,下次聊天时,这段记忆就又可以被唤醒了。

七、优雅背后的思考

通过今天的实战,我们看到了 Spring AI 如何通过 ChatMemory​ 和 ChatMemoryRepository​ 的巧妙分工,将复杂的会话记忆持久化问题变得如此简单和优雅。

然而,一个优秀的工程师总能发现潜在的问题:这种基于 MessageWindowChatMemory的设计,虽然完美服务于 AI 模型,但它为了实现滑动窗口,会在数据库层面删除旧消息。这意味着,从业务角度看,我们的聊天记录并不是“永久”的。

那么,如何实现既能给 AI 模型提供“短期记忆”,又能为业务留存“永久历史”的完美方案呢?这将是我们在下一篇文章《解决Spring AI聊天记录丢失:自定义Advisor实现持久化存储》中要揭秘的核心内容。


附录:快速回顾

核心概念

作用

好比是

ChatMemoryRepository

持久化引擎,直接与数据库交互

日记本

ChatMemory

记忆管理器,控制上下文窗口

记忆管家

conversation_id

区分不同会话的唯一标识

日记本的分册标签

maxMessages

限制单次对话上下文的长度

记忆的黄金七秒法则

Logo

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

更多推荐