[版权申明] 非商业目的注明出处可自由转载 出自:shusheng007
概述
经过前面两篇的介绍大家大概已经明白了应用层使用LLMs的本质:调用API! 这多大点儿事啊,我们作为API小王子,调包小能手可不是浪得虚名的...
SpringAI要解决的问题就是:如何帮助我们在API调用前后解决各种问题。例如调用前参数怎么构建,调用之后结果怎么处理...
于是SpringAI设计了Advisor ,理解其至关重要!
Advisor基本原理
Advisor和WebFilter的实现思路如出一辙,都是对请求进行过滤和增强,相信已经掌握了WebFilter的同学掌握起Advisor时犹如探囊取物(裤裆掏雀,手拿把抓)。
下图是其UML图,也非常简单。由于LLM的API一般支持两种输出方式:一种是返回值一次性输出,另一种是Steam输出,所以这里分别也有两种实现,原理都是一样的,我们就以经典的一次输出来讲解。
Advisor作用流程如下:
- 一个
AdvisedRequest
先通过第一个Advisor
的aroundCall
方法,在这里你可以对基于AdvisedRequest
做定制。例如记录log,修改其内容等。 - 调用
CallAroundAdvisorChain
的nextAroundCall(AdvisedRequest advisedRequest)
方法,去调用下一个Advisor
的aroundCall
方法。就这样一直执行完所有需要执行的Advisor
(也有可能执行到某个Advisor后,其不满足继续执行的条件,后面的就不执行了) - 调用LLM的API,返回
AdvisedResponse
AdvisedResponse
就会以与AdvisedRequest
执行顺序相反的方向依次通过Advisor
,request最先到达第一个Advisor,response最后从其返回。 如下图所示
其中非常值得注意的是 AdvisedRequest
与 AdvisedResponse
包含了一个 Map<String, Object>
,被称作AdvisorContext
,放入此Map的数据会在整个Advisor链中流转。
下面是多个Advisor的执行顺序,每个Advisor都实现了 Ordered
接口,其顺序决定了Advisor的执行顺序。如下图所示,3个Advisor序号越小越早执行,但是返回就是反方向的了。例如request先经过advisor1,response就最后经过advisor1。
Advisor实践
如何使用
- 将Advisor设置给ChatClient
可以通过 defaultAdvisors 方法
来设置。例如此处设置了一个 MessageChatMemoryAdvisor
。通过这种方式设置的Advisor会作用于ChatClient发起的每一次对话。
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder){
return chatClientBuilder
.defaultSystem("You are a helpful assistant")
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory()))
.build();
}
当然我们也可以为每一次的对话设置advisor。如果default也设置了同一类型的Advisor,那么对话级别的优先级高于default,这个应该非常容易理解。
String response = chatClient
.prompt(prompt)
.advisors(memoryAdvisor)
.advisors(advisorSpec ->
advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
.call()
.content();
- 为Advisor设置参数
上面的代码有如下片段,这是在给 MessageChatMemoryAdvisor设置参数,所有设置给此ChatClient的Advisor的参数都是存放在一个Map中的,那么我们如何知道key是什么呢?
.advisors(advisorSpec ->
advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
我的理解是只能去看源码或者官方文档,每个Advisor的参数的key都被定义为常量,所以从源码中应该很容易获得
- 调用LLM
当给ChatClient设置了Advisor后,当调用LLM的API时这些Advisor就会起作用。我们还可以给这些Advisor传入参数。
Advisor的执行顺序首先由实现的 Ordered
接口决定,如果两个序号一致那么以设置顺序为准
深入理解
要实现一个Advidor,需要实现如下两个接口
public interface CallAroundAdvisor extends Advisor {
AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
}
public interface StreamAroundAdvisor extends Advisor {
Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}
他们的工作原理相同,只是因为LLM的API支持两种返回方式。CallAroundAdvisor作用于API一次性放回结果的情形,而SteamAroundAdvisor做用于API以Stream返回的情形。
从源码中可以看出,这两个接口都非常的简单,其原理我们已经在第一份介绍过了。接下来让我们看一个实例,其是SpringAi提供给我们的用于记录与大模型交互日志的Advisor。
public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
//将AdvisedRequest转化为字符串的默认实现
public static final Function<AdvisedRequest, String> DEFAULT_REQUEST_TO_STRING = request -> request.toString();
//将ChatResponse转化为字符串的默认实现
public static final Function<ChatResponse, String> DEFAULT_RESPONSE_TO_STRING = response -> ModelOptionsUtils
.toJsonStringPrettyPrinter(response);
private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);
private AdvisedRequest before(AdvisedRequest request) {
logger.debug("request: {}", this.requestToString.apply(request));
return request;
}
private void observeAfter(AdvisedResponse advisedResponse) {
logger.debug("response: {}", this.responseToString.apply(advisedResponse.response()));
}
//CallAroundAdvisor方法
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
advisedRequest = before(advisedRequest);
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
observeAfter(advisedResponse);
return advisedResponse;
}
//StreamAroundAdvisor方法
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
advisedRequest = before(advisedRequest);
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
return new MessageAggregator().aggregateAdvisedResponse(advisedResponses, this::observeAfter);
}
}
从源码可以看出,SimpleLoggerAdvisor 在请求前通过 before(AdvisedRequest request)
方法记录了请求日志,然后将请求传递给下一个Advisor,最后在请求后通过 observeAfter(AdvisedResponse advisedResponse)记录了返回日志。
所以如果SpringAi提供的Advisor不能满足我们的要求,我们也可以自定义Advisor。在最新版中,SpringAi还贴心的为我们提供了一个BaseAdvisor
接口,其为我们实现了一些骨架代码,使得我们自定义起来更加的方便,推荐使用这个。
总结
Advisor在SpringAI中扮演着非常重要的角色,所以必须深刻理解。更多详情请查看官方文档Advisor API
源码
一如既往,你可以从GitHub找到本文的源码 AI-exploration