一、概述
1.1 什么是Advisor
Spring AI Alibaba的Advisor(顾问/拦截器)是专门针对AI交互场景设计的增强组件,其核心灵感来源于责任链模式和面向切面编程(AOP)思想,为AI应用提供了统一、可扩展的请求/响应拦截与增强能力。
Advisor本质上是AI交互的"中间件",在用户请求发送到大语言模型(LLM)之前和模型响应返回给用户之后,自动执行通用逻辑,让开发者专注于业务核心而非重复的横切关注点。
1.2 核心优势
代码复用:将对话记忆、敏感词过滤、日志记录等通用功能封装为可重用组件
统一治理:在单一位置实现跨所有AI调用的安全、监控、限流等能力
灵活扩展:支持自定义拦截逻辑,轻松集成第三方服务
可移植性:编写的Advisor可在不同模型和用例间无缝迁移
链式执行:支持多个Advisor按指定顺序协同工作
1.3 适用场景
对话历史管理
检索增强生成(RAG)
敏感内容过滤
请求/响应日志记录
性能监控与指标采集
限流与熔断
提示词动态注入
工具调用拦截与增强
二、核心原理剖析
2.1 执行流程:洋葱模型
Advisor采用经典的**洋葱模型(Onion Model)**执行方式,请求从外层到内层依次经过每个Advisor的前置处理,到达模型后,响应再从内层到外层依次经过每个Advisor的后置处理。
用户请求 ↓ Advisor1 前置处理 ↓ Advisor2 前置处理 ↓ 实际调用大模型 ↓ Advisor2 后置处理 ↓ Advisor1 后置处理 ↓ 返回给用户关键特性:第一个处理请求的Advisor,最后一个处理响应。
2.2 接口体系
Spring AI Alibaba的Advisor接口体系设计清晰,主要分为同步和流式两大分支:
Advisor (顶层接口) ├─ CallAdvisor (同步调用拦截) │ └─ ChatClientResponse adviseCall(ChatClientRequest, CallAdvisorChain) ├─ StreamAdvisor (流式调用拦截) │ └─ Flux<ChatClientResponse> adviseStream(ChatClientRequest, StreamAdvisorChain) └─ BaseAdvisor (抽象基类,提供通用实现)
核心方法说明:
getName():返回Advisor的唯一名称
getOrder():返回执行优先级,值越小优先级越高
adviseCall()/adviseStream():拦截方法,包含前置和后置处理逻辑
chain.nextCall()/chain.nextStream():调用链中的下一个Advisor
2.3 上下文共享:AdvisorContext
AdvisorContext是一个Map<String, Object>对象,用于在整个Advisor链中传递和共享数据。通过它,多个Advisor可以协同工作,例如第一个Advisor计算的值可以被后续的Advisor使用。
使用方式:
// 在前置处理中设置上下文
request.context().put("userId", "12345");
// 在后置处理中获取上下文
String userId = (String) response.context().get("userId");
三、内置Advisor详解
Spring AI Alibaba提供了多种开箱即用的内置Advisor,覆盖了大部分常见的AI应用场景:
3.1 MessageChatMemoryAdvisor
功能:自动管理对话历史,将历史消息注入到每次请求中,实现多轮对话能力。
使用示例:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory)
.conversationId("default-conversation")
.maxMessages(10) // 保留最近10条消息
.build()
)
.build();
3.2 QuestionAnswerAdvisor功能:实现检索增强生成(RAG),自动从向量库中检索相关文档并注入到提示词中。
使用示例:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
QuestionAnswerAdvisor.builder(vectorStore)
.topK(5) // 返回最相关的5个文档
.similarityThreshold(0.7) // 相似度阈值
.build()
)
.build();
3.3 SimpleLoggerAdvisor功能:记录AI请求和响应的详细日志,便于调试和问题排查。
使用示例:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
3.4 SafeGuardAdvisor功能:敏感词过滤,检测请求和响应中的敏感内容并进行拦截或替换。
使用示例:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new SafeGuardAdvisor(List.of("敏感词1", "敏感词2"))
)
.build();
四、自定义Advisor开发
4.1 同步调用拦截器(CallAdvisor)
适用于chatClient.prompt().call()这种同步调用场景。
完整示例:自定义日志拦截器
import lombok.extern.slf4j.Slf4j;
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.core.Ordered;
@Slf4j
public class CustomLoggerAdvisor implements CallAdvisor {
@Override
public String getName() {
return "CustomLoggerAdvisor";
}
@Override
public int getOrder() {
// 较低优先级,接近模型调用
return Ordered.LOWEST_PRECEDENCE - 100;
}
@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
// 前置处理:记录请求信息
long startTime = System.currentTimeMillis();
log.info("=== AI 请求开始 ===");
log.info("用户ID: {}", request.context().get("userId"));
log.info("用户消息: {}", request.prompt().getUserMessage().getText());
// 记录系统提示词
request.prompt().getSystemMessage().ifPresent(
sysMsg -> log.info("系统提示词: {}", sysMsg.getText())
);
// 放行,调用下一个Advisor
ChatClientResponse response = chain.nextCall(request);
// 后置处理:记录响应信息
long endTime = System.currentTimeMillis();
log.info("=== AI 请求结束 ===");
log.info("响应耗时: {}ms", endTime - startTime);
log.info("响应内容: {}", response.getResult().getOutput().getText());
return response;
}
}
4.2 流式调用拦截器(StreamAdvisor)适用于chatClient.prompt().stream()这种流式响应场景。
完整示例:流式日志拦截器
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import org.springframework.core.Ordered;
import reactor.core.publisher.Flux;
@Slf4j
public class StreamLoggerAdvisor implements StreamAdvisor {
@Override
public String getName() {
return "StreamLoggerAdvisor";
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
// 前置处理:记录请求信息
long startTime = System.currentTimeMillis();
log.info("=== 流式AI 请求开始 ===");
log.info("用户消息: {}", request.prompt().getUserMessage().getText());
// 放行,调用下一个Advisor
return chain.nextStream(request)
.doOnNext(response -> {
// 处理每个响应片段
if (response.getResult() != null && response.getResult().getOutput() != null) {
log.debug("收到响应片段: {}", response.getResult().getOutput().getText());
}
})
.doOnComplete(() -> {
// 流式响应完成后的处理
long endTime = System.currentTimeMillis();
log.info("=== 流式AI 请求结束 ===");
log.info("总耗时: {}ms", endTime - startTime);
})
.doOnError(error -> {
// 异常处理
log.error("流式AI请求失败", error);
});
}
}
4.3 同时支持同步和流式
为了让自定义Advisor同时支持同步和流式调用,可以同时实现两个接口:
@Slf4j
public class UniversalLoggerAdvisor implements CallAdvisor, StreamAdvisor {
@Override
public String getName() {
return "UniversalLoggerAdvisor";
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
// 同步处理逻辑
return chain.nextCall(request);
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
// 流式处理逻辑
return chain.nextStream(request);
}
} 4.4 工具调用拦截器(ToolInterceptor)Spring AI Alibaba还提供了专门用于拦截工具调用的ToolInterceptor接口,适用于Agent场景。
完整示例:工具调用耗时统计拦截器
import lombok.extern.slf4j.Slf4j;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallHandler;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallRequest;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallResponse;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolInterceptor;
@Slf4j
public class ToolPerformanceInterceptor extends ToolInterceptor {
@Override
public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
// 前置处理:记录开始时间
long startTime = System.currentTimeMillis();
log.info("工具调用开始: {}", request.getToolName());
log.info("工具参数: {}", request.getArguments());
try {
// 执行工具调用
ToolCallResponse response = handler.call(request);
// 后置处理:记录耗时和结果
long endTime = System.currentTimeMillis();
log.info("工具调用成功: {}", request.getToolName());
log.info("耗时: {}ms", endTime - startTime);
log.info("返回结果: {}", response.getResult());
return response;
} catch (Exception e) {
// 异常处理
long endTime = System.currentTimeMillis();
log.error("工具调用失败: {}, 耗时: {}ms",
request.getToolName(), endTime - startTime, e);
throw e;
}
}
} 五、最佳实践与常见误区
5.1 最佳实践
(1)合理设置执行顺序
安全相关的Advisor(如敏感词过滤、限流)应设置最高优先级
日志记录Advisor应设置较低优先级,确保能记录完整的请求和响应
业务相关的Advisor(如动态提示词)应在安全检查之后执行
(2)同时支持同步和流式
自定义Advisor尽量同时实现CallAdvisor和StreamAdvisor接口
确保两种调用方式下的行为一致
(3)使用上下文传递数据
避免使用ThreadLocal传递数据,使用AdvisorContext
上下文数据应明确命名,避免冲突
(4)异常处理
在Advisor中捕获并处理异常,避免影响整个调用链
对于不可恢复的异常,返回友好的错误响应
(5)性能优化
避免在Advisor中执行耗时操作
对于需要外部调用的逻辑,考虑使用异步处理
5.2 常见误区
(1)忘记调用chain.next()
这是最常见的错误,会导致请求无法到达模型
确保在前置处理完成后调用chain.nextCall()或chain.nextStream()
(2)在after()中抛出异常
后置处理中抛出异常会导致响应丢失
建议只在后置处理中记录日志和采集指标
(3)流式调用使用CallAdvisor
流式调用必须使用StreamAdvisor
否则会导致响应无法正常返回
(4)硬编码配置
避免在Advisor中硬编码敏感词、限流阈值等配置
使用配置文件或外部服务进行管理
(5)过度使用Advisor
不要将业务逻辑放在Advisor中
Advisor只应处理横切关注点
六、总结
Spring AI Alibaba的Advisor机制为AI应用开发提供了强大而灵活的增强能力。通过理解其洋葱模型执行原理和接口体系,开发者可以轻松实现各种通用功能的封装和复用。
Spring AI Alibaba 格式化输出详解
