1.ChatMemoryRepository 接⼝

ChatMemoryRepository 接⼝是对话记忆存储的抽象。⽀持多种存储⽅式,例如:内存⽅式、 JDBC ⽅式以及 Redis ⽅式等,每种实现⽅式都有特定的实现类。⽐如内存⽅式就是通过 InMemoryChatMemoryRepository 类实现的。

源码:

public interface ChatMemoryRepository {
// 获取所有活跃会话ID列表
List<String> findConversationIds();
 // 根据会话ID获取消息列表
List<Message> findByConversationId(String conversationId);
 // 根据会话ID全量替换存储,清除旧消息,录⼊新消息
void saveAll(String conversationId, List<Message> messages);
 // 根据会话ID清除消息列表
void deleteByConversationId(String conversationId);
}

2.Advisor机制(拦截器)(面试可能会问到)

SpringAI Advisors SpringAI 框架中⽤于拦截和增强 AI 交互的核⼼组件,其设计灵感类似
WebFilter ,通过链式调⽤实现对请求和响应的处理。

2.1什么是Advisor

SpringAI Advisors 是连接 AI 模型与业务逻辑的核⼼中间件,其设计理念与 SpringAOP 深度契
合。她提供了⼀种灵活⽽强⼤的⽅法来拦截、修改和增强 Spring 应⽤程序中的 AI 驱动的交互。
通过利⽤ Advisors API ,开发⼈员可以创建更复杂、可重⽤和可维护的 AI 组件。例如:我们可
能会创建聊天记录、排除敏感词或为每个请求添加额外的上下⽂。
例如:排除敏感词,对敏感违法的词,数据进行拦截

2.2大致流程

  1. 我们将提示词发送给 AI ⼤模型。
  2. ⼤模型附加的 Advisor ,会依次执⾏链中每个 Advisor before ⽅法
  3. 执⾏完所有的 before ⽅法后,交给⼤模型处理。
  4. ⼤模型处理后的响应,会依次执⾏链中每个 Advisor 的 after ⽅法
  5.  执⾏完所有的 after ⽅法后,我们获得了最终的⼤模型响应内容,执⾏结束

2.3核心功能

1. 请求和响应拦截
通过 AroundAdvisor 接⼝动态修改聊天请求和响应,⽀持⽇志记录、内容转换等场景。处理
流程遵循责任链模式,请求按顺序通过所有 Advisor ,响应则逆序返回。
2. 上下⽂共享
通过 AdvisorContext (会被所有的Advisor拦截器所共享的资源,例如在第一个Advisor中进行了一个操作,第二个Advisor中可以拿到第一个Advisor所做的操作的信息,会有一个共享容器Advisor 链中传递数据,实现跨拦截器的状态共享。
3. 多模型兼容性
封装通⽤ AI 模式、例如如记忆管理、⽇志记录等,确保代码可复⽤且兼容不同⼤模型。

2.4基本架构

可以看到Advisor接口有两个子接口,分别是CallAdvisor和StreamAdvisor。

2.4.1Advisor接口

Advisor 接⼝是 advisor 体系中的顶层接⼝,它继承了 Ordered (只和顺序有关),⽤于⽅便制定
Advisor 链的执⾏顺序。
public interface Advisor extends Ordered {
    int DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER = -2147482648;
    String getName();
}

interface中自动加public static finial

该接⼝的主要作⽤就是⽤来声明当前的类是 Advisor 增强类,并可以声明⾃⼰的 advisor 名称。如果有多个 advisor 类,这些类按照其 getOrder() 值来排序。⾸先执⾏较低的值。⾃动添加的最后⼀个 advisor 将请求发送到⼤模型中。
public interface Ordered {
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;    
    int getOrder();
}
注意:如果多个 advisor getOrder() 返回的值相同,则不能保证执⾏顺序。

2.4.2CallAdvisor StreamAdvisor ⼦接⼝

CallAdvisor 接⼝继承于 Advisor 接⼝,提供了⼀个环绕通知 adviseCall() ⽅法,可以在⽬标⽅法执⾏ 之前和执 之后都会执⾏逻辑,完全控制⽬标⽅法的执⾏流程。
public interface CallAdvisor extends Advisor {
ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, 
CallAdvisorChain callAdvisorChain);
}

StreamAdvisor 接⼝同样继承于 Advisor 接⼝,同样提供了⼀个环绕通知adviseStream() ⽅法,同样在⽬标⽅法前后执⾏逻辑,控制执⾏流程。

public interface StreamAdvisor extends Advisor {
    Flux<ChatClientResponse> adviseStream(ChatClientRequest
            chatClientRequest, 
            StreamAdvisorChain
            streamAdvisorChain);
}
CallAdvisor 接⼝与 StreamAdvisor 接⼝不同的是: CallAdvisor 接⼝是同步的请求和
响应,⽽ StreamAdvisor 接⼝是流式请求和流失响应,通过返回 Flux<> 来增强流中的数
据操作。

2.4.3CallAdvisorChain 接⼝和 StreamAdvisorChain 接⼝

CallAdvisorChain SpringAI 框架中⽤于链式调⽤多个 Advisor 的核⼼组件,实现请
求的拦截与增强功能。
public interface CallAdvisorChain extends AdvisorChain {
        ChatClientResponse nextCall(ChatClientRequest chatClientRequest);
        List<CallAdvisor> getCallAdvisors();
}
StreamAdvisorChain SpringAI 框架中⽤于处理流式请求的链式拦截组件,其核⼼功能
是通过链式调⽤多个 StreamAdvisor 处理流式交互。
public interface StreamAdvisorChain extends AdvisorChain {
        Flux<ChatClientResponse> nextStream(ChatClientRequest
        chatClientRequest);
        List<StreamAdvisor> getStreamAdvisors();
}

流程:

2.4.4BaseAdvisor 接⼝

BaseAdvisor 接⼝同时实现同步拦截(CallAdvisor)和流式拦截(StreamAdvisor )。BaseAdvisor 接⼝,这个接⼝同时继承了 CallAdvisor 和 StreamAdvisor 接⼝,同时提供了同步和流式的链式默认实现。在默认实现的环绕通知⽅法 中,前置和后置⽅法调⽤ before after ⽅法⽤于使⽤者⾃定义。因此我们在需要时仅仅 定义我们的 before after 即可。

3自定义Advisor

要⾃定义⼀个 Advisor ,其实很简单。只需遵循以下步骤:
  • 创建⼀个,实现 CallAroundAdvisor StreamAroundAdvisor 接⼝。
  • 实现接⼝的 aroundCall()|aroundStream() getName() 以及 getOrder() ⽅法。
  • 调⽤⼤模型时,将自定义的 advisor 添加进去即可。

入门案例:计算大模型的耗时

config包

package com.jiazhong.mingxing.ai.advisor.glm.config;

import com.jiazhong.mingxing.ai.advisor.glm.advisor.TimerAdvisor;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatClientConfig {
    @Resource
    private OpenAiChatModel openAiChatModel;
    @Resource
    private TimerAdvisor timerAdvisor;
    @Bean("openAiChatClient")
    public ChatClient openAiChatClient(){
       return ChatClient.builder(openAiChatModel)
               //3.进行全局注册
               .defaultAdvisors(timerAdvisor)
               .build();
    }
}

advisor包

package com.jiazhong.mingxing.ai.advisor.glm.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.stereotype.Component;
@Slf4j
@Component //将一个普通的 Java 类标记为 Spring 容器管理的 Bean
public class TimerAdvisor implements CallAdvisor {
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        //1.获取进来的时候的时间
        long begin = System.currentTimeMillis();
        log.info("开始时间:{}",begin);
        //2.执行后面的操作(包含下一个Advisor(可能多个)以及大模型)
        ChatClientResponse chatClientResponse = chain.nextCall(request);
        //4.获取到出去的时候的时间
        long end = System.currentTimeMillis();
        log.info("结束时间:{}",end);
        //5.计算时间
        long time = end - begin;
        log.info("总共花费了:"+time+"毫秒");
        return chatClientResponse;
    }

    @Override
    //Advisor的名字
    public String getName() {
        return "timer";
    }

    @Override
    //Advisor的优先级,数字越小优先级越高
    public int getOrder() {
        return 0;
    }
}

controller包

package com.jiazhong.mingxing.ai.advisor.glm.controller;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
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;

@RestController
@RequestMapping("/advisor")
public class AdvisorController {
    @Resource
    private ChatClient openAiChatClient;
    @GetMapping(value = "/stream",produces = "text/html;charset=utf-8")
    public String call(@RequestParam("question") String question){
        return openAiChatClient.prompt()
                .user(question)
                .call().content();
    }
}

   AdvisorContext上下文

在TimerAdvisor类中进行添加

package com.jiazhong.mingxing.ai.advisor.glm.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.stereotype.Component;
@Slf4j
@Component //将一个普通的 Java 类标记为 Spring 容器管理的 Bean
public class TimerAdvisor implements CallAdvisor {
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        //1.获取进来的时候的时间
        long begin = System.currentTimeMillis();
        request.context().put("A","这个是我在上下文中存放的内容");
        log.info("开始时间:{}",begin);
        //2.执行后面的操作(包含下一个Advisor(可能多个)以及大模型)
        ChatClientResponse chatClientResponse = chain.nextCall(request);
        //4.获取到出去的时候的时间
        long end = System.currentTimeMillis();
        Object a = request.context().get("A");
        log.info("上下文存放的内容是:{}",a);
        log.info("结束时间:{}",end);
        //5.计算时间
        long time = end - begin;
        log.info("总共花费了:"+time+"毫秒");
        return chatClientResponse;
    }

    @Override
    //Advisor的名字
    public String getName() {
        return "timer";
    }

    @Override
    //Advisor的优先级,数字越小优先级越高
    public int getOrder() {
        return 0;
    }
}

新建SecondAdvisor类

package com.jiazhong.mingxing.ai.advisor.glm.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class SecondAdvisor implements CallAdvisor {
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
        Object a = chatClientRequest.context().get("A");
        log.info("secondAdvisor:{}",a);
        return callAdvisorChain.nextCall(chatClientRequest);
    }

    @Override
    public String getName() {
        return "second";
    }

    @Override
    public int getOrder() {
        return 3;
    }
}

修改Cinfig类

package com.jiazhong.mingxing.ai.advisor.glm.config;

import com.jiazhong.mingxing.ai.advisor.glm.advisor.SecondAdvisor;
import com.jiazhong.mingxing.ai.advisor.glm.advisor.TimerAdvisor;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatClientConfig {
    @Resource
    private OpenAiChatModel openAiChatModel;
    @Resource
    private TimerAdvisor timerAdvisor;
    @Resource
    private SecondAdvisor secondAdvisor;
    @Bean("openAiChatClient")
    public ChatClient openAiChatClient(){
       return ChatClient.builder(openAiChatModel)
               //3.进行全局注册
               .defaultAdvisors(timerAdvisor,secondAdvisor)
               .build();
    }
}

运行结果

说明各个Advisor是互通的,根据优先级确定执行顺序。

4内置的Advisor

4.1. MessageChatMemoryAdvisor

管理多轮会话上下⽂,使⽤ MessageChatMemoryAdvisor ,我们可以通过 messages 属性
提供聊天客户端调⽤的聊天历史记录。我们可以将所有消息保存在 ChatMemory 实现中,并
控制历史记录的⼤⼩。
我们多轮会话中不管是内存⽅式还是 JDBC ⽅式使⽤的就是这种效果:

4.2PromptChatMemoryAdvisor

PromptChatMemoryAdvisor MessageChatMemoryAdvisor 都能实现类似效果。不同的
PromptChatMemoryAdvisor 会将对话历史封装到系统提示词( System Prompt )中,兼容
不⽀持多轮上下⽂的⼤模型。
这⾥我们直接把 MessageChatMemoryAdvisor 替换成 PromptChatMemoryAdvisor 效果也
是⼀样的。
@Bean("ollamaChatClient")
public ChatClient chatClient() {
return ChatClient
 .builder(ollamaChatModel)
//.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) 
// 内存⽅式
//.defaultAdvisors(new 
MessageChatMemoryAdvisor(jdbcChatMemory)) // JDBC⽅式
 .defaultAdvisors(new
PromptChatMemoryAdvisor(jdbcChatMemory)) // 替换成PromptChatMemoryAdvisor
 .build();
}

4.3. SimpleLoggerAdvisor

SimpleLoggerAdvisor SpringAI 框架中⽤于⽇志记录的内置组件,主要⽤于拦截并记
录聊天请求与响应的详细信息,⽀持调试和监控应⽤程序运⾏状态。
核⼼功能
  • ⽇志记录:默认记录 Debug 级别的⽇志,包括⽤户输⼊⽂本、模型响应等内容,但默认不显示。
  • 可配置性:⽀持通过配置⽂件调整⽇志级别(如改为 Info 级别),或下载源码⾃定义逻辑。
项⽬搭配
  • 添加 SimpleLoggerAdvisor

  • 修改配置⽂件
  • 在application.yml文件中加入下述代码

  • 测试结果

4.44. SafeGuardAdvisor

Advisor 基于关键词或正则表达式过滤敏感内容,拦截⾮法请求。
在config类中

输入敏感词的结果如下图

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐