Spring AI Advisors API 为我们的 SpringAI 应用提供了一种灵活且强大的方式,用于拦截、修改和增强基于 AI 的交互。通过利用 Advisors API,我们可以构建更复杂、可重用且易于维护的 AI 组件。
其主要优势包括:封装常见的生成式 AI 模式、对发送给大型语言模型(LLMs)以及从中返回的数据进行转换,并支持在不同模型和使用场景之间的可移植性。
1. Advisors 处理流程图
步骤解释
-
Spring AI 框架会根据用户的 Prompt 创建一个
ChatClientRequest,同时生成一个空的 Advisor 上下文对象。 -
链中的每个 Advisor 会处理该请求,并可能对其进行修改。或者,Advisor 也可以选择阻止该请求 —— 即不调用链中的下一个实体。在这种情况下,Advisor 需要自己填充返回内容。与Filter用法类似。
-
由框架提供的最后一个 Advisor 会将请求发送给 ChatModel大模型(LLM)处理。
-
Chatmodel返回的响应会沿着 Advisor 链反向传递,并被转换为
ChatClientResponse,其中包含共享的 Advisor 上下文实例。 -
每个 Advisor 都可以处理或修改该响应。
-
最终,通过提取
ChatCompletion,将ChatClientResponse返回给调用方。
2. Advisor使用场景
-
日志请求记录
-
大模型响应时间记录
-
敏感词拦截
-
提示词的拦截、修改、增强
-
ChatMemory、RAG、可视化等等都离不开对话拦截器 Advisor
3. Advisors 涉及的类图
adviseCall() 和 adviseStream() 是关键的 Advisor 方法,通常用于执行以下操作:检查未封装的 Prompt 数据、自定义和增强 Prompt 数据、调用 Advisor 链中的下一个实体、有选择地阻止请求、检查聊天补全响应,以及在处理出错时抛出异常。
另外,getOrder() 方法用于确定 Advisor 在链中的执行顺序,而 getName() 方法则用于提供一个唯一的 Advisor 名称。
由 Spring AI 框架创建的 Advisor Chain 链 支持按顺序调用多个 Advisor,其执行顺序由各自的 getOrder() 值决定,数值越小的 Advisor 越先执行。链中的最后一个 Advisor 是由系统自动添加的,它负责将请求发送到 LLM。
4. 自定义日志拦截器
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientMessageAggregator;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import reactor.core.publisher.Flux;
/**
* @author : upfive
* @version : 1.0.0
* @date : 2025/8/20 16:29
*/
@Slf4j
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
logRequest(chatClientRequest);
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
logResponse(chatClientResponse);
return chatClientResponse;
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
logRequest(chatClientRequest);
Flux<ChatClientResponse> chatClientResponse = streamAdvisorChain.nextStream(chatClientRequest);
return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponse, this::logResponse );
}
private void logRequest(ChatClientRequest request) {
log.debug("request: {}", request);
}
private void logResponse(ChatClientResponse chatClientResponse) {
log.debug("response: {}", chatClientResponse);
}
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public int getOrder() {
return 0;
}
}
import lombok.AllArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.SafeGuardAdvisor;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.List;
@AllArgsConstructor
@RestController
public class ChatClientAdvisorController {
private final DeepSeekChatModel chatModel;
@RequestMapping(value = "/advisor", produces = "text/stream;charset=UTF-8")
public void advisor() {
var chatClient = ChatClient.builder(chatModel)
//.defaultSystem(prompt)
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
String content = chatClient.prompt().user("你好,你是谁?").call().content();
System.out.println(content);
System.out.println("=================");
Flux<ChatClientResponse> res = chatClient.prompt()
// SpringAI提供的默认拦截器
.advisors(new SafeGuardAdvisor(List.of("疯狂")))
.user("今天星期几?疯狂星期四吗?").stream().chatClientResponse();
res.toIterable().forEach(System.out::println);
}
}
5. 自定义拦截器实现重读功能
重读(Re2)
重读策略的核心在于让LLMs重新审视输入的问题,这借鉴了人类解决问题的思维方式。通过这种方式,LLMs能够更深入地理解问题,发现复杂的模式,从而在各种推理任务中表现得更加强大。
{Input_Query}
再次阅读问题:{Input_Query}
可以基于BaseAdvisor来实现自定义Advisor,它实现了重复的代码,提供模版方法让我们可以专注自己业务编写即可。
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import java.util.Map;
/**
* @author : upfive
* @version : 1.0.0
* @date : 2025/8/20 16:29
*/
public class ReReadingAdvisor implements BaseAdvisor {
private static final String DEFAULT_USER_TEXT_ADVISE = """
{re2_input_query}
Read the question again: {re2_input_query}
""";
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
String contents = chatClientRequest.prompt().getContents();
String re2InputQuery = PromptTemplate.builder().template(DEFAULT_USER_TEXT_ADVISE).build()
.render(Map.of("re2_input_query", contents));
// 复制一个新的ChatClientRequest对象,并修改其中的prompt
return chatClientRequest.mutate()
.prompt(Prompt.builder().content(re2InputQuery).build())
.build();
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
return chatClientResponse;
}
@Override
public int getOrder() {
return 0;
}
}
@RequestMapping(value = "/reReading", produces = "text/stream;charset=UTF-8")
public void reReading() {
var chatClient = ChatClient.builder(chatModel)
//.defaultSystem(prompt)
.defaultAdvisors(new org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor(), new ReReadingAdvisor())
.build();
String content = chatClient.prompt().user("你好,你是谁?").call().content();
System.out.println(content);
}
6. 最佳实践
- 让Advisors专注于特定任务,以实现更好的模块化。
- 在需要时使用
adviseContext在Advisors之间共享状态。 - 实现Advisors的流式(streaming)和非流式(non-streaming)版本,以获得最大的灵活性。
- 仔细考虑Advisors链中的顺序,以确保数据流的正确性。