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 文件

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 版)

  1. 基础只提供 BaseChatMemoryAdvisor
  2. 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

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

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

MessageChatMemoryAdvisor

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

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

为什么需要覆盖 adviseStream 方法?

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

相关推荐
-曾牛11 天前
Spring AI 与 Hugging Face 深度集成:打造高效文本生成应用
java·人工智能·后端·spring·搜索引擎·springai·deepseek
代码蛀虫向品15 天前
SpringAI框架详解:功能、接口及支持的AI模型
人工智能·框架·ai模型·springai·编程简化
-曾牛19 天前
Spring AI聊天模型API:轻松构建智能聊天交互
java·人工智能·后端·spring·交互·api·springai
-曾牛20 天前
探索 Spring AI 的 ChatClient API:构建智能对话应用的利器
java·人工智能·spring boot·后端·spring·springai·ai指南
-曾牛21 天前
开启 Spring AI 之旅:从入门到实战
java·人工智能·spring·指南·教学·大模型应用·springai
续亮~1 个月前
基于SpringAI Alibaba实现RAG架构的深度解析与实践指南
java·人工智能·架构·ai编程·springai
续亮~1 个月前
基于Redis实现RAG架构的技术解析与实践指南
java·redis·架构·wpf·springai·文档检索
何似在人间5751 个月前
SpringAI+DeepSeek大模型应用开发——1 AI概述
java·人工智能·spring·springai
续亮~1 个月前
提示词 (Prompt)
java·人工智能·prompt·ai编程·springai