原文链接:SpringAI的Advisor:快速上手+源码解读
教程说明
说明:本教程将采用2025年5月20日正式的GA版,给出如下内容
- 核心功能模块的快速上手教程
- 核心功能模块的源码级解读
- Spring ai alibaba增强的快速上手教程 + 源码级解读
版本:JDK21 + SpringBoot3.4.5 + SpringAI 1.0.0 + SpringAI Alibaba最新
将陆续完成如下章节教程

快速上手
实战代码可见:https://github.com/GTyingzi/spring-ai-tutorial 下的 advisor
pom 文件
xml
<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
yaml
server:
port: 8080
spring:
application:
name: advisor-base
ai:
openai:
api-key: ${DASHSCOPEAPIKEY}
base-url: https://dashscope.aliyuncs.com/compatible-mode
chat:
options:
model: qwen-max
controller
MemoryMessageAdvisorController
java
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
java
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 版)
- 基础只提供 BaseChatMemoryAdvisor
- rag 模块引入 RAG,源码可参照 Rag 源码篇
架构图

Advisor
advisor 基础信息配置
- name:指定名字,确保唯一性
- order:数值越小,执行越靠前
java
package org.springframework.ai.chat.client.advisor.api;
import org.springframework.core.Ordered;
public interface Advisor extends Ordered {
int DEFAULTCHATMEMORYPRECEDENCEORDER = -2147482648;
String getName();
}
java
package org.springframework.core;
public interface Ordered {
int HIGHESTPRECEDENCE = Integer.MINVALUE;
int LOWESTPRECEDENCE = Integer.MAXVALUE;
int getOrder();
}
CallAdvisor
call 调用,跟 AI 模型交互前、后的一些逻辑
java
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 模型交互前、后的一些逻
java
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模型响应后的逻辑,需要子类实现 |
java
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,若不存在则使用默认值
java
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导致混乱 |
java
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导致混乱 |
java
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清除消息 |
java
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 |
java
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
java
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 到消息列表的键值对
java
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
-
有些模型可能可能不支持 messag
- 如本地部署,LLaMA、BLOOM 等 text-in/text-out 模型
-
调试时,希望快速看到完整上下文
MessageChatMemoryAdvisor
- 需要精确控制消息类型(用户、系统、助手)
- 使用 OpenAI GPT-3.5/4 等 chat 模型
总结:MessageChatMemoryAdvisor 是面向结构化对话记忆的最佳实践,而 PromptChatMemoryAdvisor 是面向文本提示增强的经典方案
为什么需要覆盖 adviseStream 方法?
在将多个流式响应合并成一个完整响应对象后,在调用 after,确保只保留完整的模型输出,避免部分信息写入 memory 导致混乱