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

SpringAI中的模块化链式Advisor调用(源码学习)

文章目录

  • 前言
  • 正文
    • 1.1 项目结构
    • 1.2 项目环境
    • 1.3 完整代码
      • 1.3.1 CallAdvisor
      • 1.3.2 Ordered
      • 1.3.3 CallAdvisorChain
      • 1.3.4 ReqSpec
      • 1.3.5 RespSpec
      • 1.3.6 AdvisorClient
      • 1.3.7 DefaultCallAdvisorChain
      • 1.3.8 DefaultAdvisorClient
      • 1.3.9 日志增强器:SimpleLoggerAdvisor
      • 1.3.10 默认调用增强器:DefaultCallAdvisor
    • 1.4 测试执行
      • 1.4.1 执行流程
      • 1.4.2 测试代码
      • 1.4.3 控制台输出

前言

在进行了Spring AI 的对话模型的实践(见文章:SpringAI 使用通义千问进行聊天对话开发)之后,我发现 Spring AI 框架中的 Advisor 的链式调用挺有意思,而且可能应用到比较复杂的业务场景中。

在这里插入图片描述
通过之前的AI 对话,我最终发现,调用链路中,最后一环拥有最低的优先级,也就是对话模型真正执行的advisor。基于此,在调用链路中模块化的增加一些 advisor 来处理对应的业务,或调整请求参数做一些业务逻辑。

但是Spring AI 本身的接口和类定义比较固定,都是基于对话的。我进行了简化,并且使用泛型来做通用化。可以包装任意请求和响应,包括最后一环使用 Function 接口进行执行后置。

完整代码在代码仓库:https://gitee.com/fengsoshuai/song-tools/tree/dev/advisor-tool

具体的就请看本文正文!!

正文

1.1 项目结构

在这里插入图片描述

  • CallAdvisor :调用增强器,所有advisor的顶层接口。提供调用方法、排序方式,order 值越小,表示优先级越高,越先执行。
  • CallAdvisorChain: 调用增强器链,提供获取下一个advisor,并执行该advisor的方法。
  • ReqSpec:请求规则,提供构建 advisor,请求参数的方法,支持lambda调用。可以组装响应规则。
  • RespSpec:响应规则,返回响应内容。
  • AdvisorClient:增强器客户端,提供获取 ReqSpec 的方法。
  • DefaultCallAdvisor:默认调用增强器,调用链的最后会调用。一般用于实际业务的最后一步请求处理。比如http请求,或请求数据库。

1.2 项目环境

项目环境基于 java 17,在maven 中进行如下配置:

<properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties>

不使用用其他任何依赖,包括 lombok!! 方便被其他模块使用,真实项目中如果有业务场景,建议将advisor作为一个jar包,在其他模块引入。

1.3 完整代码

1.3.1 CallAdvisor

package com.song.tools.advisor;/*** 调用增强器** @author pine* @version v1.0* @since 2025-08-16 09:47*/
public interface CallAdvisor<Req, Resp> extends Ordered {/*** 调用增强** @param req       请求参数* @param chain     调用链* @return 响应结果*/Resp adviseCall(Req req, CallAdvisorChain<Req, Resp> chain);/*** 默认排序*/int DEFAULT_ORDER = HIGHEST_PRECEDENCE + 1000;default String getName() {return getClass().getSimpleName();}
}

1.3.2 Ordered

package com.song.tools.advisor;/*** 排序接口:数值越小,优先级越高** @author pine* @version v1.0* @since 2025-08-16 09:45*/
public interface Ordered {int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;int LOWEST_PRECEDENCE = Integer.MAX_VALUE;int getOrder();
}

1.3.3 CallAdvisorChain

package com.song.tools.advisor;import java.util.List;/*** 增强器调用链** @author pine* @version v1.0* @since 2025-08-16 09:49*/
public interface CallAdvisorChain<Req, Resp> {/*** 获取下一个增强器,并执行** @param req 请求参数* @return 响应参数*/Resp nextCall(Req req);/*** 获取所有增强器** @return 所有增强器*/List<CallAdvisor<Req, Resp>> getCallAdvisors();
}

1.3.4 ReqSpec

package com.song.tools.advisor;import java.util.List;/*** 增强器req规则** @author pine* @version v1.0* @since 2025-08-16 09:41*/
public interface ReqSpec<Req, Resp> {ReqSpec<Req, Resp> advisor(CallAdvisor<Req, Resp> advisor);ReqSpec<Req, Resp> advisors(List<CallAdvisor<Req, Resp>> advisors);/*** 请求参数:构建请求参数** @param req 请求参数* @return 增强器req规则*/ReqSpec<Req, Resp> requestParam(Req req);/*** 组装响应规则,不实际执行** @return 响应规则*/RespSpec<Req, Resp> call();
}

1.3.5 RespSpec

package com.song.tools.advisor;/*** 增强器resp规则** @author pine* @version v1.0* @since 2025-08-16 09:41*/
public interface RespSpec<Req, Resp> {/*** 获取响应结果的内容** @return 响应结果的内容*/String content();/*** 获取响应结果** @return 响应结果*/Resp responseEntity();/*** 获取响应结果为json** @return json*/default String json() {throw new UnsupportedOperationException();}
}

1.3.6 AdvisorClient

package com.song.tools.advisor;/*** 增强器客户端** @author pine* @version v1.0* @since 2025-08-16 09:59*/
public interface AdvisorClient<Req, Resp> {/*** 初始化:获取请求规则** @return 请求规则*/ReqSpec<Req, Resp> reqSpec(DefaultCallAdvisor<Req, Resp> defaultCallAdvisor);
}

1.3.7 DefaultCallAdvisorChain

使用优先级队列对链中的advisor进行排序。

package com.song.tools.advisor;import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.PriorityBlockingQueue;/*** 默认执行增强器链** @author pine* @version v1.0* @since 2025-08-16 10:07*/
public class DefaultCallAdvisorChain<Req, Resp> implements CallAdvisorChain<Req, Resp> {private final List<CallAdvisor<Req, Resp>> originalCallAdvisors;private final PriorityBlockingQueue<CallAdvisor<Req, Resp>> callAdvisors;private DefaultCallAdvisorChain(PriorityBlockingQueue<CallAdvisor<Req, Resp>> callAdvisors) {this.callAdvisors = callAdvisors;this.originalCallAdvisors = List.copyOf(callAdvisors);}/*** 获取下一个增强器,并执行** @param req 请求参数* @return 响应参数*/@Overridepublic Resp nextCall(Req req) {Objects.requireNonNull(req);if (this.callAdvisors.isEmpty()) {throw new IllegalStateException("No CallAdvisors available to execute");}// 获取下一个增强器,并从队列中移除CallAdvisor<Req, Resp> currentAdvisor = callAdvisors.poll();// 执行增强器return currentAdvisor.adviseCall(req, this);}/*** 获取所有增强器** @return 所有增强器*/@Overridepublic List<CallAdvisor<Req, Resp>> getCallAdvisors() {return originalCallAdvisors;}public static class Builder<Req, Resp> {private final PriorityBlockingQueue<CallAdvisor<Req, Resp>> callAdvisors;public Builder() {this.callAdvisors = new PriorityBlockingQueue<>(20, Comparator.comparingInt(Ordered::getOrder));}public Builder<Req, Resp> addAdvisor(CallAdvisor<Req, Resp> advisor) {return addAdvisors(List.of(advisor));}public Builder<Req, Resp> addAdvisors(List<CallAdvisor<Req, Resp>> advisors) {this.callAdvisors.addAll(advisors);return this;}public DefaultCallAdvisorChain<Req, Resp> build() {return new DefaultCallAdvisorChain<>(callAdvisors);}}
}

1.3.8 DefaultAdvisorClient

定义内部静态类请求规则和响应规则的实现:DefaultReqSpec、DefaultRespSpec 。

package com.song.tools.advisor;import java.util.ArrayList;
import java.util.List;
import java.util.Objects;/*** 默认增强器客户端** @author pine* @version v1.0* @since 2025-08-16 10:20*/
public class DefaultAdvisorClient<Req, Resp> implements AdvisorClient<Req, Resp> {/*** 初始化:获取请求规则** @return 请求规则*/@Overridepublic ReqSpec<Req, Resp> reqSpec(DefaultCallAdvisor<Req, Resp> defaultCallAdvisor) {return new DefaultReqSpec<>(defaultCallAdvisor);}/*** 默认请求规则** @param <Req>  请求参数类型* @param <Resp> 响应参数类型*/public static class DefaultReqSpec<Req, Resp> implements ReqSpec<Req, Resp> {private final List<CallAdvisor<Req, Resp>> advisors = new ArrayList<>();private Req req;public DefaultReqSpec(DefaultCallAdvisor<Req, Resp> defaultCallAdvisor) {Objects.requireNonNull(defaultCallAdvisor);this.advisors.add(defaultCallAdvisor);}@Overridepublic ReqSpec<Req, Resp> advisor(CallAdvisor<Req, Resp> advisor) {Objects.requireNonNull(advisors);this.advisors.add(advisor);return this;}@Overridepublic ReqSpec<Req, Resp> advisors(List<CallAdvisor<Req, Resp>> advisors) {Objects.requireNonNull(advisors);this.advisors.addAll(advisors);return this;}@Overridepublic ReqSpec<Req, Resp> requestParam(Req req) {this.req = req;return this;}/*** 组装响应规则** @return 响应规则*/@Overridepublic RespSpec<Req, Resp> call() {Objects.requireNonNull(req);DefaultCallAdvisorChain<Req, Resp> advisorChain = buildAdvisorChain();return new DefaultRespSpec<>(req, advisorChain);}private DefaultCallAdvisorChain<Req, Resp> buildAdvisorChain() {return new DefaultCallAdvisorChain.Builder<Req, Resp>().addAdvisors(advisors).build();}}/*** 默认响应规则** @param <Req>  请求参数类型* @param <Resp> 响应参数类型*/public static class DefaultRespSpec<Req, Resp> implements RespSpec<Req, Resp> {private final CallAdvisorChain<Req, Resp> advisorChain;private final Req req;public DefaultRespSpec(Req req, CallAdvisorChain<Req, Resp> advisorChain) {Objects.requireNonNull(req);Objects.requireNonNull(advisorChain);this.req = req;this.advisorChain = advisorChain;}@Overridepublic String content() {return String.valueOf(advisorChain.nextCall(req));}@Overridepublic Resp responseEntity() {return advisorChain.nextCall(req);}}
}

1.3.9 日志增强器:SimpleLoggerAdvisor

增强器的一种实现,可以选择使用,会在调用链的起始和结束打印请求和响应结果。

package com.song.tools.advisor;/*** 简单日志增强器** @author pine* @version v1.0* @since 2025-08-16 09:53*/
public class SimpleLoggerAdvisor<Req, Resp> implements CallAdvisor<Req, Resp> {@Overridepublic Resp adviseCall(Req req, CallAdvisorChain<Req, Resp> chain) {System.out.println("SimpleLoggerAdvisor=========");logRequest(req);// 继续调用下一个增强器Resp resp = chain.nextCall(req);logResponse(resp);return resp;}/*** 打印请求** @param req 请求*/private void logRequest(Req req) {System.out.println("SimpleLoggerAdvisor.logRequest=========" + req);}/*** 打印响应** @param resp 响应*/private void logResponse(Resp resp) {System.out.println("SimpleLoggerAdvisor.logResponse=========" + resp);}@Overridepublic int getOrder() {return DEFAULT_ORDER;}
}

1.3.10 默认调用增强器:DefaultCallAdvisor

最终需要执行的业务,作为调用链的最后一个增强器,它会实际处理业务,并生成响应。比如实际调用http请求的逻辑就可以用它。

package com.song.tools.advisor;import java.util.Objects;
import java.util.function.Function;/*** 默认执行增强器:作为调用链的最后一个** @author pine* @version v1.0* @since 2025-08-16 10:58*/
public class DefaultCallAdvisor<Req, Resp> implements CallAdvisor<Req, Resp> {/*** 调用函数:由调用方指定,作为业务逻辑。需要生成响应结果。*/private final Function<Req, Resp> callFunction;public DefaultCallAdvisor(Function<Req, Resp> callFunction) {Objects.requireNonNull(callFunction);this.callFunction = callFunction;}@Overridepublic Resp adviseCall(Req req, CallAdvisorChain<Req, Resp> chain) {System.out.println("DefaultCallAdvisor=========");return callFunction.apply(req);}@Overridepublic int getOrder() {return LOWEST_PRECEDENCE;}
}

1.4 测试执行

如果你想增强某方面的业务逻辑,又不方便调整业务逻辑本身,可以实现自己的 CallAdvisor,然后设置其优先级。构建到执行链路中即可。

注意DefaultCallAdvisor必须只设置一个,不设置多次!而且必须要有!

1.4.1 执行流程

组装如下的逻辑:

  1. 定义业务逻辑,Function<Req, Resp>
  2. 定义请求参数
  3. 创建增强器客户端
  4. 客户端获取请求规则,请求规则装载多个advisor,请求规则装载请求参数
  5. 请求规则构建响应规则
  6. 响应规则执行请求,并组装响应结果

1.4.2 测试代码

package com.song.tools.advisor.test;import com.song.tools.advisor.AdvisorClient;
import com.song.tools.advisor.DefaultAdvisorClient;
import com.song.tools.advisor.DefaultCallAdvisor;
import com.song.tools.advisor.SimpleLoggerAdvisor;import java.util.List;
import java.util.function.Function;/*** 测试** @author pine* @version v1.0* @since 2025-08-16 10:18*/
public class Demo {public static void main(String[] args) {// 业务逻辑本身:后续如果用spring,可以是注入某个service的方法Function<UserReq, UserResp> callFunction = req -> {UserResp userResp = new UserResp();userResp.name = req.name + "涨了10岁";userResp.age = req.age + 10;return userResp;};// 请求参数UserReq userReq = new UserReq();userReq.name = "张三";userReq.age = 18;AdvisorClient<UserReq, UserResp> client = new DefaultAdvisorClient<>();UserResp userResp = client.reqSpec(new DefaultCallAdvisor<>(callFunction)).advisors(List.of(new SimpleLoggerAdvisor<>())).requestParam(userReq).call().responseEntity();System.out.println(userResp.name);System.out.println(userResp.age);}public static class UserReq {public String name;public Integer age;}public static class UserResp {public String name;public Integer age;}
}

1.4.3 控制台输出

SimpleLoggerAdvisor=========
SimpleLoggerAdvisor.logRequest=========com.song.tools.advisor.test.Demo$UserReq@1e80bfe8
DefaultCallAdvisor=========
SimpleLoggerAdvisor.logResponse=========com.song.tools.advisor.test.Demo$UserResp@cc34f4d
张三涨了10岁
28

可以观察到日志增强器在链路的起始和结束执行了,并打印了请求和响应。
其次DefaultCallAdvisor在链路的最后执行,Function<UserReq, UserResp> callFunction 作为业务逻辑,会实际执行。

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

相关文章:

  • B3865 [GESP202309 二级] 小杨的 X 字矩阵(举一反三)
  • Linux 多线程:线程回收策略 线程间通信(互斥锁详解)
  • linux下程序运行一段时间无端崩溃/被杀死,或者内存占用一直增大。linux的坑
  • Docker in Test:用一次性的真实环境,终结“测试永远跑不通”魔咒
  • 集成运算放大器(反向比例,同相比例)
  • C++实战
  • 静态库和动态库
  • 【leetcode】5 最长回文子串 动态规划法
  • Protues使用说明及Protues与Keil联合仿真实现点亮小灯和流水灯
  • 【Docker项目实战】使用Docker部署Notepad轻量级记事本
  • 【基础-判断】HarmonyOS提供了基础的应用加固安全能力,包括混淆、加密和代码签名能力
  • 数据结构 实现循环队列的三种方法
  • 如何在 MacOS 上安装 SQL Server
  • 搭建ktg-mes
  • 新手向:Python列表、元组、集合和字典的用法对比
  • MySQL的三大范式:
  • AI云电脑盒子技术分析——从“盒子”到“算力云边缘节点”的跃迁
  • 实现Android图片手势缩放功能的完整自定义View方案,结合了多种手势交互功能
  • Vue 3.5重磅更新:响应式Props解构,让组件开发更简洁高效
  • MQ积压如何处理
  • Markdown 生成 Gantt 甘特图
  • 使用js完成抽奖项目 效果和内容自定义,可以模仿游戏抽奖页面
  • 31 HTB Union 机器 - 中等难度
  • C:\Windows\WinSxS 目录详解
  • 【C++】标准库中用于组合多个值的数据结构pair、tuple、array...
  • AI搜索引擎下的内容优化新范式:GEO的关键技术解析
  • 二十七、动态SQL
  • RK3568 NPU RKNN(三):RKNN-ToolKit2模型构建与推理
  • 大模型教机器人叠衣服:2025年”语言理解+多模态融合“的智能新篇
  • PowerPoint和WPS演示放映PPT时如何禁止鼠标翻页