Spring AI 实战系列 | 第 5 篇
Advisors:自定义 AI 中间件
系列说明:本文为《Spring AI 实战系列 入门篇》第 5 篇
前置知识:完成第 1-4 篇
预计阅读时间:15 分钟
📖 目录
一、什么是 Advisors?
1.1 生活中的类比
Advisors = AI 应用的"中间件"
Web 开发中的 Filter:
请求 → Filter1 → Filter2 → Controller → Filter2 → Filter1 → 响应
AI 开发中的 Advisor:
Prompt → Advisor1 → Advisor2 → LLM → Advisor2 → Advisor1 → 响应
1.2 Advisors 能做什么?
| 功能 | 说明 |
|---|---|
| 📝 日志记录 | 记录请求与响应 |
| 🔒 安全检查 | 内容审核、敏感词过滤 |
| 💬 对话记忆 | 维护多轮对话上下文 |
| 🔄 重试机制 | 失败自动重试 |
| 📊 监控统计 | 请求次数、耗时统计 |
| 🎯 RAG | 检索增强生成(QuestionAnswerAdvisor) |
二、工作原理
2.1 执行流程
┌────────────────────────────────────────────────────────────────────┐
│ Advisor 执行链 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 用户请求 │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Advisor1 (order=0, LOW) │ │
│ │ • 处理请求 │ │
│ │ • 可修改 Prompt │ │
│ │ • 调用下一个 → │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Advisor2 (order=100) │ │
│ │ • 处理请求 │ │
│ │ • 可修改 Prompt │ │
│ │ • 调用下一个 → │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ LLM(最后一个环节,实际调用 AI) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 响应 ← Advisor2(处理响应)← Advisor1(处理响应)← 用户 │
│ │
└────────────────────────────────────────────────────────────────────┘
2.2 核心接口
package org.springframework.ai.chat.client.advisor.api;
public interface CallAdvisor extends Advisor {
/** Advisor 名称 */
String getName();
/** 执行顺序(值越小越先执行) */
int getOrder();
/** 处理请求 */
ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain);
}
// 拦截器链
public interface CallAdvisorChain {
ChatClientResponse nextCall(ChatClientRequest request);
}
2.3 顺序规则
┌────────────────────────────────────────┐
│ 执行顺序说明 │
├────────────────────────────────────────┤
│ │
│ Integer.MIN_VALUE → HIGHEST_PRECEDENCE │
│ │
│ ┌─────────────────────────────┐ │
│ │ Advisor A (order = 0) │ ← 先执行
│ │ └→ 修改请求 │ │
│ │ 调用下一个 │ │
│ │ ←─ 处理响应(最后返回) │ │
│ └─────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────┐ │
│ │ Advisor B (order = 100) │ │
│ │ └→ 修改请求 │ │
│ │ 调用下一个 │ │
│ │ ←─ 处理响应 │ │
│ └─────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────┐ │
│ │ LLM(系统内置,最后执行) │ │
│ └─────────────────────────────┘ │
│ │
│ Integer.MAX_VALUE → LOWEST_PRECEDENCE │
│ │
└────────────────────────────────────────┘
三、内置 Advisors
3.1 常用内置 Advisors
| Advisor | 功能 |
|---|---|
QuestionAnswerAdvisor | RAG 检索增强 |
ReReadingAdvisor | 重新阅读问题,提升准确性 |
LoggerAdvisor | 日志记录 |
MemoryPreservationAdvisor | 记忆保留 |
3.2 使用内置 Advisors
@RestController
public class AdvisorController {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public AdvisorController(ChatClient.Builder builder, VectorStore vectorStore) {
this.chatClient = builder.build();
this.vectorStore = vectorStore;
}
@GetMapping("/rag")
public String ragAsk(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.advisors(
new QuestionAnswerAdvisor(vectorStore) // RAG Advisor
)
.call()
.content();
}
@GetMapping("/log")
public String logAsk(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.advisors(
new LoggerAdvisor() // 日志 Advisor
)
.call()
.content();
}
}
3.3 配置 Advisor 参数
// 传入配置
chatClient.prompt()
.user(question)
.advisors(new QuestionAnswerAdvisor(vectorStore,
SearchRequest.builder()
.topK(5)
.similarityThreshold(0.8)
.build()))
.call()
.content();
四、自定义 Advisors
4.1 日志记录 Advisor
package com.example.demo.advisor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.core.Ordered;
public class LoggingAdvisor implements CallAdvisor {
private static final Logger log = LoggerFactory.getLogger(LoggingAdvisor.class);
@Override
public String getName() {
return "logging-advisor";
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100; // 较低优先级
}
@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
// 记录请求
log.info("=== AI 请求 ===");
log.info("用户消息: {}", request.prompt().getUserMessage().getText());
// 继续调用链
ChatClientResponse response = chain.nextCall(request);
// 记录响应
log.info("=== AI 响应 ===");
log.info("响应内容: {}", response.chatResponse().getResult().getOutput().getText());
return response;
}
}
4.2 安全检查 Advisor
package com.example.demo.advisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.core.Ordered;
import java.util.List;
public class SecurityAdvisor implements CallAdvisor {
private static final List<String> SENSITIVE_WORDS = List.of(
"密码", "银行卡", "身份证", "信用卡", "验证码", "手机号"
);
@Override
public String getName() {
return "security-advisor";
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 100; // 较高优先级
}
@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
// 获取用户输入
String userText = request.prompt().getUserMessage().getText();
// 检查敏感词
for (String word : SENSITIVE_WORDS) {
if (userText.contains(word)) {
throw new SecurityException("包含敏感词: " + word);
}
}
// 继续调用链
return chain.nextCall(request);
}
}
4.3 对话记忆 Advisor
public class SimpleMemoryAdvisor implements CallAdvisor {
private final List<Message> history = new ArrayList<>();
@Override
public String getName() {
return "simple-memory";
}
@Override
public int getOrder() {
return 0;
}
@Override
public ChatClientRequest adviseCall(
ChatClientRequest request,
CallAdvisorChain chain
) {
// 添加历史消息到请求
ChatClientRequest modifiedRequest = new ChatClientRequest(
request.user(),
MessageUtils.appendMessages(
history,
MessageUtils.fromUser(request.user())
),
request.options()
);
// 继续执行
ChatClientResponse response = chain.nextCall(modifiedRequest);
// 记录对话历史
history.add(MessageUtils.fromAssistant(response.result().getText()));
// 限制历史长度
if (history.size() > 20) {
history.subList(0, 10).clear();
}
return response;
}
@Override
public Flux<ChatClientResponse> adviseStream(
Flux<ChatClientResponse> flux,
StreamAdvisorChain chain
) {
return chain.next(flux);
}
}
4.4 重试机制 Advisor
public class RetryAdvisor implements CallAdvisor {
private final int maxRetries;
private final long retryDelayMs;
public RetryAdvisor(int maxRetries, long retryDelayMs) {
this.maxRetries = maxRetries;
this.retryDelayMs = retryDelayMs;
}
@Override
public String getName() {
return "retry-advisor";
}
@Override
public int getOrder() {
return Integer.MAX_VALUE; // 最后执行
}
@Override
public ChatClientRequest adviseCall(
ChatClientRequest request,
CallAdvisorChain chain
) {
Exception lastException = null;
for (int i = 0; i < maxRetries; i++) {
try {
return chain.nextCall(request);
} catch (Exception e) {
lastException = e;
if (i < maxRetries - 1) {
try {
Thread.sleep(retryDelayMs);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
}
throw new RuntimeException("重试失败", lastException);
}
@Override
public Flux<ChatClientResponse> adviseStream(
Flux<ChatClientResponse> flux,
StreamAdvisorChain chain
) {
return chain.next(flux);
}
}
五、实战案例
5.1 组合多个 Advisors
@RestController
@RequestMapping("/ai")
public class AdvisorDemoController {
private final ChatClient chatClient;
public AdvisorDemoController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/secure-chat")
public String secureChat(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.advisors(
new SecurityAdvisor(), // 1. 安全检查(最先)
new LoggingAdvisor(), // 2. 日志记录
new RetryAdvisor(3, 1000) // 3. 重试机制(最后)
)
.call()
.content();
}
@GetMapping("/memory-chat")
public String memoryChat(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.advisors(
new SimpleMemoryAdvisor() // 对话记忆
)
.call()
.content();
}
}
5.2 带参数的 Advisor
@Configuration
public class AdvisorConfig {
@Bean
public CallAdvisor customLoggerAdvisor() {
return new CallAdvisor() {
@Override
public String getName() {
return "custom-logger";
}
@Override
public int getOrder() {
return 0;
}
@Override
public ChatClientRequest adviseCall(
ChatClientRequest request,
CallAdvisorChain chain
) {
System.out.println(">>> 请求: " + request.user());
ChatClientResponse response = chain.nextCall(request);
System.out.println("<<< 响应: " + response.result().getText());
return response;
}
@Override
public Flux<ChatClientResponse> adviseStream(
Flux<ChatClientResponse> flux,
StreamAdvisorChain chain
) {
return chain.nextStream(flux);
}
};
}
}
六、系列预告
本篇小结
- ✅ 理解了 Advisors 原理与执行链
- ✅ 掌握了内置 Advisors 用法
- ✅ 学会了自定义 Advisors
完整系列
| 篇目 | 内容 | 状态 |
|---|---|---|
| 第 1 篇 | 核心概念 + 快速上手 | ✅ 已完成 |
| 第 2 篇 | Tool Calling + 工具调用 | ✅ 已完成 |
| 第 3 篇 | VectorStore + RAG | ✅ 已完成 |
| 第 4 篇 | 结构化输出 | ✅ 已完成 |
| 第 5 篇 | Advisors 中间件 | ✅ 本文 |
| 第 6 篇 | 国产模型集成 | 🔜 (终篇) |
下篇预告
第 6 篇:国产模型集成指南
- 通义千问 (Qwen)
- 智谱 GLM
- 百度文心一言
- Ollama 本地部署
📚 参考资料
- Spring AI Advisors 文档
📌 引用说明:本文核心概念与技术描述参考自 Spring AI 官方文档(docs.spring.io/spring-ai/r… 相关内容来自官方 Advisors API 章节。
关注公众号「AI日撰」,点击菜单「获取源码」获取完整代码(Gitee 仓库)。
系列:《Spring AI 实战系列 入门篇》第 5 篇(共 6 篇)