spring-ai-alibaba第七章阿里dashscope集成RedisChatMemory实现对话记忆

1、RedisChatMemory的实现

从git上 https://github.com/alibaba/spring-ai-alibaba 下载代码;

对 RedisChatMemory进行简单改造,改造后的代码如下

java 复制代码
/*
 * Copyright 2024-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.cloud.ai.memory.redis;

import java.util.ArrayList;
import java.util.List;

import com.alibaba.cloud.ai.memory.redis.serializer.MessageDeserializer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;

public class RedisChatMemory implements ChatMemory, AutoCloseable {

	private static final Logger logger = LoggerFactory.getLogger(RedisChatMemory.class);

	private static final String DEFAULT_KEY_PREFIX = "spring_ai_alibaba_chat_memory:";

	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 Jedis jedis;

	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.jedis = jedisPool.getResource();
		this.objectMapper = new ObjectMapper();
		SimpleModule module = new SimpleModule();
		module.addDeserializer(Message.class, new MessageDeserializer());
		this.objectMapper.registerModule(module);

		logger.info("Connected to Redis at {}:{}", host, port);
	}

	@Override
	public void add(String conversationId, List<Message> messages) {

		String key = DEFAULT_KEY_PREFIX + conversationId;

		for (Message message : messages) {
			try {
				String messageJson = objectMapper.writeValueAsString(message);
				jedis.rpush(key, messageJson);
			}
			catch (JsonProcessingException e) {
				throw new RuntimeException("Error serializing message", e);
			}
		}

		logger.info("Added messages to conversationId: {}", conversationId);
	}

	@Override
	public List<Message> get(String conversationId, int lastN) {

		String key = DEFAULT_KEY_PREFIX + conversationId;

		List<String> messageStrings = jedis.lrange(key, -lastN, -1);
		List<Message> messages = new ArrayList<>();

		for (String messageString : messageStrings) {
			try {
				Message message = objectMapper.readValue(messageString, Message.class);
				messages.add(message);
			}
			catch (JsonProcessingException e) {
				throw new RuntimeException("Error deserializing message", e);
			}
		}

		logger.info("Retrieved {} messages for conversationId: {}", messages.size(), conversationId);

		return messages;
	}

	@Override
	public void clear(String conversationId) {

		String key = DEFAULT_KEY_PREFIX + conversationId;

		jedis.del(key);
		logger.info("Cleared messages for conversationId: {}", conversationId);
	}

	@Override
	public void close() {

		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;

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

	public void updateMessageById(String conversationId, String messages) {
		String key = DEFAULT_KEY_PREFIX + conversationId;
		try {
			this.jedis.del(key);
			this.jedis.rpush(key, new String[] { messages });
		}
		catch (Exception var6) {
			logger.error("Error updating messages from Redis chat memory", var6);
			throw new RuntimeException(var6);
		}
	}

}

然后用maven编译安装,

bash 复制代码
 mvn install -DskipTests   

记下安装后的版本,此时我用的版本是

复制代码
1.0.0-M6.2-SNAPSHOT

2、spring-ai 大模型应用程序通过RedisChatMemory 实现对话记忆

pom文件

html 复制代码
<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.alibaba.cloud.ai</groupId>
			<artifactId>spring-ai-alibaba-starter</artifactId>
			<version>${spring-ai-alibaba.version}</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba.cloud.ai</groupId>
			<artifactId>spring-ai-alibaba-redis-memory</artifactId>
			<version>1.0.0-M6.2-SNAPSHOT</version>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-pool2</artifactId>
			<version>2.12.1</version>
		</dependency>

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>5.2.0</version>
		</dependency>


	</dependencies>

yml配置文件

html 复制代码
server:
  port: 8080

spring:
  ai:
    dashscope:
      api-key: sk-xoxoxoxoxoxox
      chat:
        model: qwq-plus

controller

java 复制代码
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.example.chatmemory.controller;


import com.alibaba.cloud.ai.memory.redis.RedisChatMemory;
import org.springframework.http.MediaType;
import reactor.core.publisher.Flux;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;

/**
 * @author yuluo
 * @author <a href="mailto:[email protected]">yuluo</a>
 */

@RestController
@RequestMapping("/chat-memory")
public class ChatMemoryController {

    private final ChatClient chatClient;

    public ChatMemoryController(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel).build();
    }


    @GetMapping(value = "/redis", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> redis(
            @RequestParam("prompt") String prompt,
            @RequestParam("chatId") String chatId,
            HttpServletResponse response
    ) {
        response.setCharacterEncoding("UTF-8");
        return chatClient.prompt(prompt).advisors(
                new MessageChatMemoryAdvisor(new RedisChatMemory(
                        "127.0.0.1",
                        6379,
                        "zsj381599113QQ"
                ))
        ).advisors(
                a -> a
                        .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
        ).stream().content();
    }

}

启动类

java 复制代码
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.example.chatmemory;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author yuluo
 * @author <a href="mailto:[email protected]">yuluo</a>
 */

@SpringBootApplication
public class ChatMemoryApplication {

	public static void main(String[] args) {

		SpringApplication.run(ChatMemoryApplication.class, args);
	}

}

测试如下

相关推荐
jack_xu3 分钟前
高频面试题:如何保证数据库和es数据一致性
后端·mysql·elasticsearch
264玫瑰资源库4 分钟前
问道数码兽 怀旧剧情回合手游源码搭建教程(反查重优化版)
java·开发语言·前端·游戏
pwzs13 分钟前
Java 中 String 转 Integer 的方法与底层原理详解
java·后端·基础
东阳马生架构16 分钟前
Nacos简介—2.Nacos的原理简介
java
Asthenia041222 分钟前
InnoDB文件存储结构与Socket技术(从Linux的FD到Java的API)
后端
普if加的帕29 分钟前
java Springboot使用扣子Coze实现实时音频对话智能客服
java·开发语言·人工智能·spring boot·实时音视频·智能客服
Asthenia041242 分钟前
RocketMQ 消息不丢失与持久化机制详解-生产者与Broker之间的详解
后端
爱喝一杯白开水42 分钟前
SpringMVC从入门到上手-全面讲解SpringMVC的使用.
java·spring·springmvc
王景程1 小时前
如何测试短信接口
java·服务器·前端