大模型专栏--Spring AI Chat Memory

Spring AI Chat Memory

LLM 模型本身是一个无状态的模型,没有临时记忆的能力。当发生如下场景时,就会产生错误回答:

java 复制代码
@RestController
@RequestMapping("/ai")
public class AIController {

	private final ChatClient chatClient;

	public AIController(ChatClient.Builder builder) {
		this.chatClient = builder.build();
	}


	@RequestMapping("/chat/{msg}")
	public String chat(@PathVariable String msg) {

		return this.chatClient.prompt(new Prompt(msg)).call().chatResponse().getResult().getOutput().getContent();
	}

}

启动项目,访问接口时会出现:

shell 复制代码
# 1
input:你好,我的名字叫牧生
output:你好,牧生!很高兴认识你。我叫通义千问,是阿里云开发的超大规模语言模型。我可以帮助你解答问题、提供信息或进行各种话题的讨论。有什么我可以帮到你的吗?

# 2
input:我叫什么名字
output:您没有告诉我您的名字,所以我无法直接回答。如果您愿意分享,可以告诉我您的名字是什么。

第二次调用 llm 发送 prompt,大模型无法记住第一轮的上下文,所以无法给出正确的答案。

要实现一个可以让大模型具有聊天记忆能力,根据之前的聊天信息进行回答,应该如何如何实现呢?

手动实现一个 Chat Memory

在实现之前,先了解下 Spring AI 的 Message 类型,确保塞到正确的 Message 类型中,避免出现错误。

其中:

  • UserMessage:用户消息,指用户输入的 prompt;
  • SystemMessage:系统限制性消息,通常用于指定 LLM 角色,一般只会设置一次;
  • AssistantMessage:LLM 输出;
  • FunctionMessage:函数调用时的消息。

为此,可以写出如下的代码:

java 复制代码
@RestController
@RequestMapping("/ai")
public class AIController {

	List<Message> historyMessage = new ArrayList<>();

	private final ChatClient chatClient;

	public AIController(ChatClient.Builder builder) {
		this.chatClient = builder.build();
	}


	@RequestMapping("/chat/{msg}")
	public String chat(@PathVariable String msg) {

		// 添加用户消息
		historyMessage.add(new UserMessage(msg));

		// 调用大模型时,传入 list
		AssistantMessage output = this.chatClient.prompt(new Prompt(historyMessage)).call().chatResponse().getResult().getOutput();

		// 添加模型消息
		historyMessage.add(output);

		return output.getContent();
	}

}
shell 复制代码
# 1
input:你好,我的名字叫牧生
output:你好,牧生!很高兴认识你。我叫通义千问,是阿里云研发的超大规模语言模型。我可以帮助你解答问题、提供信息或者进行聊天。有什么我可以帮到你的吗?

# 2
input:我叫什么名字
output:你刚才提到你的名字叫牧生。如果你有其他问题或需要进一步的帮助,请告诉我!

至此,已经实现了一个简单的 chat memory 功能。然后,我们来分析一波这种写法存在的问题:

  1. historyMessage 的大小是无限的吗?

    在 java 代码中无限,但是 llm 的输入 Token 有限,例如 GPT,通义等模型的 Token 限制:

  1. 文本内容:在传递给 llm 的内容中,可能存在无关的文本,影响 llm 的输出,使其产生幻觉或者占用大量 token。

  2. 没有和对话关联,直接从 list 中获取,正常应该是每次会话一个历史消息。

  3. 数据不持久,在服务器关机重启之后数据丢失。

  4. 没有相关策略合理组织 Message。

之后,来看下 Spring AI 的 chat Memory 实现

Spring AI Chat Memory

ChatMemory 接口

Spring AI 中提供的对 Message 存取的接口。

java 复制代码
public interface ChatMemory {
    default void add(String conversationId, Message message) {
        this.add(conversationId, List.of(message));
    }

    // conversationId:会话ID, message:消息(包括用户消息和回复消息)
    // 将 Message 添加到会话中
    void add(String conversationId, List<Message> messages);

    // conversationId:会话ID, 
    // 取最新的几条数据,以此可以控制一次会话窗口的大小,比如对于 qwen-plsu 的 30720 token 限制
    List<Message> get(String conversationId, int lastN);

    // conversationId:会话ID
	// 清除对话历史消息
    void clear(String conversationId);
}

InMemoryChatMemory

ChatMemory 的实现,表示为聊天对话历史记录提供内存存储;

java 复制代码
public class InMemoryChatMemory implements ChatMemory {
    
    // 主要数据结构
    Map<String, List<Message>> conversationHistory = new ConcurrentHashMap();

    public InMemoryChatMemory() {
    }

    public void add(String conversationId, List<Message> messages) {
        this.conversationHistory.putIfAbsent(conversationId, new ArrayList());
        ((List)this.conversationHistory.get(conversationId)).addAll(messages);
    }

    public List<Message> get(String conversationId, int lastN) {
        List<Message> all = (List)this.conversationHistory.get(conversationId);
        return all != null ? all.stream().skip((long)Math.max(0, all.size() - lastN)).toList() : List.of();
    }

    public void clear(String conversationId) {
        this.conversationHistory.remove(conversationId);
    }
}

使用方式

Spring AI 框架提供了三种Advisor来使用ChatMeomry。

  • MessageChatMemoryAdvisor:查询对象会话ID的历史消息添加到提示词文本中,核心代码如下;
  • PromptChatMemoryAdvisor:检索到的内存中的历史消息将添加到提示的系统文本中;
  • VectorStoreChatMemoryAdvisor:检索向量数据库中的历史消息将添加到提示的系统文本中。
java 复制代码
@RestController
@RequestMapping("/ai")
public class AIController {

	private final ChatClient chatClient;

	private final ChatModel chatModel;

	public AIController(ChatClient.Builder builder, ChatModel chatModel) {

		this.chatModel = chatModel;
		this.chatClient = ChatClient.builder(chatModel)
				.defaultAdvisors(
						new MessageChatMemoryAdvisor(new InMemoryChatMemory())
				).build();
	}

	@GetMapping("/chatWithChatMemory")
	public String chatWithChatMemory(String chatId, String prompt) {

		return chatClient.prompt()
				.user(prompt)
				.advisors(
						a -> a
								.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
								.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
				).call().chatResponse().getResult().getOutput().getContent();
	}

}

访问:

shell 复制代码
# 1
input: http://localhost:8080/ai/chatWithChatMemory?chatId=10001&prompt=你好,我是牧生
output:你好,牧生!很高兴认识你。有什么我可以帮到你的吗?

# 2
input:http://localhost:8080/ai/chatWithChatMemory?chatId=10001&prompt=我是谁
output:您是这次对话的发起者,您之前提到您的名字叫牧生。如果您有其他身份或角色想要分享,或者有任何问题和需要帮助的地方,都欢迎告诉我!

# 当切换 chatId 时
input:http://localhost:8080/ai/chatWithChatMemory?chatId=10002&prompt=我叫什么名字
output:您好!您刚才没有提到您的名字,所以我无法直接回答您的问题。如果您愿意分享,可以告诉我您的名字,或者如果您是在某种特定情境下问这个问题,也可以提供更多背景信息,这样我可能能更好地帮助您。如果这是一个私人问题,您也可以选择不回答。我在这里是为了支持您,有任何问题都可以问我。

其中参数chatId 表示会话 ID,实现上下文与会话绑定。CHAT_MEMORY_RETRIEVE_SIZE_KEY 表示历史会话最多100条发给AI。

问题

通过两种方式都可以实现 chat memory,相比 spring ai 更加完善。

但是也存在以下问题:

  1. 数据不持久,在服务器关机重启之后数据丢失;

  2. 没有相关策略合理组织 Message。

Spring AI Alibaba 的 Memory 正在紧锣密鼓的开发中!!!

相关推荐
肉包之5 分钟前
pythonOpenCV篇:0基础带你python入门之常用函数
人工智能·python·opencv·计算机视觉·目标跟踪
喜欢AC~不爱WA8 分钟前
简单的Java小项目
java·开发语言
康谋自动驾驶13 分钟前
康谋方案 | 多源相机数据采集与算法集成测试方案
人工智能·科技·数据分析·自动驾驶·汽车
互联网动态分析13 分钟前
Apache Kafka:实时数据流处理的强大引擎
java·kafka
泰山小张只吃荷园17 分钟前
期末复习-计算机网络篇
java·网络·网络协议·计算机网络·面试
qq85722263120 分钟前
java+springboot+mysql科研成果管理系统
java·spring boot·mysql
小嗷犬21 分钟前
【论文笔记】VisionZip: Longer is Better but Not Necessary in Vision Language Models
论文阅读·人工智能·语言模型·大模型·多模态
gikod22 分钟前
【笔记】架构上篇Day6 法则四:为什么要顺应技术的生命周期?
大数据·人工智能·笔记·架构
Easy数模25 分钟前
经典NLP案例 | 推文评论情绪分析:从数据预处理到模型构建的全面指南
人工智能·自然语言处理·nlp
m0_7482526032 分钟前
用Maven开发Spring Boot 项目
java·spring boot·maven