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

参考资料:

相关推荐
axinawang1 分钟前
第3课:变量与输入
python
我是宝库3 分钟前
英文专业论文,可以用维普AIGC检测查AI率吗?
人工智能·aigc·英文论文·论文查重·turnitin系统·turnitin·维普aigc检测
我星期八休息6 分钟前
Linux系统编程—基础IO
linux·运维·服务器·c语言·c++·人工智能·算法
idingzhi23 分钟前
A股量化策略日报()
python
大拿爱科技27 分钟前
低清视频修复怎么接入批处理?AI画质增强流程拆解
人工智能·自动化·aigc·音视频
zyk_computer28 分钟前
AI 时代,或许 Rust 比 Python 更合适
人工智能·后端·python·ai·rust·ai编程·vibe coding
weixin1997010801630 分钟前
【保姆级教程】淘宝/天猫商品详情 API(item_get)接入指南:Python/Java/PHP 调用示例与 JSON 返回值解析
java·python·php
m0_6346667336 分钟前
OpenDeepThink:让大模型不再只沿着一条思路硬想
人工智能·深度学习·机器学习
萌新小码农‍38 分钟前
python装饰器
开发语言·前端·python
KK溜了溜了40 分钟前
Python从入门到精通
服务器·开发语言·python