摘要:本文第一部分深入解析了 Spring AI 中的 ChatMemory 接口,这是实现对话机器人会话记忆功能的核心契约。通过分析源码,详细介绍了接口的常量定义、四个核心方法(单消息添加、批量添加、获取历史、清空记忆)及其设计特点,为理解 Spring AI 的对话记忆机制奠定基础。
摘要:本文第二部分通过完整的代码示例,演示了如何在 Spring AI 中实际配置和使用 ChatMemory。从 ChatMemoryRepository 和 MessageWindowChatMemory 的 Bean 配置,到支持记忆的 ChatClient 构建,再到 REST 控制器的实现,最后通过测试流程验证多轮对话的记忆效果,并总结了会话隔离、窗口管理和持久化选择等关键实践要点。
一,ChatMemory入门介绍
结合之前我们讨论的Spring AI大模型集成、低延迟推理网关的技术背景,对话机器人的会话记忆功能核心是持久化多轮交互上下文,避免大模型每次请求丢失历史对话信息,实现连贯的对话体验。


SpringAI 会话记忆的接口ChatMemory 源码:
java
/*
* Copyright 2023-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.chat.memory;
import java.util.List;
import org.springframework.ai.chat.messages.Message;
import org.springframework.util.Assert;
/**
* The contract for storing and managing the memory of chat conversations.
*
* @author Christian Tzolov
* @author Thomas Vitale
* @since 1.0.0
*/
public interface ChatMemory {
/**
* The key to retrieve the chat memory conversation id from the context.
*/
String CONVERSATION_ID = "chat_memory_conversation_id";
/**
* Save the specified message in the chat memory for the specified conversation.
*/
default void add(String conversationId, Message message) {
Assert.hasText(conversationId, "conversationId cannot be null or empty");
Assert.notNull(message, "message cannot be null");
this.add(conversationId, List.of(message));
}
/**
* Save the specified messages in the chat memory for the specified conversation.
*/
void add(String conversationId, List<Message> messages);
/**
* Get the messages in the chat memory for the specified conversation.
*/
List<Message> get(String conversationId);
/**
* Clear the chat memory for the specified conversation.
*/
void clear(String conversationId);
}
1、整体说明
这是Spring AI 1.0.0及以上版本提供的对话记忆存储契约接口,定义了所有聊天记忆实现类都必须遵循的统一标准,用于存储和管理大模型对话的历史上下文,支撑多轮对话的上下文延续能力。该接口由Christian Tzolov和Thomas Vitale两位核心开发者共同设计维护。
2、核心常量定义
接口中预先定义了全局常量:
java
String CONVERSATION_ID = "chat_memory_conversation_id";
这个常量是上下文里用来标识对话会话ID的统一key,所有ChatMemory实现类都通过这个key从会话上下文中获取当前操作的唯一对话编号。
3、四个核心抽象方法说明
单消息添加方法(默认实现)
java
default void add(String conversationId, Message message)
提供了默认实现,校验对话ID非空、消息对象不为空后,自动将单条消息封装成列表调用重载的批量添加方法,简化单消息插入的调用逻辑。
批量添加消息方法(抽象方法)
java
void add(String conversationId, List<Message> messages)
所有实现类必须自行实现该方法,用于将多条对话消息批量写入指定会话ID对应的记忆存储空间,是聊天记忆持久化的核心能力。
获取对话历史方法
java
List<Message> get(String conversationId)
从存储空间中读取指定会话ID下所有已保存的完整对话消息列表,供大模型推理时加载历史上下文,实现多轮对话的语义连贯。
清空对话记忆方法
java
void clear(String conversationId)
彻底删除指定会话ID对应的所有聊天历史记录,用于会话重置、资源释放等场景。
4、设计特点
所有方法都通过Spring的Assert校验参数合法性,避免非法输入破坏存储逻辑
默认方法和抽象方法结合的设计,兼顾了代码复用和实现灵活性,官方提供的内置实现包括InMemoryChatMemory、RedisChatMemory等,用户也可以基于该接口自定义对接数据库等存储介质的聊天记忆实现。
二,ChatMemory实现示例代码编写
在 Spring AI 中,ChatMemory 并非直接由开发者实现一个接口来存储数据,而是通过配置 ChatMemoryRepository(负责持久化)和 MessageWindowChatMemory 或 TokenWindowChatMemory(负责管理窗口逻辑)来协同工作。
以下是一个基于 Spring AI 实现带记忆对话的完整示例。该示例展示了如何配置内存型聊天记忆,并在 REST 控制器中使用它来保持多轮对话的上下文。
1. 核心概念说明
ChatMemoryRepository:负责实际存储和检索消息记录(如内存、Redis、JDBC等)。
ChatMemory:通常指 MessageWindowChatMemory,它定义了记忆的规则(如保留最近 N 条消息或 N 个 Token)。
Advisor:在 Spring AI 中,通过 MessageChatMemoryAdvisor 将记忆功能注入到 ChatClient 的执行链路中。
2. 代码实现
A. 配置 ChatMemory Bean
首先,我们需要配置一个基于内存的 ChatMemoryRepository 和一个 ChatMemory 实例。
java
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatMemoryConfig {
/**
* 配置基于内存的消息仓库
* 生产环境建议替换为 RedisChatMemoryRepository 或 JdbcChatMemoryRepository
*/
@Bean
public InMemoryChatMemoryRepository chatMemoryRepository() {
return new InMemoryChatMemoryRepository();
}
/**
* 配置聊天记忆实例
* maxMessages: 设置保留最近的消息数量,避免上下文过长导致费用增加或超出模型限制
*/
@Bean
public ChatMemory chatMemory(InMemoryChatMemoryRepository repository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.maxMessages(10) // 保留最近10条消息
.build();
}
}
B. 创建支持记忆的 ChatClient
在 Service 或 Controller 中,构建 ChatClient 时注入 ChatMemory,并通过 defaultAdvisors 添加 MessageChatMemoryAdvisor。
java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.stereotype.Service;
@Service
public class ChatService {
private final ChatClient chatClient;
// 构造函数注入 ChatMemory 和 ChatModel
public ChatService(ChatMemory chatMemory, OpenAiChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
// 关键步骤:添加记忆顾问,自动处理上下文的读取和保存
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
/**
* 发送消息并获取回复
* @param conversationId 会话ID,用于区分不同用户的记忆
* @param userMessage 用户输入
* @return AI 回复
*/
public String chat(String conversationId, String userMessage) {
return chatClient.prompt()
.user(userMessage)
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, conversationId))// 指定会话ID
.call()
.content();
}
}
C. REST 控制器示例
提供一个简单的接口来测试多轮对话。
java
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ChatService chatService;
public ChatController(ChatService chatService) {
this.chatService = chatService;
}
@PostMapping("/message")
public String sendMessage(@RequestParam String conversationId, @RequestParam String message) {
return chatService.chat(conversationId, message);
}
}
3. 测试流程
第一轮对话:
请求:POST /api/chat/message?conversationId=user1&message=我叫小明,请记住我的名字。
响应:好的,小明,我已经记住了你的名字。
底层行为:MessageChatMemoryAdvisor 会将用户消息和 AI 回复保存到 InMemoryChatMemoryRepository 中,关联 key 为 user1。
第二轮对话:
请求:POST /api/chat/message?conversationId=user1&message=我叫什么名字?
响应:你叫小明。
底层行为:Advisor 在发送请求前,从仓库中读取 user1 的历史消息,将其附加到当前的 Prompt 中发送给大模型,从而使模型能够"回忆"起之前的信息。
4. 关键点总结
会话隔离:务必使用 conversationId(或 chatId)来隔离不同用户或不同会话的记忆,否则所有用户的对话会混在一起。
窗口管理:MessageWindowChatMemory 通过 maxMessages 限制历史消息数量,防止 Token 溢出。如果需要更精细的控制(如基于 Token 数量),可以使用 TokenWindowChatMemory。
持久化选择:InMemoryChatMemoryRepository 仅适用于开发测试或单节点临时场景。生产环境中,应使用 RedisChatMemoryRepository 或 JdbcChatMemoryRepository 以确保数据持久化和分布式共享。