大模型专栏--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 正在紧锣密鼓的开发中!!!

相关推荐
wal13145203 小时前
OpenClaw v2026.4.8 发布:记忆系统重大升级 + 多项安全修复
人工智能·安全·openclaw
Deepoch3 小时前
VLA 端侧智能赋能:Deepoc 开发板重构除草机器人自主作业能力
人工智能·科技·机器人·具身模型·deepoc·除草
m0_372257023 小时前
bert和LLM训练的时候输入输出的格式是什么有什么区别
人工智能·深度学习·bert
han_hanker3 小时前
@Validated @Valid 用法
java·spring boot
紧固视界3 小时前
2026上海紧固件专业展,紧固件设备集中展示平台
大数据·人工智能·上海紧固件展·紧固件展·上海紧固件专业展
小CC吃豆子3 小时前
详细介绍一下静态分析工具 SonarQube
java
DevOpenClub3 小时前
全国三甲医院主体信息 API 接口
java·大数据·数据库
杨夏同学3 小时前
AI入门——如何计算神经网络的参数
人工智能·深度学习·神经网络
言慢行善3 小时前
SpringBoot中的注解介绍
java·spring boot·后端
好运的阿财3 小时前
大模型热切换功能完整实现指南
人工智能·python·程序人生·开源·ai编程