SpringAI - Advisor(二)
Advisor的核心实现
Advisor家族
核心族谱
下图是官方给出的
Advisor
类图官方的图因为版本的原因,类名上有些出入。下面是在IDEA生成的类图,一起结合看一下。
可以看到最上层接口是
Ordered
,前面也说到过是排序。排序规则是,数值越小优先级越高。它具备两个常量,相当于最高和最低优先级。1
2int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;Advisor
接口只有一个getName,核心还是CallAdvisor, StreamAdvisor
两个接口,所以核心方法也就是adviseCall
和adviseStream
两个方法了。然后就是
CallAdvisorChain
和StreamAdvisorChain
,从上图可以看出与CallAdvisor, StreamAdvisor
是一个对应的聚合关系,组成了Advisor的调用链。ChatClientRequest
就是请求,ChatClientRequest
可以获取到ChatCLient
调用AI的提示词、上下文等,还可以进行复制和异变操作,这些在后续会继续记录。最后是
ChatClientResponse
响应了。ChatClientResponse
在之前的“SpringAI - ChatClient(二)”的“更多响应类型”文中就记录过了。
前置&后置处理
这里有一点在上一篇
LogAdvisor
案例中没体现出来的,其实在调用callAdvisorChain.nextCall(chatClientRequest);
后可以再继续做一些处理。如下:1
2
3
4
5
6
7
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
log.info("\nadviseCall prompt -> {}", chatClientRequest.prompt().getContents());
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
log.info("\nadviseCall response text-> {}", chatClientResponse.chatResponse().getResult().getOutput().getText());
return chatClientResponse;
}将
adviseCall
中改造了一下,然后看看输出结果1
2
3
4
52025-06-27T12:19:28.058+08:00 INFO 82818 --- [spring-ai-example] [ main] c.s.ai.example.advisor.one.LogAdvisor :
adviseCall prompt -> 你是一个Java专家,请帮忙解答提出的Java相关问题。Java 之父的全称叫什么?
2025-06-27T12:19:36.455+08:00 INFO 82818 --- [spring-ai-example] [ main] c.s.ai.example.advisor.one.LogAdvisor :
adviseCall response text-> Java 之父的全称是 **詹姆斯·高斯林(James Gosling)**,通常被称为 **"Java 之父"(Father of Java)**。到这里就能看出来为什么官方的图中名称叫
xxxAroundAdvisor
了吧,那是不是更觉得有些眼熟了啊?
Advisor执行流程
执行流程
先看看官方给的执行流程图,由于版本的关系略有出入,参考帮助理解即可。
第一步先是封装
ChatClientRequest
,从代码中看到是封装了提示词和上下文。以及复制和异变的操作,这个后续在记录。1
2Prompt prompt = chatClientRequest.prompt();
Map<String, Object> context = chatClientRequest.context();第二步执行整个链中
Advisor
的前置动作,在代码中体现出来的就是callAdvisorChain.nextCall(chatClientRequest);
前的逻辑处理。顺序按getOrder
的升序执行。第三步和第四步是执行调用AI与AI通信得到输出响应,下面详细记录。
第五步在得到响应后,执行链中
Advisor
的后置动作,即callAdvisorChain.nextCall(chatClientRequest);
后的逻辑处理。顺序则与第二步相反,是按getOrder
的降序执行。最后将响应返回给
ChatClient
调用处。
如何与AI通信的
其实也是通过一个
Advisor
来执行的,只是这个Advisor
不需要显示的添加到ChatClient
,默认就会有的。实现类分别是
ChatModelCallAdvisor
和ChatModelStreamAdvisor
,是SpringAI封装好的,默认优先级是最低的,也就是Ordered.LOWEST_PRECEDENCE
记录踩坑:
- 在写
LogAdvisor
案例的时候,给的排序也是Ordered.LOWEST_PRECEDENCE
,然后发现并没有执行LogAdvisor
。 - 检查了很多地方,发现写的并没有问题,通过debug源码后看到在链中还有有一个
ChatModelCallAdvisor
。 - 且发现执行完
ChatModelCallAdvisor
链中后续的Advisor
就不会再执行了。 - 所以其他的
Advisor
的排序必须要小于Ordered.LOWEST_PRECEDENCE
- 在写
最后看一下
ChatModelCallAdvisor
和ChatModelStreamAdvisor
的adviseCall
和adviseStream
方法中的执行adviseCall
1
2
3
4
5
6
7
8
9
10
11
12
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
ChatClientRequest formattedChatClientRequest = augmentWithFormatInstructions(chatClientRequest);
ChatResponse chatResponse = this.chatModel.call(formattedChatClientRequest.prompt());
return ChatClientResponse.builder()
.chatResponse(chatResponse)
.context(Map.copyOf(formattedChatClientRequest.context()))
.build();
}adviseStream
1
2
3
4
5
6
7
8
9
10
11
12
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,
StreamAdvisorChain streamAdvisorChain) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
return this.chatModel.stream(chatClientRequest.prompt())
.map(chatResponse -> ChatClientResponse.builder()
.chatResponse(chatResponse)
.context(Map.copyOf(chatClientRequest.context()))
.build())
.publishOn(Schedulers.boundedElastic()); // TODO add option to disable
}
可以看到最后是通过
chatModel
调用的call或者stream进行与AI交互了。
执行顺序
前面的记录中多次提到了顺序是有
getOrder
决定的。数值越小越优先执行。Advisor
链从链首向链尾执行请求处理(前置处理),再由链尾向链首执行响应处理(后置处理)。执行类似于一个栈结构。那么到这里,就会发现与Web中的
Filter
非常相似。1
2
3
4
5
6
7public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//请求(前置)处理
filterChain.doFilter(servletRequest, servletResponse); // 过滤器链
//响应(后置)处理
}
总结
- 到这基本上对
Advisor
有个全面的认识了。 - 核心是
CallAdvisor
和StreamAdvisor
接口的adviseCall
和adviseStream
两个方法。 Advisor
链的执行顺序类似栈结构。- 默认具有
ChatModelCallAdvisor
和ChatModelStreamAdvisor
,并通过二者与AI进行交互。 - 所有的
Advisor
的排序必须要小于Ordered.LOWEST_PRECEDENCE
Advisor
与JavaWeb的Filter
有着与曲同工之妙。
最后
- 具体
Advisor
的源码分析等后面深入Spring AI整体源码学习的时候再做记录,目前还是已使用为主。 - 下一篇会继续记录几个简单的案例来尝试拦截、增强与AI的交互。
- 所有案例的源码,都会提交在GitHub上。