Spring AI系列之对话记忆与工具调用指南

我来为你优化这篇关于 Spring AI 对话记忆与工具调用的博客,补充 Redis 缓存方案,并让它更加清晰易懂。

首先让我搜索一些相关的图片素材来丰富博客内容:

现在我来为你呈现优化后的博客内容,图文并茂,结构清晰:


🧠 Spring AI 对话记忆与工具调用完全指南

让大语言模型拥有"记忆力",并学会使用工具


一、为什么需要对话记忆?

1.1 大模型的"失忆症"

大语言模型(LLM)本质上是无状态的,每次请求都是独立的。就像金鱼只有7秒记忆,模型不会记住你上一秒说了什么。

场景对比:

无记忆对话 有记忆对话
用户:我叫张三 AI:你好张三! 用户:我叫什么? AI:抱歉,我不知道你的名字。 用户:我叫张三 AI:你好张三! 用户:我叫什么? AI:你刚才告诉我你叫张三。

二、Spring AI 记忆架构

Spring AI 通过 Advisor 机制 实现记忆功能,核心组件:

复制代码
┌─────────────────────────────────────────┐
│           ChatClient (客户端)            │
├─────────────────────────────────────────┤
│  ┌─────────────────────────────────┐   │
│  │     MessageChatMemoryAdvisor    │   │
│  │         (记忆顾问)               │   │
│  └─────────────────────────────────┘   │
├─────────────────────────────────────────┤
│  ┌─────────────────────────────────┐   │
│  │        ChatMemory (接口)         │   │
│  │   ┌─────────────────────────┐   │   │
│  │   │ ChatMemoryRepository    │   │   │
│  │   │    (存储仓库)            │   │   │
│  │   └─────────────────────────┘   │   │
│  └─────────────────────────────────┘   │
└─────────────────────────────────────────┘

三、基础实现:内存记忆

3.1 快速开始

使用 MessageWindowChatMemory 实现基于内存的对话记忆:

java 复制代码
@Autowired
private OpenAiChatModel chatModel;

@Test
void testMemory() {
    // 1. 创建记忆容器:最多保留10条消息
    ChatMemory memory = MessageWindowChatMemory.builder()
            .maxMessages(10)
            .build();
    
    // 2. 构建客户端并添加记忆顾问
    ChatClient client = ChatClient.builder(chatModel)
            .defaultAdvisors(new MessageChatMemoryAdvisor(memory))
            .build();
    
    // 3. 第一轮对话
    String response1 = client.prompt()
            .user("我叫张三,今年28岁")
            .call()
            .content();
    System.out.println("AI: " + response1);
    // 输出:你好张三,很高兴认识你!
    
    // 4. 第二轮对话(测试记忆)
    String response2 = client.prompt()
            .user("我今年多大了?")
            .call()
            .content();
    System.out.println("AI: " + response2);
    // 输出:你今年28岁。
}

3.2 原理解析

MessageChatMemoryAdvisor 工作流程:

java 复制代码
// 伪代码展示Advisor的工作流程
public class MessageChatMemoryAdvisor implements CallAroundAdvisor {
    
    @Override
    public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) {
        // 1. 从记忆中读取历史消息
        List<Message> history = chatMemory.get(conversationId);
        
        // 2. 将历史消息添加到当前请求
        request.messages().addAll(0, history);
        
        // 3. 调用模型获取响应
        AdvisedResponse response = chain.nextAroundCall(request);
        
        // 4. 将新对话存入记忆
        chatMemory.add(conversationId, request.userMessage());
        chatMemory.add(conversationId, response.response());
        
        return response;
    }
}

四、生产级方案:持久化存储

内存存储在应用重启后会丢失,生产环境需要持久化方案。

4.1 JDBC 数据库存储

4.1.1 添加依赖
xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-jdbc-memory</artifactId>
</dependency>
4.1.2 配置数据源
java 复制代码
@Configuration
public class ChatMemoryConfig {
    
    @Bean
    public ChatMemoryRepository chatMemoryRepository(
            JdbcTemplate jdbcTemplate) {
        
        return JdbcChatMemoryRepository.builder()
                .jdbcTemplate(jdbcTemplate)
                .dialect(new MysqlChatMemoryRepositoryDialect())
                .build();
    }
    
    @Bean
    public ChatMemory chatMemory(ChatMemoryRepository repository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(repository)
                .maxMessages(100)  // 保留最近100条
                .build();
    }
}
4.1.3 数据库表结构
sql 复制代码
-- MySQL 表结构(自动创建)
CREATE TABLE chat_memory (
    conversation_id VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    type VARCHAR(50) NOT NULL,  -- USER / ASSISTANT / SYSTEM
    timestamp BIGINT NOT NULL,
    INDEX idx_conversation (conversation_id, timestamp)
);

4.2 🚀 Redis 缓存存储(高性能方案)

对于高并发场景,Redis 是更好的选择,提供亚毫秒级响应。

4.2.1 为什么选 Redis?
特性 JDBC Redis
读取速度 ~10ms ~1ms
支持过期策略
分布式共享
内存占用 中等
适用场景 中小规模 高并发/实时
4.2.2 自定义 RedisChatMemory

Spring AI 官方暂未提供 Redis 实现,我们可以自定义:

java 复制代码
@Component
public class RedisChatMemory implements ChatMemory {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String KEY_PREFIX = "chat:memory:";
    private static final long TTL_HOURS = 24; // 24小时过期
    
    @Override
    public void add(String conversationId, List<Message> messages) {
        String key = KEY_PREFIX + conversationId;
        
        // 使用 Redis List 结构存储消息
        for (Message message : messages) {
            String json = serializeMessage(message);
            redisTemplate.opsForList().rightPush(key, json);
        }
        
        // 设置过期时间
        redisTemplate.expire(key, TTL_HOURS, TimeUnit.HOURS);
        
        // 保留最近 N 条(滑动窗口)
        trimMemory(key, 50);
    }
    
    @Override
    public List<Message> get(String conversationId, int lastN) {
        String key = KEY_PREFIX + conversationId;
        
        // 从 Redis 读取最近 N 条
        List<String> jsonList = redisTemplate.opsForList()
                .range(key, -lastN, -1);
        
        return jsonList.stream()
                .map(this::deserializeMessage)
                .collect(Collectors.toList());
    }
    
    @Override
    public void clear(String conversationId) {
        redisTemplate.delete(KEY_PREFIX + conversationId);
    }
    
    // 序列化/反序列化方法
    private String serializeMessage(Message message) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(message);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("消息序列化失败", e);
        }
    }
    
    private Message deserializeMessage(String json) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            return mapper.readValue(json, Message.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("消息反序列化失败", e);
        }
    }
    
    private void trimMemory(String key, int maxSize) {
        Long size = redisTemplate.opsForList().size(key);
        if (size != null && size > maxSize) {
            // 保留后 maxSize 条,删除前面的
            redisTemplate.opsForList().trim(key, size - maxSize, -1);
        }
    }
}
4.2.3 Redis 配置类
java 复制代码
@Configuration
public class RedisChatMemoryConfig {
    
    @Bean
    public RedisChatMemory redisChatMemory() {
        return new RedisChatMemory();
    }
    
    @Bean
    public ChatClient chatClient(
            OpenAiChatModel chatModel,
            RedisChatMemory chatMemory) {
        
        return ChatClient.builder(chatModel)
                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
                .build();
    }
}

参考资料:

相关推荐
北京软秦科技有限公司2 小时前
IACheck助力能源电力检测报告智能审核:AI报告审核提升质量与效率
大数据·人工智能·能源
arvin_xiaoting2 小时前
从 0 到 1:搭建自学习 AI Agent 系统的完整工程指南
人工智能·学习·系统设计·ai agent·lancedb·自学习·openclaw
火山引擎开发者社区2 小时前
真的懂?搞定 10 大热门 Skills,用 ArkClaw 实现养虾自由
人工智能
weixin_704266052 小时前
Spring整合MyBatis(一)
java·spring·mybatis
冰西瓜6002 小时前
深度学习的数学原理(十七)—— 归一化:BN与LN
人工智能·深度学习
A10169330712 小时前
maven导入spring框架
数据库·spring·maven
飞Link2 小时前
深度解析 TS2Vec:时序表示学习中的层次化建模(Hierarchical Contrastive Learning)
开发语言·python·学习·数据挖掘
代码探秘者2 小时前
【Java集合】ArrayList :底层原理、数组互转与扩容计算
java·开发语言·jvm·数据库·后端·python·算法
bryant_meng2 小时前
【Reading Notes】(7.11)Favorite Articles from 2024 November
人工智能·深度学习·计算机视觉·aigc·资讯