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 执行流程
组装如下的逻辑:
- 定义业务逻辑,Function<Req, Resp>
- 定义请求参数
- 创建增强器客户端
- 客户端获取请求规则,请求规则装载多个advisor,请求规则装载请求参数
- 请求规则构建响应规则
- 响应规则执行请求,并组装响应结果
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
作为业务逻辑,会实际执行。