shusheng007
Published on 2025-04-20 / 6 Visits
0
0

SpringAI开发指南(三):如何使用Advisor

[版权申明] 非商业目的注明出处可自由转载 出自:shusheng007

概述

经过前面两篇的介绍大家大概已经明白了应用层使用LLMs的本质:调用API! 这多大点儿事啊,我们作为API小王子,调包小能手可不是浪得虚名的...

SpringAI要解决的问题就是:如何帮助我们在API调用前后解决各种问题。例如调用前参数怎么构建,调用之后结果怎么处理...

于是SpringAI设计了Advisor ,理解其至关重要!

Advisor基本原理

Advisor和WebFilter的实现思路如出一辙,都是对请求进行过滤和增强,相信已经掌握了WebFilter的同学掌握起Advisor时犹如探囊取物(裤裆掏雀,手拿把抓)。

下图是其UML图,也非常简单。由于LLM的API一般支持两种输出方式:一种是返回值一次性输出,另一种是Steam输出,所以这里分别也有两种实现,原理都是一样的,我们就以经典的一次输出来讲解。

Advisors API Classes

Advisor作用流程如下:

  1. 一个 AdvisedRequest先通过第一个 AdvisoraroundCall方法,在这里你可以对基于 AdvisedRequest做定制。例如记录log,修改其内容等。
  2. 调用 CallAroundAdvisorChainnextAroundCall(AdvisedRequest advisedRequest)方法,去调用下一个 AdvisoraroundCall方法。就这样一直执行完所有需要执行的 Advisor(也有可能执行到某个Advisor后,其不满足继续执行的条件,后面的就不执行了)
  3. 调用LLM的API,返回 AdvisedResponse
  4. AdvisedResponse就会以与 AdvisedRequest执行顺序相反的方向依次通过 Advisor,request最先到达第一个Advisor,response最后从其返回。 如下图所示

Advisors API Flow

其中非常值得注意的是 AdvisedRequest AdvisedResponse 包含了一个 Map<String, Object>,被称作AdvisorContext ,放入此Map的数据会在整个Advisor链中流转。

下面是多个Advisor的执行顺序,每个Advisor都实现了 Ordered接口,其顺序决定了Advisor的执行顺序。如下图所示,3个Advisor序号越小越早执行,但是返回就是反方向的了。例如request先经过advisor1,response就最后经过advisor1。

advisor01-dslw.jpg

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


Comment