当前位置: 首页 > news >正文

【大模型记忆实战Demo】基于SpringAIAlibaba通过内存和Redis两种方式实现多轮记忆对话

文章目录

  • 多轮对话记忆管理——基于Memory的对话记忆
    • 基于内存存储历史对话
    • 基于Redis存储历史对话

多轮对话记忆管理——基于Memory的对话记忆

Spring AI Alibaba共实现了三种方式:

  1. 基于内存的方式
  2. 基于jdbc(数据库)的方式
  3. 基于redis的方式

下文主要演示基于内存和redis的方式

基于内存存储历史对话

  • 代码
    首先定义大模型的角色,一个旅游规划师
    设置增强拦截器
    接着接口传入prompt和chatId
    设定好唯一标识符和记忆轮数
	private final ChatClient chatClient;public ChatMemoryController(ChatModel chatModel) {this.chatClient = ChatClient.builder(chatModel).defaultSystem("你是一个旅游规划师,请根据用户的需求提供旅游规划建议。").defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())).build();}	
/*** 获取内存中的聊天内容* 根据提供的prompt和chatId,从内存中获取相关的聊天内容,并设置响应的字符编码为UTF-8。** @param prompt 用于获取聊天内容的提示信息* @param chatId 聊天的唯一标识符,用于区分不同的聊天会话* @param response HTTP响应对象,用于设置响应的字符编码* @return 返回包含聊天内容的Flux<String>对象*/@GetMapping("/in-memory")public Flux<String> memory(@RequestParam("prompt") String prompt,@RequestParam("chatId") String chatId,HttpServletResponse response) {response.setCharacterEncoding("UTF-8");return chatClient.prompt(prompt).advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)).stream().content();}
  • 调用结果
    第一次:

我提问,想去杭州玩

在这里插入图片描述

  • 第二轮

我提问:那有哪些好玩的地方

在这里插入图片描述

可以看到,第二次根据我第一次“杭州”的关键词进行了推荐,拥有了记忆

基于内存的方法存在一个缺点:如果机器重启了,记忆就消失了,因此可以采用持久化到Redis的方式

基于Redis存储历史对话

  • 代码
	private final ChatClient chatClient;public ChatMemoryController(ChatModel chatModel) {this.chatClient = ChatClient.builder(chatModel).defaultSystem("你是一个旅游规划师,请根据用户的需求提供旅游规划建议。").defaultAdvisors(new MessageChatMemoryAdvisor(new RedisChatMemory("127.0.0.1",6379,null))).build();}/*** 从Redis中获取聊天内容* 根据提供的prompt和chatId,从Redis中检索聊天内容,并以Flux<String>的形式返回** @param prompt 聊天内容的提示或查询关键字* @param chatId 聊天的唯一标识符,用于从Redis中检索特定的聊天内容* @param response HttpServletResponse对象,用于设置响应的字符编码为UTF-8* @return Flux<String> 包含聊天内容的反应式流*/@GetMapping("/redis")public Flux<String> redis(@RequestParam("prompt") String prompt,@RequestParam("chatId") String chatId,HttpServletResponse response) {response.setCharacterEncoding("UTF-8");return chatClient.prompt(prompt).advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)).stream().content();}

其中的RedisChatMemory:

/**** 基于Redis的聊天记忆实现。* 该类实现了ChatMemory接口,提供了将聊天消息存储到Redis中的功能。** @author Fox*/
public class RedisChatMemory implements ChatMemory, AutoCloseable {private static final Logger logger = LoggerFactory.getLogger(RedisChatMemory.class);private static final String DEFAULT_KEY_PREFIX = "chat:";private static final String DEFAULT_HOST = "127.0.0.1";private static final int DEFAULT_PORT = 6379;private static final String DEFAULT_PASSWORD = null;private final JedisPool jedisPool;private final ObjectMapper objectMapper;public RedisChatMemory() {this(DEFAULT_HOST, DEFAULT_PORT, DEFAULT_PASSWORD);}public RedisChatMemory(String host, int port, String password) {JedisPoolConfig poolConfig = new JedisPoolConfig();this.jedisPool = new JedisPool(poolConfig, host, port, 2000, password);this.objectMapper = new ObjectMapper();logger.info("Connected to Redis at {}:{}", host, port);}@Overridepublic void add(String conversationId, List<Message> messages) {String key = DEFAULT_KEY_PREFIX + conversationId;AtomicLong timestamp = new AtomicLong(System.currentTimeMillis());try (Jedis jedis = jedisPool.getResource()) {// 使用pipeline批量操作提升性能var pipeline = jedis.pipelined();messages.forEach(message ->pipeline.hset(key, String.valueOf(timestamp.getAndIncrement()), message.toString()));pipeline.sync();}logger.info("Added messages to conversationId: {}", conversationId);}@Overridepublic List<Message> get(String conversationId, int lastN) {String key = DEFAULT_KEY_PREFIX + conversationId;try (Jedis jedis = jedisPool.getResource()) {Map<String, String> allMessages = jedis.hgetAll(key);if (allMessages.isEmpty()) {return List.of();}return allMessages.entrySet().stream().sorted((e1, e2) ->Long.compare(Long.parseLong(e2.getKey()), Long.parseLong(e1.getKey()))).limit(lastN).map(entry -> new UserMessage(entry.getValue())).collect(Collectors.toList());}}@Overridepublic void clear(String conversationId) {String key = DEFAULT_KEY_PREFIX + conversationId;try (Jedis jedis = jedisPool.getResource()) {jedis.del(key);}logger.info("Cleared messages for conversationId: {}", conversationId);}@Overridepublic void close() {try (Jedis jedis = jedisPool.getResource()) {if (jedis != null) {jedis.close();logger.info("Redis connection closed.");}if (jedisPool != null) {jedisPool.close();logger.info("Jedis pool closed.");}}}public void clearOverLimit(String conversationId, int maxLimit, int deleteSize) {try {String key = DEFAULT_KEY_PREFIX + conversationId;try (Jedis jedis = jedisPool.getResource()) {List<String> all = jedis.lrange(key, 0, -1);if (all.size() >= maxLimit) {all = all.stream().skip(Math.max(0, deleteSize)).toList();}this.clear(conversationId);for (String message : all) {jedis.rpush(key, message);}}}catch (Exception e) {logger.error("Error clearing messages from Redis chat memory", e);throw new RuntimeException(e);}}}
  • 第一次调用

提问:我想去三亚

在这里插入图片描述

在这里插入图片描述

可见成功将提问和回答都写入redis

  • 第二次调用
    在这里插入图片描述
    在这里插入图片描述

成功读取记忆,并将新的问答结果写入

至此,我们成功完成了基于内存和Redis两种方式,实现大模型的多轮记忆对话!

http://www.xdnf.cn/news/1169407.html

相关文章:

  • Arraylist与LinkedList区别
  • STM32-SPI全双工同步通信
  • LWIP学习记录2——MAC内核
  • mybatis多对一一对多的关联及拼接操作以及缓存处理
  • 【学习路线】Python全栈开发攻略:从编程入门到AI应用实战
  • Custom SRP - Draw Calls
  • Claude Code Kimi K2 环境配置指南 (Windows/macOS/Ubuntu)
  • python小工具:测内网服务器网速和延迟
  • Qt资源系统:如何有效管理图片和文件
  • Canmv k230 DAC案例——TLV5638
  • 104.二叉树的最大深度
  • API是什么,如何保障API安全?
  • 刀客doc:Netflix与YouTube开始在广告战场正面交锋
  • [学习] 笛卡尔坐标系的任意移动与旋转详解
  • 洛谷 B3939:[GESP样题 四级] 绝对素数 ← 素数判定+逆序整数
  • 深入解析 Pandas:Python 数据分析的强大工具
  • Jenkins接口自动化测试(构建)平台搭建
  • Kafka监控体系搭建:基于Prometheus+JMX+Grafana的全方位性能观测方案
  • NLP自然语言处理的一些疑点整理
  • JavaScript AJAX 实现,演示如何将 Token 添加到 Authorization
  • 怎么在Mac系统中使用不坑盒子?
  • 交叉编译opencv(Cpp)于arm64架构开发板上
  • .NET使用EPPlus导出EXCEL的接口中,文件流缺少文件名信息
  • 【n8n教程笔记——工作流Workflow】文本课程(第一阶段)——1、导航编辑器界面(Navigating the editor UI)介绍
  • numpy库的基础知识(二)
  • 理解后端开发中的API设计原则
  • 达梦数据库表字段增加时报错[-2106]:无效的表或视图名,[-2116]:列[IS_REPEAT]已存在
  • [3-02-02].第04节:开发应用 - RequestMapping注解的属性2
  • 支付网关系统前后端鉴权方案
  • 网络原理 HTTP 和 HTTPS