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();
    }
}

参考资料:

相关推荐
H Journey18 小时前
openCV之高通滤波
人工智能·opencv·计算机视觉
披着羊皮不是狼18 小时前
从零搭建 Spring Boot 3 + 本地大模型 (Ollama) 的 AI 开发环境
人工智能·spring boot·后端
chushiyunen18 小时前
python实现分离不同人声、wespeaker
开发语言·python
甄心爱学习18 小时前
【项目实训(个人2)】
python·个人开发
hnult18 小时前
考试云智能题库系统:无限层级分类 + AI 判分技术落地详解
大数据·人工智能·笔记·课程设计
波动几何18 小时前
Minimal Agent — 极简操作系统控制代理
人工智能
Dfreedom.18 小时前
神经网络训练中的梯度稳定性分析
人工智能·深度学习·神经网络·梯度·梯度消失·梯度爆炸
nimadan1218 小时前
AI仿真人剧服务商2025推荐,前沿技术与创新体验结合
人工智能·python
AI自动化工坊18 小时前
Claw Code技术深度解析:Python+Rust混合架构的设计与实现
开发语言·人工智能·python·ai·架构·rust·开源
带娃的IT创业者18 小时前
期中总结:从神经元到 GPT——AI 架构全景回顾(Version B)
人工智能·gpt·深度学习·神经网络·架构·nlp·transformer