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

SpringAI(GA版)的Advisor:快速上手+源码解读

原文链接:SpringAI的Advisor:快速上手+源码解读

教程说明

说明:本教程将采用2025年5月20日正式的GA版,给出如下内容

  1. 核心功能模块的快速上手教程
  2. 核心功能模块的源码级解读
  3. Spring ai alibaba增强的快速上手教程 + 源码级解读

版本:JDK21 + SpringBoot3.4.5 + SpringAI 1.0.0 + SpringAI Alibaba最新

将陆续完成如下章节教程

快速上手

实战代码可见:https://github.com/GTyingzi/spring-ai-tutorial 下的 advisor

pom 文件

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-autoconfigure-model-openai</artifactId><version>${spring-ai.version}</version></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-autoconfigure-model-chat-client</artifactId><version>${spring-ai.version}</version></dependency></dependencies>

application.yml

server:port: 8080spring:application:name: advisor-baseai:openai:api-key: ${DASHSCOPEAPIKEY}base-url: https://dashscope.aliyuncs.com/compatible-modechat:options:model: qwen-max

controller

MemoryMessageAdvisorController
package com.spring.ai.tutorial.advisor.controller;import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.messages.Message;
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 java.util.List;import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATIONID;/*** @author yingzi* @date 2025/5/22 22:59*/
@RestController
@RequestMapping("/advisor/memory/message")
public class MemoryMessageAdvisorController {private final ChatClient chatClient;private final InMemoryChatMemoryRepository chatMemoryRepository = new InMemoryChatMemoryRepository();private final int MAXMESSAGES = 100;private final MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(MAXMESSAGES).build();public MemoryMessageAdvisorController(ChatClient.Builder builder) {this.chatClient = builder.defaultAdvisors(MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build()).build();}@GetMapping("/call")public String call(@RequestParam(value = "query", defaultValue = "你好,我的外号是影子,请记住呀") String query,@RequestParam(value = "conversationid", defaultValue = "yingzi") String conversationId) {return chatClient.prompt(query).advisors(a -> a.param(CONVERSATIONID, conversationId)).call().content();}@GetMapping("/messages")public List<Message> messages(@RequestParam(value = "conversationid", defaultValue = "yingzi") String conversationId) {return messageWindowChatMemory.get(conversationId);}}
效果

以会话 Id=“yingzi”,先告知模型我的名字

再以同一个会话 Id=“yingzi”,模型能根据以往的消息记住了我的名字

获取历史消息记录,我们能得到历史消息记录

MemoryPromptAdvisorController
package com.spring.ai.tutorial.advisor.controller;import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.messages.Message;
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 java.util.List;import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATIONID;/*** @author yingzi* @date 2025/5/23 17:29*/
@RestController
@RequestMapping("/advisor/memory/prompt")
public class MemoryPromptAdvisorController {private final ChatClient chatClient;private final InMemoryChatMemoryRepository chatMemoryRepository = new InMemoryChatMemoryRepository();private final int MAXMESSAGES = 100;private final MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(MAXMESSAGES).build();public MemoryPromptAdvisorController(ChatClient.Builder builder) {this.chatClient = builder.defaultAdvisors(PromptChatMemoryAdvisor.builder(messageWindowChatMemory).build()).build();}@GetMapping("/call")public String call(@RequestParam(value = "query", defaultValue = "你好,我的外号是影子,请记住呀") String query,@RequestParam(value = "conversationid", defaultValue = "yingzi") String conversationId) {return chatClient.prompt(query).advisors(a -> a.param(CONVERSATIONID, conversationId)).call().content();}@GetMapping("/messages")public List<Message> messages(@RequestParam(value = "conversationid", defaultValue = "yingzi") String conversationId) {return messageWindowChatMemory.get(conversationId);}
}
效果

以会话 Id=“yingzi”,先告知模型我的名字

再以同一个会话 Id=“yingzi”,模型能根据以往的消息记住了我的名字

获取历史消息记录,我们能得到历史消息记录

advisor 源码篇

[!TIP]
本源码取自:Spring AI 的 5 月 20 号发布的 1.0.0(GA 版)

  1. 基础只提供 BaseChatMemoryAdvisor
  2. rag 模块引入 RAG,源码可参照 Rag 源码篇

架构图

Advisor

advisor 基础信息配置

  • name:指定名字,确保唯一性
  • order:数值越小,执行越靠前
package org.springframework.ai.chat.client.advisor.api;import org.springframework.core.Ordered;public interface Advisor extends Ordered {int DEFAULTCHATMEMORYPRECEDENCEORDER = -2147482648;String getName();
}
package org.springframework.core;public interface Ordered {int HIGHESTPRECEDENCE = Integer.MINVALUE;int LOWESTPRECEDENCE = Integer.MAXVALUE;int getOrder();
}
CallAdvisor

call 调用,跟 AI 模型交互前、后的一些逻辑

package org.springframework.ai.chat.client.advisor.api;import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;public interface CallAdvisor extends Advisor {ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}
StreamAdvisor

Stream 调用,跟 AI 模型交互前、后的一些逻

package org.springframework.ai.chat.client.advisor.api;import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import reactor.core.publisher.Flux;public interface StreamAdvisor extends Advisor {Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}
BaseAdvisor

类说明:继承 CallAdvisor、StreamAdvisor,提供统一扩展点,统计拦截机制实现与 AI 模型请求和响应后的统一交互逻辑。

字段说明

字段名称
类型
描述
DEFAULTSCHEDULER
Scheduler
定义默认调度器Schedulers.boundedElastic(),用于流式处理时的线程调度

方法说明

方法名称
描述
adviseCall
同步调用,拦截call调用AI模型的请求和响应。子类实现before、after方法
adviseStream
流式调用,拦截stream调用AI模型的请求和响应。子类实现before、after方法
before
AI模型请求前的逻辑,需要子类实现
after
AI模型响应后的逻辑,需要子类实现
package org.springframework.ai.chat.client.advisor.api;import java.util.Objects;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.AdvisorUtils;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;public interface BaseAdvisor extends CallAdvisor, StreamAdvisor {Scheduler DEFAULTSCHEDULER = Schedulers.boundedElastic();default ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");Assert.notNull(callAdvisorChain, "callAdvisorChain cannot be null");ChatClientRequest processedChatClientRequest = this.before(chatClientRequest, callAdvisorChain);ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(processedChatClientRequest);return this.after(chatClientResponse, callAdvisorChain);}default Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");Assert.notNull(streamAdvisorChain, "streamAdvisorChain cannot be null");Assert.notNull(this.getScheduler(), "scheduler cannot be null");Mono var10000 = Mono.just(chatClientRequest).publishOn(this.getScheduler()).map((request) -> this.before(request, streamAdvisorChain));Objects.requireNonNull(streamAdvisorChain);Flux<ChatClientResponse> chatClientResponseFlux = var10000.flatMapMany(streamAdvisorChain::nextStream);return chatClientResponseFlux.map((response) -> {if (AdvisorUtils.onFinishReason().test(response)) {response = this.after(response, streamAdvisorChain);}return response;}).onErrorResume((error) -> Flux.error(new IllegalStateException("Stream processing failed", error)));}default String getName() {return this.getClass().getSimpleName();}ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain);ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain);default Scheduler getScheduler() {return DEFAULTSCHEDULER;}
}

BaseChatMemoryAdvisor

类说明:从传入的上下文 Map 中提取会话 Id,若不存在则使用默认值

package org.springframework.ai.chat.client.advisor.api;import java.util.Map;
import org.springframework.util.Assert;public interface BaseChatMemoryAdvisor extends BaseAdvisor {default String getConversationId(Map<String, Object> context, String defaultConversationId) {Assert.notNull(context, "context cannot be null");Assert.noNullElements(context.keySet().toArray(), "context cannot contain null keys");Assert.hasText(defaultConversationId, "defaultConversationId cannot be null or empty");return context.containsKey("chatmemoryconversationid") ? context.get("chatmemoryconversationid").toString() : defaultConversationId;}
}
MessageChatMemoryAdvisor

类的说明:消息记忆存储的 Advisor 类

字段说明

字段名称
类型
描述
order
int
指定顺序
defaultConversationId
String
默认会话Id
chatMemory
ChatMemory
聊天记忆接口
scheduler
Scheduler
流式处理时的线程调度

方法说明

方法名称
描述
before
1. 从ChatMemory中取出历史消息
2. 当前消息加入ChatMemory
3. 整合当前消息+历史消息
after
将模型响应的消息加入ChatMemory
adviseStream
覆盖了BaseAdvisor默认实现逻辑
- 注:在将多个流式响应合并成一个完整响应对象后,在调用after,确保只保留完整的模型输出,避免部分信息写入memory导致混乱
package org.springframework.ai.chat.client.advisor;import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.springframework.ai.chat.client.ChatClientMessageAggregator;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.client.advisor.api.BaseChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;public final class MessageChatMemoryAdvisor implements BaseChatMemoryAdvisor {private final ChatMemory chatMemory;private final String defaultConversationId;private final int order;private final Scheduler scheduler;private MessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int order, Scheduler scheduler) {Assert.notNull(chatMemory, "chatMemory cannot be null");Assert.hasText(defaultConversationId, "defaultConversationId cannot be null or empty");Assert.notNull(scheduler, "scheduler cannot be null");this.chatMemory = chatMemory;this.defaultConversationId = defaultConversationId;this.order = order;this.scheduler = scheduler;}public int getOrder() {return this.order;}public Scheduler getScheduler() {return this.scheduler;}public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {String conversationId = this.getConversationId(chatClientRequest.context(), this.defaultConversationId);List<Message> memoryMessages = this.chatMemory.get(conversationId);List<Message> processedMessages = new ArrayList(memoryMessages);processedMessages.addAll(chatClientRequest.prompt().getInstructions());ChatClientRequest processedChatClientRequest = chatClientRequest.mutate().prompt(chatClientRequest.prompt().mutate().messages(processedMessages).build()).build();UserMessage userMessage = processedChatClientRequest.prompt().getUserMessage();this.chatMemory.add(conversationId, userMessage);return processedChatClientRequest;}public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {List<Message> assistantMessages = new ArrayList();if (chatClientResponse.chatResponse() != null) {assistantMessages = chatClientResponse.chatResponse().getResults().stream().map((g) -> g.getOutput()).toList();}this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId), assistantMessages);return chatClientResponse;}public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {Scheduler scheduler = this.getScheduler();Mono var10000 = Mono.just(chatClientRequest).publishOn(scheduler).map((request) -> this.before(request, streamAdvisorChain));Objects.requireNonNull(streamAdvisorChain);return var10000.flatMapMany(streamAdvisorChain::nextStream).transform((flux) -> (new ChatClientMessageAggregator()).aggregateChatClientResponse(flux, (response) -> this.after(response, streamAdvisorChain)));}public static Builder builder(ChatMemory chatMemory) {return new Builder(chatMemory);}public static final class Builder {private String conversationId = "default";private int order = -2147482648;private Scheduler scheduler;private ChatMemory chatMemory;private Builder(ChatMemory chatMemory) {this.scheduler = BaseAdvisor.DEFAULTSCHEDULER;this.chatMemory = chatMemory;}public Builder conversationId(String conversationId) {this.conversationId = conversationId;return this;}public Builder order(int order) {this.order = order;return this;}public Builder scheduler(Scheduler scheduler) {this.scheduler = scheduler;return this;}public MessageChatMemoryAdvisor build() {return new MessageChatMemoryAdvisor(this.chatMemory, this.conversationId, this.order, this.scheduler);}}
}
PromptChatMemoryAdvisor

类的说明:将聊天记忆嵌入到系统提示词的 Advisor 类

字段说明

字段名称
类型
描述
order
int
指定顺序
defaultConversationId
String
默认会话Id
chatMemory
ChatMemory
聊天记忆接口
scheduler
Scheduler
流式处理时的线程调度
systemPromptTemplate
PromptTemplate
当前使用的系统提示模板

方法说明

方法名称
描述
before
1. 从ChatMemory中取出历史消息
2. 当前消息加入ChatMemory
3. 将历史消息结合系统提示消息作为最新的系统提示
after
将模型响应的消息加入ChatMemory
adviseStream
覆盖了BaseAdvisor默认实现逻辑
- 注:在将多个流式响应合并成一个完整响应对象后,在调用after,确保只保留完整的模型输出,避免部分信息写入memory导致混乱
package org.springframework.ai.chat.client.advisor;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClientMessageAggregator;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.client.advisor.api.BaseChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.MessageType;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;public final class PromptChatMemoryAdvisor implements BaseChatMemoryAdvisor {private static final Logger logger = LoggerFactory.getLogger(PromptChatMemoryAdvisor.class);private static final PromptTemplate DEFAULTSYSTEMPROMPTTEMPLATE = new PromptTemplate("{instructions}\n\nUse the conversation memory from the MEMORY section to provide accurate answers.\n\n---------------------\nMEMORY:\n{memory}\n---------------------\n\n");private final PromptTemplate systemPromptTemplate;private final String defaultConversationId;private final int order;private final Scheduler scheduler;private final ChatMemory chatMemory;private PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int order, Scheduler scheduler, PromptTemplate systemPromptTemplate) {Assert.notNull(chatMemory, "chatMemory cannot be null");Assert.hasText(defaultConversationId, "defaultConversationId cannot be null or empty");Assert.notNull(scheduler, "scheduler cannot be null");Assert.notNull(systemPromptTemplate, "systemPromptTemplate cannot be null");this.chatMemory = chatMemory;this.defaultConversationId = defaultConversationId;this.order = order;this.scheduler = scheduler;this.systemPromptTemplate = systemPromptTemplate;}public static Builder builder(ChatMemory chatMemory) {return new Builder(chatMemory);}public int getOrder() {return this.order;}public Scheduler getScheduler() {return this.scheduler;}public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {String conversationId = this.getConversationId(chatClientRequest.context(), this.defaultConversationId);List<Message> memoryMessages = this.chatMemory.get(conversationId);logger.debug("[PromptChatMemoryAdvisor.before] Memory before processing for conversationId={}: {}", conversationId, memoryMessages);String memory = (String)memoryMessages.stream().filter((m) -> m.getMessageType() == MessageType.USER || m.getMessageType() == MessageType.ASSISTANT).map((m) -> {String var10000 = String.valueOf(m.getMessageType());return var10000 + ":" + m.getText();}).collect(Collectors.joining(System.lineSeparator()));SystemMessage systemMessage = chatClientRequest.prompt().getSystemMessage();String augmentedSystemText = this.systemPromptTemplate.render(Map.of("instructions", systemMessage.getText(), "memory", memory));ChatClientRequest processedChatClientRequest = chatClientRequest.mutate().prompt(chatClientRequest.prompt().augmentSystemMessage(augmentedSystemText)).build();UserMessage userMessage = processedChatClientRequest.prompt().getUserMessage();this.chatMemory.add(conversationId, userMessage);return processedChatClientRequest;}public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {List<Message> assistantMessages = new ArrayList();if (chatClientResponse.chatResponse() != null) {assistantMessages = chatClientResponse.chatResponse().getResults().stream().map((g) -> g.getOutput()).toList();} else if (chatClientResponse.chatResponse() != null && chatClientResponse.chatResponse().getResult() != null && chatClientResponse.chatResponse().getResult().getOutput() != null) {assistantMessages = List.of(chatClientResponse.chatResponse().getResult().getOutput());}if (!assistantMessages.isEmpty()) {this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId), assistantMessages);logger.debug("[PromptChatMemoryAdvisor.after] Added ASSISTANT messages to memory for conversationId={}: {}", this.getConversationId(chatClientResponse.context(), this.defaultConversationId), assistantMessages);List<Message> memoryMessages = this.chatMemory.get(this.getConversationId(chatClientResponse.context(), this.defaultConversationId));logger.debug("[PromptChatMemoryAdvisor.after] Memory after ASSISTANT add for conversationId={}: {}", this.getConversationId(chatClientResponse.context(), this.defaultConversationId), memoryMessages);}return chatClientResponse;}public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {Scheduler scheduler = this.getScheduler();Mono var10000 = Mono.just(chatClientRequest).publishOn(scheduler).map((request) -> this.before(request, streamAdvisorChain));Objects.requireNonNull(streamAdvisorChain);return var10000.flatMapMany(streamAdvisorChain::nextStream).transform((flux) -> (new ChatClientMessageAggregator()).aggregateChatClientResponse(flux, (response) -> this.after(response, streamAdvisorChain)));}public static final class Builder {private PromptTemplate systemPromptTemplate;private String conversationId;private int order;private Scheduler scheduler;private ChatMemory chatMemory;private Builder(ChatMemory chatMemory) {this.systemPromptTemplate = PromptChatMemoryAdvisor.DEFAULTSYSTEMPROMPTTEMPLATE;this.conversationId = "default";this.order = -2147482648;this.scheduler = BaseAdvisor.DEFAULTSCHEDULER;this.chatMemory = chatMemory;}public Builder systemPromptTemplate(PromptTemplate systemPromptTemplate) {this.systemPromptTemplate = systemPromptTemplate;return this;}public Builder conversationId(String conversationId) {this.conversationId = conversationId;return this;}public Builder scheduler(Scheduler scheduler) {this.scheduler = scheduler;return this;}public Builder order(int order) {this.order = order;return this;}public PromptChatMemoryAdvisor build() {return new PromptChatMemoryAdvisor(this.chatMemory, this.conversationId, this.order, this.scheduler, this.systemPromptTemplate);}}
}

ChatMemory

类说明:管理会话聊天记忆的接口,提供了保存、获取、清除对话消息的基本功能

字段说明

字段名称
类型
描述
CONVERSATIONID
String
会话Id。当作键,方便提取对应的List

方法说明

方法名称
描述
add
添加消息到指定会话Id中
get
根据指定会话Id获取消息
clear
根据会话Id清除消息
package org.springframework.ai.chat.memory;import java.util.List;
import org.springframework.ai.chat.messages.Message;
import org.springframework.util.Assert;public interface ChatMemory {String DEFAULTCONVERSATIONID = "default";String CONVERSATIONID = "chatmemoryconversationid";default void add(String conversationId, Message message) {Assert.hasText(conversationId, "conversationId cannot be null or empty");Assert.notNull(message, "message cannot be null");this.add(conversationId, List.of(message));}void add(String conversationId, List<Message> messages);List<Message> get(String conversationId);void clear(String conversationId);
}
MessageWindowChatMemory

类说明:消息窗口类,提供了保存、获取、清除对话消息的基本功能

字段说明

字段名称
类型
描述
maxMessages
int
当前会话最多保留的消息数量
chatMemoryRepository
ChatMemoryRepository
存储后端,实现该接口可拓展内存、Mysql、Redis、ES等数据库存储消息

其他方法说明

方法名称
描述
process
用于控制消息数量,核心逻辑下
1. 新增 SystemMessage 时,清除之前的 SystemMessage
2. 若消息数超过限制,优先保留SystemMessage
package org.springframework.ai.chat.memory;import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.util.Assert;public final class MessageWindowChatMemory implements ChatMemory {private static final int DEFAULTMAXMESSAGES = 20;private final ChatMemoryRepository chatMemoryRepository;private final int maxMessages;private MessageWindowChatMemory(ChatMemoryRepository chatMemoryRepository, int maxMessages) {Assert.notNull(chatMemoryRepository, "chatMemoryRepository cannot be null");Assert.isTrue(maxMessages > 0, "maxMessages must be greater than 0");this.chatMemoryRepository = chatMemoryRepository;this.maxMessages = maxMessages;}public void add(String conversationId, List<Message> messages) {Assert.hasText(conversationId, "conversationId cannot be null or empty");Assert.notNull(messages, "messages cannot be null");Assert.noNullElements(messages, "messages cannot contain null elements");List<Message> memoryMessages = this.chatMemoryRepository.findByConversationId(conversationId);List<Message> processedMessages = this.process(memoryMessages, messages);this.chatMemoryRepository.saveAll(conversationId, processedMessages);}public List<Message> get(String conversationId) {Assert.hasText(conversationId, "conversationId cannot be null or empty");return this.chatMemoryRepository.findByConversationId(conversationId);}public void clear(String conversationId) {Assert.hasText(conversationId, "conversationId cannot be null or empty");this.chatMemoryRepository.deleteByConversationId(conversationId);}private List<Message> process(List<Message> memoryMessages, List<Message> newMessages) {List<Message> processedMessages = new ArrayList();Set<Message> memoryMessagesSet = new HashSet(memoryMessages);Stream var10000 = newMessages.stream();Objects.requireNonNull(SystemMessage.class);boolean hasNewSystemMessage = var10000.filter(SystemMessage.class::isInstance).anyMatch((messagex) -> !memoryMessagesSet.contains(messagex));var10000 = memoryMessages.stream().filter((messagex) -> !hasNewSystemMessage || !(messagex instanceof SystemMessage));Objects.requireNonNull(processedMessages);var10000.forEach(processedMessages::add);processedMessages.addAll(newMessages);if (processedMessages.size() <= this.maxMessages) {return processedMessages;} else {int messagesToRemove = processedMessages.size() - this.maxMessages;List<Message> trimmedMessages = new ArrayList();int removed = 0;for(Message message : processedMessages) {if (!(message instanceof SystemMessage) && removed < messagesToRemove) {++removed;} else {trimmedMessages.add(message);}}return trimmedMessages;}}public static Builder builder() {return new Builder();}public static final class Builder {private ChatMemoryRepository chatMemoryRepository;private int maxMessages = 20;private Builder() {}public Builder chatMemoryRepository(ChatMemoryRepository chatMemoryRepository) {this.chatMemoryRepository = chatMemoryRepository;return this;}public Builder maxMessages(int maxMessages) {this.maxMessages = maxMessages;return this;}public MessageWindowChatMemory build() {if (this.chatMemoryRepository == null) {this.chatMemoryRepository = new InMemoryChatMemoryRepository();}return new MessageWindowChatMemory(this.chatMemoryRepository, this.maxMessages);}}
}

ChatMemoryRepository

package org.springframework.ai.chat.memory;import java.util.List;
import org.springframework.ai.chat.messages.Message;public interface ChatMemoryRepository {List<String> findConversationIds();List<Message> findByConversationId(String conversationId);void saveAll(String conversationId, List<Message> messages);void deleteByConversationId(String conversationId);
}
InMemoryChatMemoryRepository

类说明:基于内存的实际存储数据,维护一个会话 Id 到消息列表的键值对

package org.springframework.ai.chat.memory;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.ai.chat.messages.Message;
import org.springframework.util.Assert;public final class InMemoryChatMemoryRepository implements ChatMemoryRepository {Map<String, List<Message>> chatMemoryStore = new ConcurrentHashMap();public List<String> findConversationIds() {return new ArrayList(this.chatMemoryStore.keySet());}public List<Message> findByConversationId(String conversationId) {Assert.hasText(conversationId, "conversationId cannot be null or empty");List<Message> messages = (List)this.chatMemoryStore.get(conversationId);return (List<Message>)(messages != null ? new ArrayList(messages) : List.of());}public void saveAll(String conversationId, List<Message> messages) {Assert.hasText(conversationId, "conversationId cannot be null or empty");Assert.notNull(messages, "messages cannot be null");Assert.noNullElements(messages, "messages cannot contain null elements");this.chatMemoryStore.put(conversationId, messages);}public void deleteByConversationId(String conversationId) {Assert.hasText(conversationId, "conversationId cannot be null or empty");this.chatMemoryStore.remove(conversationId);}
}

问题

MessageChatMemoryAdvisor 和 PromptChatMemoryAdvisor 的区别是什么?

PromptChatMemoryAdvisor

  1. 有些模型可能可能不支持 messag

    1. 如本地部署,LLaMA、BLOOM 等 text-in/text-out 模型
  2. 调试时,希望快速看到完整上下文

MessageChatMemoryAdvisor

  1. 需要精确控制消息类型(用户、系统、助手)
  2. 使用 OpenAI GPT-3.5/4 等 chat 模型

总结:MessageChatMemoryAdvisor 是面向结构化对话记忆的最佳实践,而 PromptChatMemoryAdvisor 是面向文本提示增强的经典方案

为什么需要覆盖 adviseStream 方法?

在将多个流式响应合并成一个完整响应对象后,在调用 after,确保只保留完整的模型输出,避免部分信息写入 memory 导致混乱

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

相关文章:

  • ProfiNet转Ethernet/IP网关选型策略适配西门子S7-1500与罗克韦尔ControlLogix5580的关键指标对比
  • 架构师论文《论软件可靠性模型的设计与实现》
  • 画思维导图的方法分享
  • 镭神N10P SLAM算法选型
  • 《进化陷阱》--AI 生成文章 《连载 2》
  • Java Lock使用
  • 安全运营与威胁对抗体系
  • 【分布式文件系统】FastDFS
  • 语音合成之十五 语音合成(TTS)分句生成拼接时的响度一致性问题:现状、成因与对策
  • 拉普拉斯算子过零点边缘检测原理以及抑制伪边缘的方法
  • 农业机械化、电气化和自动化知网英文普刊:1天录用,2周见刊发表!
  • 全链路解析:影刀RPA+Coze API自动化工作流实战指南
  • 静态时序分析与约束
  • Python 和 matplotlib 保存图像时,确保图像的分辨率和像素符合特定要求(如 64x64),批量保存 不溢出内存
  • 单机Kafka配置ssl并在springboot使用
  • 【android bluetooth 协议分析 02】【bluetooth hal 层详解 4】【高通蓝牙hal主要流程介绍-中】
  • 26考研|高等代数:线性变换
  • 纯虚函数必须在派生类中给出定义吗? 虚函数必须在派生类中给出定义吗?
  • Honeywell TK-PRS021 C200
  • Redis核心用法与通用命令全解析
  • Go语言中为什么map、slice、channel需要var之后还要make一下?
  • FTP Bounce Attack:原理、影响与防御
  • 如何安装和维护 Linux 系统?
  • 2025电工杯数学建模竞赛B题 城市垃圾分类运输的路径优化与调度 保姆级教程讲解|模型讲解
  • Missashe线代题型总结
  • 编译器ar命令参数
  • java中多线程的实现方式
  • 【算法篇】二分查找算法:基础篇
  • AES加密模式详解及OpenSSL C库函数指南
  • 【SSL部署与优化​】​​OCSP Stapling配置指南:减少证书验证延迟​​