SpringAI2.0 对话记忆管理:ChatMemory、Advisor 链与长期记忆架构

SpringAI2.0 对话记忆管理:ChatMemory、Advisor 链与长期记忆架构

前言:多轮对话的核心挑战

在构建 AI 应用时,实现自然的对话体验至关重要。用户期望 AI 能够记住之前的对话上下文,理解上下文,而不是每次对话都从零开始。Spring AI 2.0 提供了一套完整的对话记忆管理机制,让开发者能够轻松实现多轮对话和长期记忆。

作为架构师,我见过很多 AI 应用因为缺乏有效的记忆管理而导致用户体验差。Spring AI 2.0 通过 ChatMemory、Advisor 链等机制,让记忆管理变得简单而强大。

一、MessageChatMemoryAdvisor 与 VectorStoreChatMemoryAdvisor 差异对比

1.1 两种记忆类型

Spring AI 2.0 提供两种主要的 ChatMemory 实现:

  • MessageChatMemoryAdvisor:基于内存或 Redis 的简单记忆存储
  • VectorStoreChatMemoryAdvisor:基于向量存储的语义记忆检索

1.2 MessageChatMemoryAdvisor

java 复制代码
@Service
public class SimpleMemoryService {
    
    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    
    // 使用 MessageChatMemoryAdvisor
    public String chat(String conversationId, String message) {
        return chatClient.prompt()
            .user(message)
            .advisors(
                new MessageChatMemoryAdvisor(chatMemory),
                new PromptChatMemoryAdvisor(chatMemory)
            )
            .advisors(a -> a
                .param(CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)
                .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
            )
            .call()
            .content();
    }
}

1.3 VectorStoreChatMemoryAdvisor

java 复制代码
@Service
public class SemanticMemoryService {
    
    private final ChatClient chatClient;
    private final VectorStore memoryStore;
    
    // 使用 VectorStoreChatMemoryAdvisor
    public String chatWithSemanticMemory(
        String conversationId,
        String message
    ) {
        return chatClient.prompt()
            .user(message)
            .advisors(new VectorStoreChatMemoryAdvisor(
                memoryStore,
                SearchRequest.builder()
                    .withTopK(10)
                    .withSimilarityThreshold(0.7)
                    .build()
            ))
            .advisors(a -> a
                .param(CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)
            )
            .call()
            .content();
    }
}

1.4 对比表格

特性 MessageChatMemoryAdvisor VectorStoreChatMemoryAdvisor
存储方式 线性存储 向量检索
检索策略 最近 N 条 语义相似度
内存占用 较大 较小
适用场景 短期对话 长期记忆
性能 中等

二、记忆存储后端选型

2.1 RedisChatMemory

java 复制代码
@Configuration
public class RedisChatMemoryConfig {
    
    @Bean
    public ChatMemory redisChatMemory(
        RedisConnectionFactory connectionFactory
    ) {
        RedisChatMemoryConfig config = RedisChatMemoryConfig.builder()
            .withConversationKeyPrefix("chat:")
            .build();
        
        return new RedisChatMemory(connectionFactory, config);
    }
}

2.2 Neo4jChatMemory

java 复制代码
@Configuration
public class Neo4jChatMemoryConfig {
    
    @Bean
    public ChatMemory neo4jChatMemory(Driver neo4jDriver) {
        Neo4jChatMemoryConfig config = Neo4jChatMemoryConfig.builder()
            .withMemoryNodeLabel("Memory")
            .build();
        
        return new Neo4jChatMemory(neo4jDriver, config);
    }
}

2.3 JDBC 持久化

java 复制代码
@Configuration
public class JdbcChatMemoryConfig {
    
    @Bean
    public ChatMemory jdbcChatMemory(JdbcTemplate jdbcTemplate) {
        return new JdbcChatMemory(jdbcTemplate);
    }
}

三、上下文修剪与记忆压缩

3.1 上下文修剪

java 复制代码
@Service
public class PruningMemoryService {
    
    private final ChatMemory chatMemory;
    private final int maxMessages = 50;
    
    public void addMessage(
        String conversationId,
        Message message
    ) {
        List<Message> messages = chatMemory.get(
            conversationId, 
            maxMessages + 10
        );
        
        if (messages.size() >= maxMessages) {
            // 保留最近的消息
            List<Message> recent = messages.subList(
                messages.size() - maxMessages,
                messages.size()
            );
            
            // 更新记忆
            chatMemory.clear(conversationId);
            chatMemory.add(conversationId, recent);
        }
        
        chatMemory.add(conversationId, List.of(message));
    }
}

3.2 记忆压缩

java 复制代码
@Service
public class CompressedMemoryService {
    
    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    
    // 定期压缩记忆
    @Scheduled(cron = "0 0 */4 * * ?")  // 每 4 小时
    public void compressMemories() {
        List<String> conversationIds = getAllConversationIds();
        
        for (String conversationId : conversationIds) {
            try {
                compressConversation(conversationId);
            } catch (Exception e) {
                log.error("压缩记忆失败:{}", conversationId, e);
            }
        }
    }
    
    private void compressConversation(String conversationId) {
        List<Message> messages = chatMemory.get(conversationId, null);
        
        if (messages == null || messages.size() < 20) {
            return;  // 消息太少,不需要压缩
        }
        
        // 提取关键信息
        String summary = chatClient.prompt()
            .user("""
                请总结以下对话的要点,保持简洁:
                
                %s
                """.formatted(formatMessages(messages)))
            .call()
            .content();
        
        // 创建压缩消息
        Message summaryMessage = new SystemMessage(
            "之前的对话摘要:" + summary
        );
        
        // 清空旧记忆,只保留摘要和最近的消息
        chatMemory.clear(conversationId);
        chatMemory.add(conversationId, List.of(summaryMessage));
        
        List<Message> recent = messages.subList(
            messages.size() - 10,
            messages.size()
        );
        chatMemory.add(conversationId, recent);
    }
    
    private String formatMessages(List<Message> messages) {
        return messages.stream()
            .map(msg -> "[%s] %s".formatted(
                msg.getMessageType(),
                msg.getContent()
            ))
            .collect(Collectors.joining("\n"));
    }
}

四、Advisor 执行顺序控制

4.1 执行顺序

复制代码
用户请求
    ↓
[Advisor 1] 提示词增强
    ↓
[Advisor 2] 记忆检索(历史)
    ↓
[Advisor 3] RAG 检索
    ↓
[Advisor 4] 工具调用
    ↓
ChatModel 处理
    ↓
[Advisor 5] 响应后处理
    ↓
用户响应

4.2 先历史后检索

java 复制代码
@Service
public class HistoryFirstService {
    
    public String chatHistoryFirst(
        String conversationId,
        String message
    ) {
        return chatClient.prompt()
            .user(message)
            .advisors(
                // 先检索历史对话
                new PromptChatMemoryAdvisor(chatMemory),
                // 再检索知识库
                new QuestionAnswerAdvisor(vectorStore)
            )
            .advisors(a -> a
                .param(CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)
                .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 50)
            )
            .call()
            .content();
    }
}

4.3 先检索后历史

java 复制代码
@Service
public class KnowledgeFirstService {
    
    public String chatKnowledgeFirst(
        String conversationId,
        String message
    ) {
        return chatClient.prompt()
            .user(message)
            .advisors(
                // 先检索知识库
                new QuestionAnswerAdvisor(vectorStore),
                // 再检索历史对话
                new PromptChatMemoryAdvisor(chatMemory)
            )
            .advisors(a -> a
                .param(CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)
                .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 20)
            )
            .call()
            .content();
    }
}

五、长期记忆架构

5.1 三层记忆架构

复制代码
┌─────────────────────────────────────┐
│   工作记忆(10 条最近消息)        │
│   - Redis ChatMemory              │
│   - 快速访问                      │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│   短期记忆(最近 24 小时)         │
│   - VectorStoreChatMemory          │
│   - 语义检索                      │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│   长期记忆(用户偏好、重要事件)    │
│   - 专用数据库                    │
│   - 定期更新                      │
└─────────────────────────────────────┘

5.2 长期记忆服务

java 复制代码
@Service
public class LongTermMemoryService {
    
    private final VectorStore memoryStore;
    private final JdbcTemplate jdbcTemplate;
    private final ChatClient chatClient;
    
    // 保存重要信息到长期记忆
    public void saveImportantInfo(
        String userId,
        String info,
        double importance
    ) {
        // 存储到向量数据库
        Document memory = new Document(info, Map.of(
            "user_id", userId,
            "importance", importance,
            "created_at", Instant.now(),
            "type", "long_term"
        ));
        
        memoryStore.add(List.of(memory));
        
        // 更新用户画像
        updateUserProfile(userId, info);
    }
    
    // 从长期记忆检索
    public List<Document> retrieveLongTermMemory(
        String userId,
        String query
    ) {
        return memoryStore.similaritySearch(
            SearchRequest.query(query)
                .withTopK(5)
                .withFilterExpression("user_id == '%s'".formatted(userId))
        );
    }
    
    // 更新用户画像
    private void updateUserProfile(String userId, String info) {
        String sql = """
            INSERT INTO user_profiles (user_id, preferences, updated_at)
            VALUES (?, ?, ?)
            ON CONFLICT (user_id) 
            DO UPDATE SET 
                preferences = user_profiles.preferences || ?,
                updated_at = ?
            """;
        
        jdbcTemplate.update(
            sql,
            userId,
            info,
            Instant.now(),
            info,
            Instant.now()
        );
    }
}

六、实战案例:智能客服记忆系统

java 复制代码
@Service
public class CustomerServiceMemorySystem {
    
    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    private final LongTermMemoryService longTermMemory;
    
    public String handleCustomerInquiry(
        String customerId,
        String inquiry
    ) {
        // 1. 检索长期记忆
        List<Document> userProfile = longTermMemory
            .retrieveLongTermMemory(customerId, "用户偏好");
        
        // 2. 构建系统提示词
        String systemPrompt = buildSystemPrompt(userProfile);
        
        // 3. 调用 ChatClient
        String response = chatClient.prompt()
            .system(systemPrompt)
            .user(inquiry)
            .advisors(
                new PromptChatMemoryAdvisor(chatMemory),
                new QuestionAnswerAdvisor(knowledgeBase)
            )
            .advisors(a -> a
                .param(CHAT_MEMORY_CONVERSATION_ID_KEY, customerId)
            )
            .call()
            .content();
        
        // 4. 提取并保存重要信息
        extractAndSaveImportantInfo(customerId, inquiry, response);
        
        return response;
    }
    
    private String buildSystemPrompt(List<Document> userProfile) {
        if (userProfile.isEmpty()) {
            return "你是一个专业的客服代表";
        }
        
        String preferences = userProfile.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n"));
        
        return """
            你是一个专业的客服代表。
            
            用户偏好:
            %s
            
            请根据用户偏好提供个性化的服务。
            """.formatted(preferences);
    }
    
    private void extractAndSaveImportantInfo(
        String customerId,
        String inquiry,
        String response
    ) {
        String prompt = """
            从以下对话中提取重要的用户信息:
            
            用户提问:%s
            客服回答:%s
            
            重要信息包括:姓名、联系方式、偏好、投诉等
            """.formatted(inquiry, response);
        
        String extractedInfo = chatClient.prompt()
            .user(prompt)
            .call()
            .content();
        
        if (!extractedInfo.isBlank()) {
            longTermMemory.saveImportantInfo(
                customerId,
                extractedInfo,
                0.8  // 重要性评分
            );
        }
    }
}

总结

Spring AI 2.0 的对话记忆管理机制让构建自然的 AI 对话应用变得简单。通过合理选择 ChatMemory 实现、优化 Advisor 执行顺序、实现记忆压缩,我们可以构建出高效、智能的对话系统。

作为架构师,建议:

  1. 混合使用:结合短期和长期记忆
  2. 定期压缩:避免记忆无限增长
  3. 语义检索:使用向量存储提高检索精度
  4. 个性化:基于用户偏好定制对话
  5. 性能优化:合理设置记忆大小和检索数量

参考资料:

相关推荐
吴彦祖北京分祖2 小时前
OpenClaw爆发背后的安全深渊
人工智能
冰西瓜6002 小时前
深度学习的数学原理(二十)—— 序列建模与词嵌入
人工智能·深度学习
【建模先锋】2 小时前
Nature子刊论文复现!基于信号分解和机器学习的锂电池RUL预测
人工智能·机器学习·锂电池·锂电池寿命预测·nasa·寿命预测·锂电池rul预测
匆匆忙忙之间游刃有余2 小时前
Openclaw 为什么突然火了?我拆完它的架构后,发现它正在把 AI 助手变成“数字分身”
人工智能·后端
ar01232 小时前
维修新机遇:AR远程协助助力智能化远程维修指导
人工智能·ar
两万五千个小时2 小时前
AI Agent 能力分级:从工具到 AGI
人工智能·程序员·架构
码农三叔2 小时前
(10-4)大模型时代的人形机器人感知:感知与任务规划的联动
人工智能·机器人·人形机器人
炫饭第一名2 小时前
从前端视角解读 OpenClaw(上):Lit 驱动的 AI 控制网关面板
前端·人工智能·前端框架