一、ChatMemory 的核心作用与功能解析

Spring AI 中的的ChatMemory(聊天记忆)提供了维护 AI 聊天应用程序的对话上下文和历史的机制。聊天记忆使 AI 应用程序能够:维护对话历史、提供上下文感知的响应、实现不同的记忆策略、管理对话状态。

1. 核心功能:对话上下文管理

  • 历史对话存储:记录用户与AI模型之间的完整交互消息(包括用户提问、模型回应及工具调用记录),确保多轮对话的连贯性。

  • 上下文关联:通过分析历史对话内容,帮助模型理解当前问题的背景和意图(例如:「刚才提到的合同条款是指哪一条?」)[[历史对话]]。

2. 多轮对话支持

  • 会话ID绑定:通过 chatId (如用户ID或UUID)区分不同对话会话,实现对话数据的隔离与管理[[历史对话]]。

  • 动态上下文加载:每次请求自动附加相关历史消息到模型输入中,无需手动拼接上下文。

3. 性能与资源优化

  • 滑动窗口机制:通过 MessageWindowChatMemory 等策略限制存储的消息数量(如仅保留最近5条),避免无限增长导致内存压力。

  • Token控制:减少冗余历史信息的传输,降低模型处理的Token消耗及API成本。

4. 数据隔离与安全

  • 多用户支持:在分布式系统中,结合 chatMemoryProvider 为每个用户创建独立的对话存储空间,防止数据混淆。

  • 敏感信息过滤:可扩展实现消息内容的脱敏处理(如自动屏蔽电话号码或隐私关键词)。

5. 持久化与扩展

  • 存储适配:支持内存、Redis、关系型数据库等多种存储方式,满足不同场景的持久化需求。

  • 长期记忆增强:结合向量数据库实现语义检索,支持长期对话记忆(例如数月前的历史咨询)

1.1、典型应用场景

场景作用
智能客服保持用户咨询的连续性,自动关联历史工单或解决方案
教育助手跟踪学习进度,基于过往提问推荐相关知识模块
医疗咨询记录患者病史,确保诊断建议的连贯

1.2、配置建议

# Spring AI 示例配置 
spring:
  ai:
    chat:
      memory:
        type: redis  # 可选内存/redis/jdbc
        window-size: 5  # 滑动窗口保留消息数
        retrieval-size: 3  # 每次检索的历史消息条数

该组件已在多个实际场景中验证,例如某电商客服系统通过集成 ChatMemory + Redis 实现了日均百万级对话的上下文管理]。开发者可根据需求选择适配存储方案,并通过扩展接口实现自定义逻辑(如消息加密或归档)。


二、ChatMemory 存储方式分类

2.1 内存存储(In-Memory)

  • 实现方式
    使用 Java 堆内存存储对话记录,会话数据保存在 ConcurrentHashMap 等结构中,以 chatId 作为键。

  • 特点

    • 读写速度极快(微秒级响应)

    • 服务器重启后数据丢失,仅适用于开发和临时会话场景

  • 配置示例
    spring.ai.chat.memory.type=memory

2. 2 文件系统存储(File-Based)

  • 实现方式
    将会话历史序列化为 JSON 或 XML 文件,存储到本地磁盘14。

  • 特点

    • 数据持久化,适合离线场景1

    • 读写性能受磁盘 I/O 限制(毫秒级响应)4

    • 需注意文件锁机制避免并发冲突6

2.3 关系型数据库(RDBMS)

  • 典型方案
    MySQL、PostgreSQL 等,通过 JdbcTemplate 或 ORM 框架存储。

  • 表结构示例

    CREATE TABLE chat_memory ( session_id VARCHAR(64) PRIMARY KEY, messages JSON, last_accessed TIMESTAMP )
  • 特点

    • 支持事务和复杂查询

    • 数据安全性高,但延迟较高(10-100ms)

2.4  分布式存储(Redis/MongoDB)

  • Redis 实现
    使用 RedisTemplate 存储序列化会话数据,支持过期时间管理。

    redisTemplate.opsForValue().set(chatId, messages, 24, TimeUnit.HOURS);
  • MongoDB 实现
    利用文档数据库特性存储多轮对话的非结构化数据。

  • 特点

    • 高并发支持(10万+ QPS)

    • 天然支持分布式会话同步


2.5  向量数据库增强存储

  • 技术方案
    结合 Qdrant/Pinecone 等向量数据库,将对话内容编码为向量存储。

  • 优势

    • 支持语义检索历史对话

    • 可实现长期记忆和上下文关联分析

选型建议

存储方式适用场景性能指标数据持久性
内存存储开发测试/短时交互≤1ms
Redis高并发生产环境1-5ms可选
关系型数据库审计/复杂分析需求10-100ms
向量数据库智能客服/上下文关联场景5-20ms

扩展优化

  • 混合存储:热数据存 Redis,冷数据存 MySQL

  • 压缩策略:对历史消息使用 GZIP 压缩减少存储开销

  • TTL 机制:通过 @Scheduled 任务自动清理过期会话

以上方案已在多个实际项目中验证,例如某金融客服系统通过「Redis + 向量数据库」架构实现了 10万级并发对话管理。开发者可根据场景需求选择或组合存储方式。


三、ChatMemory 实现

3.1 基本配置

@Configuration
public class ChatMemoryConfig {
    @Bean
    public ChatMemory chatMemory() {
        return new InMemoryChatMemory();
    }
}

3.2 使用聊天记忆

@Service
public class ChatService {
    private final ChatClient chatClient;
    private final ChatMemory chatMemory;

    public ChatService(ChatClient chatClient, ChatMemory chatMemory) {
        this.chatClient = chatClient;
        this.chatMemory = chatMemory;
    }

    public String chat(String message, String sessionId) {
        // 将消息添加到记忆中
        chatMemory.addMessage(sessionId, message);
        
        // 获取对话历史
        List<Message> history = chatMemory.getMessages(sessionId);
        
        // 使用上下文生成响应
        String response = chatClient.generate(history);
        
        // 将响应存储在记忆中
        chatMemory.addMessage(sessionId, response);
        
        return response;
    }
}

3.3 记忆策略

(1) 固定窗口记忆

@Bean
public ChatMemory fixedWindowMemory() {
    return new FixedWindowChatMemory(10); // 保留最后10条消息
}

(2)基于令牌的记忆

@Bean
public ChatMemory tokenBasedMemory() {
    return new TokenBasedChatMemory(1000); // 在令牌限制内保留消息
}

(3)摘要记忆

@Bean
public ChatMemory summaryMemory() {
    return new SummaryChatMemory(summarizer);
}

3.4 配置属性

spring:
  ai:
    chat:
      memory:
        max-messages: 100
        max-tokens: 1000
        ttl: 3600
        type: in-memory

3.5 最佳实践

在实现聊天记忆时,请考虑以下最佳实践:

  • 记忆大小:根据您的用例选择适当的记忆大小

  • 持久化:在生产环境中使用持久化存储

  • 清理:为旧对话实现适当的清理机制

  • 安全性:确保适当的数据保护和隐私措施

  • 可扩展性:考虑为高规模应用程序使用分布式记忆

3.6 高级功能

自定义记忆实现
@Component
public class CustomChatMemory implements ChatMemory {
    @Override
    public void addMessage(String sessionId, String message) {
        // 自定义实现
    }

    @Override
    public List<Message> getMessages(String sessionId) {
        // 自定义实现
        return messages;
    }
}
记忆监控

监控记忆使用情况和性能:

management.endpoints.web.exposure.include=chat-memory
management.endpoint.chat-memory.enabled=true

3.7 故障排除

常见问题和解决方案:

  1. 内存泄漏

    1. 实现适当的清理

    2. 设置适当的 TTL

    3. 监控内存使用情况

  2. 性能问题

    1. 使用适当的记忆类型

    2. 实现缓存

    3. 优化存储

  3. 可扩展性

    1. 使用分布式记忆

    2. 实现适当的分区

    • 考虑缓存策略


四、关于代码段 advisors(a-> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)) 的代码作用解析

    /**
     * 会话记忆 已通过
     * @param message
     * @param chatId
     * @return
     *
     * 此次新增chatId字段,目的是为一次会话添加唯一标识,基于该标识存储会话记忆,目前使用的内存记忆功能,可以尝试使用向量数据库,永久有效。
     *
     * 问题1:共12个苹果,分给12个人,怎么分
     * 问题2:分给3个人呢
     */
    @PostMapping(value = "/memoryChat",produces = "text/html;charset=utf-8")
    public Flux<String> memoryChat(@RequestParam("message") String message, String chatId) {

        return chatClient.prompt()
                .user(message)
                .advisors(a-> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
                .stream()
                .content();

    }

这是一个典型的 Spring AI 框架中 Advisor 参数绑定代码,主要用于管理多轮对话的上下文记忆。

其核心功能如下:

1. 参数定义

  • CHAT_MEMORY_CONVERSATION_ID_KEY
    预定义常量,标识对话的唯一会话ID键(如 spring.ai.chat.memory.conversation-id )

  • chatId
    动态生成的会话唯一标识符(通常为UUID或用户ID),用于追踪特定对话的完整上下文

2. 功能实现

  • 对话内存绑定
    将 chatId 与会话管理器(如 ChatMemory)关联,确保每次请求能正确检索历史对话记录

  • 数据隔离
    通过唯一ID隔离不同用户的对话数据,避免内存混用问题

3. 运行机制

// 伪代码流程 
1. 用户发起新对话 → 生成 chatId (UUID/用户ID)
2. 调用 advisors() 配置链 → 绑定 chatId 到当前请求上下文 
3. Advisor 链处理请求时 → 通过 chatId 加载历史消息 
4. 模型响应后 → 根据 chatId 保存最新对话记录到内存/数据库 

关联技术组件

组件作用
ChatMemory存储对话历史记录的接口,支持内存/Redis等存储方式
VectorStoreChatMemoryAdvisor将对话记录存储到向量数据库的增强组件,依赖 chatId 实现长期记忆
MessageChatMemoryAdvisor基础上下文管理组件,自动附加历史消息到模型请求中

典型应用场景

  1. 多轮对话服务
    用户连续提问时,通过 chatId 保持上下文连贯性

  2. 用户个性化服务
    结合用户ID作为 chatId,实现基于用户画像的定制应答

  3. 分布式会话
    将会话ID与Redis等分布式存储结合,支持集群环境下的状态同步


配置建议

# 需配合的配置项示例(application.yaml )
spring:
  ai:
    chat:
      memory:
        type: redis # 可选内存/数据库等 
        retrieval-size: 5 # 历史消息检索条数

该模式已在客服系统、智能助手等场景广泛应用2,需注意及时清理过期 chatId 防止内存泄漏


五、Spring AI实现ChatMemory接口,实现MySQL持久化

Spring AI官方提供的InMemoryChatMemory是存储在内存中的,电脑关机后和AI的聊天记录就无了,将聊天记录存储在MySQL数据库中就能持久保存聊天记录了。

在使用InMemoryChatMemory基础上改造成将聊天记录存储到MySQL可参照下面代码:

5.1、数据库表的设计

CREATE TABLE chat_messages (

    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID,自增',
    
    conversation_id VARCHAR(255) NOT NULL COMMENT '会话ID,用于区分不同的会话',
    
    message_type ENUM('USER', 'ASSISTANT') NOT NULL COMMENT '消息类型:USER表示用户,ASSISTANT表示AI助手',
    
    content TEXT NOT NULL COMMENT '消息内容',
    
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '消息创建时间',
    
    KEY idx_conversation_id (conversation_id),

    KEY idx_conversation_time (conversation_id, created_at)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='聊天消息记录表';

5.2 创建数据表实体类

import lombok.Data;
import java.time.LocalDateTime;
 
@Data
public class ChatMessageEntity {
    private Long id;
    private String conversationId;
    private MessageType messageType;
    private String content;
    private LocalDateTime createdAt;
 
    public enum MessageType {
        USER, ASSISTANT
    }
}

5.3 创建Mapper接口

import com.zry.ai.entity.ChatMemoryEntity.ChatMessageEntity;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface MessageMapper {
    @Insert({
        "<script>",
        "INSERT INTO chat_messages (conversation_id, message_type, content, created_at)",
        "VALUES ",
        "<foreach collection='messages' item='msg' separator=','>",
        "(#{msg.conversationId}, #{msg.messageType}, #{msg.content}, #{msg.createdAt})",
        "</foreach>",
        "</script>"
    })
    void insertMessages(@Param("messages") List<ChatMessageEntity> messages);
 
    @Select("SELECT * FROM chat_messages " +
            "WHERE conversation_id = #{conversationId} " +
            "ORDER BY created_at DESC " +
            "LIMIT #{lastN}")
    List<ChatMessageEntity> findLastNMessages(@Param("conversationId") String conversationId, 
                                            @Param("lastN") int lastN);
 
    @Delete("DELETE FROM chat_messages WHERE conversation_id = #{conversationId}")
    void deleteByConversationId(String conversationId);
}

5.4 实现SpringAI的ChatMemery接口

ChatMemery接口:


实现ChatMemery接口:

 
import com.zry.ai.entity.ChatMemoryEntity.ChatMessageEntity;
import com.zry.ai.mapper.MessageMapper;
import lombok.RequiredArgsConstructor;
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.stereotype.Component;
 
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
 
@Component
@RequiredArgsConstructor
public class MysqlChatMemory implements ChatMemory {

    private final MessageMapper messageMapper;

    @Override
    public void add(String conversationId, List<Message> messages) {
        List<ChatMessageEntity> entities = messages.stream()
            .map(msg -> {
                ChatMessageEntity entity = new ChatMessageEntity();
                entity.setConversationId(conversationId);
                entity.setContent(msg.getText());
                
                if (msg instanceof UserMessage) {
                    entity.setMessageType(ChatMessageEntity.MessageType.USER);
                } else if (msg instanceof AssistantMessage) {
                    entity.setMessageType(ChatMessageEntity.MessageType.ASSISTANT);
                }
                
                entity.setCreatedAt(LocalDateTime.now());
                return entity;
            })
            .collect(Collectors.toList());
        
        messageMapper.insertMessages(entities);
    }
 
    @Override
    public List<Message> get(String conversationId, int lastN) {
        List<ChatMessageEntity> entities = messageMapper.findLastNMessages(conversationId, lastN);
        Collections.reverse(entities);
        
        return entities.stream()
            .map(entity -> {
                switch (entity.getMessageType()) {
                    case USER:
                        return new UserMessage(entity.getContent());
                    case ASSISTANT:
                        return new AssistantMessage(entity.getContent());
                    default:
                        throw new IllegalArgumentException("未知的消息类型");
                }
            })
            .collect(Collectors.toList());
    }
 
    @Override
    public void clear(String conversationId) {
        messageMapper.deleteByConversationId(conversationId);
    }
}

5.5 在配置ChatClient时将MysqlChatMemery加入环绕增强

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatConfig {

    @Bean
    public ChatMemory chatMemory(MessageMapper messageMapper) {
        return new MysqlChatMemory(messageMapper);
    }

    @Bean
    public ChatClient chatClient(OllamaChatModel model, ChatMemory chatMemory) {
        return ChatClient.builder(model)
                .defaultSystem(SystemConstants.CHAT_AI_SYSTEM_PROMPT)
                .defaultAdvisors(
                        new SimpleLoggerAdvisor(),
                        new MessageChatMemoryAdvisor(chatMemory)
                )
                .build();
    }
}

参考链接:

Spring AI实现ChatMemory接口,实现MySQL持久化_springai inmemorychatmemory-CSDN博客