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

推荐订阅源

P
Proofpoint News Feed
WordPress大学
WordPress大学
Help Net Security
Help Net Security
Jina AI
Jina AI
Security Latest
Security Latest
Y
Y Combinator Blog
Project Zero
Project Zero
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
Know Your Adversary
Know Your Adversary
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
NISL@THU
NISL@THU
Cisco Talos Blog
Cisco Talos Blog
博客园 - 司徒正美
MyScale Blog
MyScale Blog
Cyberwarzone
Cyberwarzone
D
Docker
T
The Blog of Author Tim Ferriss
G
Google Developers Blog
C
CERT Recently Published Vulnerability Notes
B
Blog
L
LangChain Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
SecWiki News
SecWiki News
The Hacker News
The Hacker News
C
Check Point Blog
L
Lohrmann on Cybersecurity
V2EX - 技术
V2EX - 技术
S
Securelist
T
Threat Research - Cisco Blogs
Stack Overflow Blog
Stack Overflow Blog
TaoSecurity Blog
TaoSecurity Blog
云风的 BLOG
云风的 BLOG
Latest news
Latest news
人人都是产品经理
人人都是产品经理
L
LINUX DO - 最新话题
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The Register - Security
The Register - Security
Webroot Blog
Webroot Blog
Simon Willison's Weblog
Simon Willison's Weblog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Microsoft Security Blog
Microsoft Security Blog
AWS News Blog
AWS News Blog
C
Cybersecurity and Infrastructure Security Agency CISA
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
小众软件
小众软件
T
Tailwind CSS Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
宝玉的分享
宝玉的分享
O
OpenAI News

博客园 - work hard work smart

使用 LangChain + Hugging Face 构建文本向量化服务 SQLAlchemy 使用详解 Python 中使用 Elasticsearch 的完整指南 Qdrant 向量数据库使用指南 OpenEvals 快速入门:LLM 评估指南 DeepEval 快速入门:LLM 应用评估指南 LangSmith 批量评估完全指南 Qwen-Agent 入门指南:快速构建智能体应用 LangSmith 集成实战:从追踪到评估的完整指南 初识 go-zero:一款让你写后端更规范、更高效的 Go 微服务框架 RAG 中为什么需要 Rerank,以及如何使用 Rerank LangChain4j RAG 核心组件与组合方式 如何使用 Elasticsearch 进行全文检索和向量检索 MinerU Docker 部署指南 5 分钟上手:为 Cline 配置一个免费的 MCP 天气服务 Neo4j 图数据库安装与 Spring Boot 集成实战指南 LangFuse 实战指南:用 @observe 三行代码给 LLM 应用加上全链路追踪 Function Call 深度解析:让大模型从"嘴炮"到"实干"的技术革命 Spring AI 提示词模板实战:告别硬编码,实现提示词工程化管理 LangChain4j 实战指南:用 Java 轻松构建 AI 应用 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 对话,让交互更加自然流畅!


参考资源