惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

博客园 - work hard work smart

Spring AI 提示词工程实战:让大模型更懂你的意图 Spring AI ChatClient 深度解析:优雅构建大模型应用的利器 Spring AI Alibaba DashScopeChatModel 实战 Spring 中 SSE 流式输出的多种实现方式详解 OpenSandbox 实战指南:为 AI Agent 构建安全的代码执行沙箱 在本机启动 LangGraph 开发服务器:完整指南 DeepAgents中Backend的奥秘:让AI Agent拥有文件操作能力 为什么选择 Go 开发 Web 接口?从入门到实践 智能搜索DeepAgent笔记 RAG学习笔记2--系统查询流程 RAG学习笔记1--系统文件导入流程 百炼 WebSearch 快速入门指南 Python 连接 MongoDB 完整指南:从连接配置到增删改查实战 一行命令搞定 MongoDB 开发环境:Docker Compose 部署 + 可视化管理 使用 Attu 可视化管理 Milvus 向量数据库 在 Windows Docker 中快速安装 Milvus 2.5.6 minio使用 Spark 集群搭建 hadoop集群安装 Spring AI Alibaba 入门实战 Windows 安装 OpenClaw 实战指南 MyBatis 核心流程和原理 Idea中安装Claude code插件 Spark 编程 使用Matplotlib 绘制直方图 Flink安装部署 Flume安装 查找导致cpu过高的代码方法 JVisualVM监控远程Java进程 jmap jacoco多模块生成java单元测试报告实践 arthas 使用demo LockSupport Exchanger CyclicBarrier CountDownLatch 手把手教你用python开始第一个机器学习项目
Spring AI 对话短期记忆实战:让大模型拥有"记忆力"
work hard work smart · 2026-05-31 · via 博客园 - work hard work smart

摘要:多轮对话是大模型应用的核心需求。本文讲解 Spring AI 如何通过 ChatMemory 和 Advisor 机制实现对话短期记忆,并支持 JDBC 持久化。内容包含:短期记忆概念、Advisor 原理、内存/JDBC 两种实现、生产环境配置。

image

一、什么是短期记忆?

短期记忆(Short-term Memory) 是指 AI 在单次对话会话中记住上下文的能力。

为什么需要短期记忆?

大模型本身是无状态的,每次请求都是独立的。如果没有短期记忆:

用户:我想去北京玩
AI:北京有很多景点,推荐故宫、长城...

用户:预算3000够吗?
AI:❓ 什么预算?(不记得前面说的是北京旅游)

有了短期记忆:

用户:我想去北京玩
AI:北京有很多景点,推荐故宫、长城...

用户:预算3000够吗?
AI:✅ 3000元预算去北京玩3-4天是够的...(记得是北京旅游)

短期记忆 vs 长期记忆

类型 作用域 存储内容 生命周期
短期记忆 单次会话 对话历史 会话结束或清空
长期记忆 跨会话 用户偏好、关键信息 永久保存

本文聚焦短期记忆的实现。


二、什么是 Advisor?

Advisor(顾问/拦截器) 是 Spring AI ChatClient 的核心扩展机制,类似于 Spring 的拦截器或 AOP。

Advisor 的作用

Advisor 可以在请求发送前后进行拦截,实现各种增强功能:

graph LR A[用户请求] --> B[Advisor 1] B --> C[Advisor 2] C --> D[ChatModel] D --> E[Advisor 2] E --> F[Advisor 1] F --> G[返回响应]

常用 Advisor

Advisor 功能 场景
MessageChatMemoryAdvisor 自动管理对话历史 多轮对话
SimpleLoggerAdvisor 打印请求/响应日志 开发调试
VectorStoreAdvisor RAG 向量检索 知识库问答

MessageChatMemoryAdvisor 工作原理

graph TD A[用户消息] --> B[MessageChatMemoryAdvisor] B --> C[从 ChatMemory 获取历史] C --> D[合并历史 + 当前消息] D --> E[发送给 ChatModel] E --> F[AI 响应] F --> G[保存到 ChatMemory] G --> H[返回给用户]

关键点

  1. 请求前:自动加载历史对话
  2. 请求后:自动保存新对话
  3. 开发者:无需手动管理消息列表

三、问题场景:无记忆的对话

假设你想让 AI 帮你策划产品发布会:

你:我们下个月要发布一款智能手表
AI:好的,智能手表发布会需要考虑场地、流程、媒体邀请...

你:预算大概50万,场地选在哪里合适?
AI:???(什么预算?)

问题:每次对话 AI 都像失忆了一样!

原因:直接使用 ChatModel 需要手动维护消息列表:

@GetMapping("/planProductLaunch")
public String planProductLaunch(String message) {
    List<Message> messages = new ArrayList<>();
    messages.add(new SystemMessage("你是一个资深产品策划专家"));
    messages.add(new UserMessage("我们下个月要发布一款智能手表"));
    ChatResponse response = dashScopeChatModel.call(new Prompt(messages));
    
    // 第二轮需要手动添加历史
    messages.add(new AssistantMessage(response.getResult().getOutput().getText()));
    messages.add(new UserMessage("预算大概50万,场地选在哪里合适?"));
    // ... 代码冗长,无法跨请求保持上下文
    return dashScopeChatModel.call(new Prompt(messages)).getResult().getOutput().getText();
}

缺点

  • ❌ 需要手动维护 List<Message>
  • ❌ 无法跨请求保持上下文
  • ❌ 代码冗长,容易出错

四、解决方案:使用 ChatMemory + Advisor

4.1 需要的依赖包

<!-- Spring AI 核心 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-spring-boot-starter</artifactId>
</dependency>

<!-- 通义千问模型 -->
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter</artifactId>
</dependency>

<!-- JDBC 持久化(生产环境) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

核心类说明

  • org.springframework.ai.chat.client.ChatClient:对话客户端
  • org.springframework.ai.chat.memory.ChatMemory:记忆接口
  • org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor:记忆拦截器
  • org.springframework.ai.chat.model.ChatModel:模型接口

4.2 实现代码

@RestController
@RequestMapping("/memory")
public class ChatMemoryController implements InitializingBean {

    @Autowired
    private ChatModel dashScopeChatModel;
    
    @Autowired
    private ChatMemory chatMemory;
    
    private ChatClient chatClient;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        this.chatClient = ChatClient.builder(dashScopeChatModel)
                .defaultAdvisors(
                    MessageChatMemoryAdvisor.builder(chatMemory).build(),
                    new SimpleLoggerAdvisor()
                )
                .defaultOptions(
                    DashScopeChatOptions.builder()
                        .withTopP(0.7)
                        .build()
                )
                .build();
    }
    
    @GetMapping("/continuousChat")
    public Flux<String> continuousChat(String message, String chatId, HttpServletResponse httpServletResponse) {
        httpServletResponse.setCharacterEncoding("UTF-8");
        return chatClient
                .prompt()
                .user(message)
                .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId))
                .stream()
                .content();
    }
}

关键点

  • MessageChatMemoryAdvisor:自动处理历史消息的加载和保存
  • chatId:会话唯一标识,同一个 chatId 共享历史
  • InitializingBean:确保 ChatClient 在应用启动时创建,全局复用

4.3 测试效果

# 第一轮对话
curl "http://localhost:8080/memory/continuousChat?message=我们下个月要发布一款智能手表&chatId=launch-001"

# 第二轮对话 - AI 记得前面说过发布会
curl "http://localhost:8080/memory/continuousChat?message=预算大概50万,场地选在哪里合适?&chatId=launch-001"

# 第三轮对话 - AI 依然记得上下文
curl "http://localhost:8080/memory/continuousChat?message=希望能邀请科技博主来做现场体验&chatId=launch-001"

效果:AI 现在能够记住整个对话历史,给出连贯的回复!


五、ChatMemory 的两种实现

5.1 内存存储(开发测试)

@Configuration
public class ChatMemoryConfiguration {

    @Bean
    public ChatMemory chatMemory() {
        return MessageWindowChatMemory.builder()
                .maxMessages(20)
                .build();
    }
}

特点

  • ✅ 配置简单,无需数据库
  • ❌ 应用重启后数据丢失
  • ✅ 适合开发和测试环境

5.2 JDBC 持久化(生产环境)

① 创建数据库表

CREATE TABLE `spring_ai_chat_memory` (
  `conversation_id` varchar(36) NOT NULL,
  `content` text NOT NULL,
  `type` enum('USER','ASSISTANT','SYSTEM','TOOL') NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`,`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

② 配置文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_ai?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  ai:
    chat:
      memory:
        repository:
          jdbc:
            enabled: true
            platform: mysql
            initialize-schema: always

③ 配置类

@Configuration
public class JdbcChatMemoryConfiguration {

    @Bean
    public ChatMemory jdbcChatMemory(JdbcChatMemoryRepository jdbcChatMemoryRepository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(jdbcChatMemoryRepository)
                .maxMessages(20)
                .build();
    }
}

④ 控制器实现

@RestController
@RequestMapping("/jdbc/memory")
public class JdbcChatMemoryController implements InitializingBean {

    @Autowired
    private ChatModel dashScopeChatModel;

    @Autowired
    private ChatMemory jdbcChatMemory;

    private ChatClient chatClient;

    @GetMapping("/persistentChat")
    public Flux<String> persistentChat(String message, String chatId, HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        return chatClient
                .prompt()
                .user(message)
                .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId))
                .stream()
                .content();
    }

    @Override
    public void afterPropertiesSet() {
        this.chatClient = ChatClient.builder(dashScopeChatModel)
                .defaultAdvisors(
                    MessageChatMemoryAdvisor.builder(jdbcChatMemory).build(), 
                    new SimpleLoggerAdvisor()
                )
                .defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).build())
                .build();
    }
}

关键差异:注入的是 jdbcChatMemory Bean,其他代码与内存版本完全一致!

⑤ 测试持久化效果

# 第一次对话
curl "http://localhost:8000/jdbc/memory/persistentChat?message=我们要发布一款智能手表&chatId=product-001"

# 第二次对话(同一会话)
curl "http://localhost:8000/jdbc/memory/persistentChat?message=预算50万,场地怎么选?&chatId=product-001"

# 重启应用后继续对话
curl "http://localhost:8000/jdbc/memory/persistentChat?message=希望能邀请科技博主&chatId=product-001"

效果:AI 依然记得之前的对话,因为数据已持久化到数据库!

5.3 两种实现对比

特性 内存存储 JDBC 存储
持久化 ❌ 重启丢失 ✅ 永久保存
性能 ⚡ 极快 ⚡ 快(有索引)
配置 简单 需要数据库
适用场景 开发测试 生产环境

六、最佳实践

6.1 合理设置消息窗口大小

MessageWindowChatMemory.builder()
    .maxMessages(20)
    .build();

建议:一般场景 10-20 条,长对话 30-50 条,注意模型的 token 限制。

6.2 数据库优化(JDBC)

连接池配置

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000

定期清理过期数据

DELETE FROM spring_ai_chat_memory 
WHERE timestamp < DATE_SUB(NOW(), INTERVAL 30 DAY);

6.3 会话 ID 管理

@GetMapping("/initiateSession")
public String initiateSession() {
    return UUID.randomUUID().toString();
}

七、常见问题

Q1:ChatMemory 会无限增长吗?

不会。使用 MessageWindowChatMemory 可以限制消息数量,超过限制后最早的消息会被自动删除。

Q2:如何区分不同用户的对话?

通过不同的 chatId

chatClient.prompt()
    .user(message)
    .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, "user-A"))
    .stream();

Q3:应用重启后记忆会丢失吗?

  • 内存存储:❌ 会丢失
  • JDBC 存储:✅ 不会丢失(持久化到数据库)

Q4:如何清除对话记忆?

chatMemory.clear(chatId);

八、总结

Spring AI 的 ChatMemory + Advisor 机制为多轮对话提供了优雅的解决方案:

核心价值

  1. 自动化:无需手动管理消息列表
  2. 隔离性:不同会话通过 chatId 隔离
  3. 可控性:支持消息数量限制、持久化存储
  4. 透明性:切换存储实现时,业务代码无需修改

快速上手

// 1. 注入 ChatMemory
@Autowired
private ChatMemory chatMemory;

// 2. 配置 ChatClient
chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
    .build();

// 3. 调用时传入 chatId
chatClient.prompt()
    .user(message)
    .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId))
    .stream()
    .content();

通过 ChatMemory,我们可以轻松实现有"记忆力"的 AI 对话,让交互更加自然流畅!


参考资源