Spring AI 中的 Advisors(顾问) 是一个关键概念,用于在模型交互过程中动态调整或增强提示词(Prompt)、控制生成过程,或注入业务逻辑。它的核心思想是对 AI 模型的输入/输出进行拦截和增强,类似于 AOP(面向切面编程)中的拦截器。以下是详细解析:
Advisor 在请求处理链中的位置:
用户输入 → [Advisor 预处理] → 模型处理 → [Advisor 后处理] → 返回用户
Prompt
。Response
。Advisor
接口import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
public class LoggingAdvisor implements Advisor {
// 预处理:记录请求日志
@Override
public Prompt preProcess(Prompt prompt) {
System.out.println("发送请求至模型,内容:" + prompt.getContents());
return prompt;
}
// 后处理:记录响应日志
@Override
public ModelResponse postProcess(ModelResponse response) {
System.out.println("收到模型响应:" + response.getResult().getOutput().getContent());
return response;
}
}
@Configuration
public class AdvisorConfig {
@Bean
public Advisor loggingAdvisor() {
return new LoggingAdvisor();
}
}
Spring AI 提供了一些预定义的 Advisor,可直接使用:
RetryAdvisor
:自动重试失败请求@Bean
public Advisor retryAdvisor() {
return new RetryAdvisor(
3, // 最大重试次数
List.of(OpenAiApiException.class) // 触发重试的异常类型
);
}
ContentFilterAdvisor
:内容过滤@Bean
public Advisor contentFilterAdvisor() {
return new ContentFilterAdvisor(keywords -> {
// 自定义过滤逻辑(如屏蔽敏感词)
return keywords.stream()
.anyMatch(word -> word.contains("暴力"));
});
}
public class HistoryAdvisor implements Advisor {
private final ThreadLocal<List<String>> conversationHistory = new ThreadLocal<>();
@Override
public Prompt preProcess(Prompt prompt) {
List<String> history = conversationHistory.get();
if (history == null) {
history = new ArrayList<>();
conversationHistory.set(history);
}
// 将历史记录添加到 Prompt
String historyContext = String.join("\n", history);
String enhancedPrompt = "历史对话:\n" + historyContext + "\n当前问题:" + prompt.getContents();
return new Prompt(enhancedPrompt, prompt.getOptions());
}
@Override
public ModelResponse postProcess(ModelResponse response) {
// 将当前对话加入历史
conversationHistory.get().add(response.getResult().getOutput().getContent());
return response;
}
}
通过 AdvisorChain
控制执行顺序:
@Bean
public AdvisorChain advisorChain() {
return new AdvisorChain(
List.of(
new HistoryAdvisor(), // 先执行
new LoggingAdvisor(), // 后执行
new RetryAdvisor()
)
);
}
public class SystemInstructionAdvisor implements Advisor {
@Override
public Prompt preProcess(Prompt prompt) {
String systemInstruction = """
你是一个严谨的科技助手,回答需遵循以下规则:
1. 答案必须基于事实,不确定时明确告知。
2. 避免使用 Markdown 格式。
3. 用中文回答。
""";
String modifiedContent = systemInstruction + "\n\n用户问题:" + prompt.getContents();
return new Prompt(modifiedContent, prompt.getOptions());
}
@Override
public ModelResponse postProcess(ModelResponse response) {
return response; // 无后处理
}
}
通过 Advisors,可以灵活控制 AI 交互的全流程,而无需修改核心业务代码。以下是一个结合 多轮对话业务场景 的详细示例,展示如何通过 Spring AI Advisors 实现上下文管理、敏感词过滤和日志记录,并附上完整代码:
假设我们正在开发一个 客服对话系统,需求如下:
src/main/java
├── com.example
│ ├── config
│ │ ├── AdvisorConfig.java // Advisor配置
│ ├── advisor
│ │ ├── HistoryAdvisor.java // 上下文管理
│ │ ├── ContentFilterAdvisor.java // 敏感词过滤
│ │ ├── LoggingAdvisor.java // 请求日志
│ │ ├── RetryAdvisor.java // 异常重试
│ ├── controller
│ │ ├── ChatController.java // 对话接口
HistoryAdvisor
)package com.example.advisor;
import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
import org.springframework.stereotype.Component;
import java.util.Deque;
import java.util.LinkedList;
@Component
public class HistoryAdvisor implements Advisor {
// 使用 ThreadLocal 保证线程安全(假设每个请求一个线程)
private static final ThreadLocal<Deque<String>> HISTORY = ThreadLocal.withInitial(() -> new LinkedList<>());
private static final int MAX_HISTORY = 3; // 保留最近3轮对话
@Override
public Prompt preProcess(Prompt prompt) {
Deque<String> history = HISTORY.get();
String historyContext = String.join("\n", history);
// 将历史对话添加到当前 Prompt
String enhancedPrompt = """
历史对话记录:
%s
当前用户问题:
%s
""".formatted(historyContext, prompt.getContents());
return new Prompt(enhancedPrompt, prompt.getOptions());
}
@Override
public ModelResponse postProcess(ModelResponse response) {
Deque<String> history = HISTORY.get();
String currentResponse = response.getResult().getOutput().getContent();
// 维护固定长度的历史队列
if (history.size() >= MAX_HISTORY) {
history.removeFirst();
}
history.addLast(currentResponse);
return response;
}
// 清理 ThreadLocal(例如在请求结束时)
public static void clearHistory() {
HISTORY.remove();
}
}
ContentFilterAdvisor
)package com.example.advisor;
import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ContentFilterAdvisor implements Advisor {
private static final List<String> BANNED_WORDS = List.of("赌博", "色情", "诈骗");
@Override
public Prompt preProcess(Prompt prompt) {
String userInput = prompt.getContents();
// 检查用户输入是否包含敏感词
if (containsBannedWords(userInput)) {
throw new IllegalArgumentException("输入包含敏感内容,请修改后重试");
}
return prompt;
}
@Override
public ModelResponse postProcess(ModelResponse response) {
String modelOutput = response.getResult().getOutput().getContent();
// 过滤模型输出中的敏感词
String filteredOutput = modelOutput;
for (String word : BANNED_WORDS) {
filteredOutput = filteredOutput.replace(word, "***");
}
response.getResult().getOutput().setContent(filteredOutput);
return response;
}
private boolean containsBannedWords(String text) {
return BANNED_WORDS.stream().anyMatch(text::contains);
}
}
LoggingAdvisor
)package com.example.advisor;
import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class LoggingAdvisor implements Advisor {
private static final Logger logger = LoggerFactory.getLogger(LoggingAdvisor.class);
private long startTime;
@Override
public Prompt preProcess(Prompt prompt) {
startTime = System.currentTimeMillis();
logger.info("收到用户请求: {}", prompt.getContents());
return prompt;
}
@Override
public ModelResponse postProcess(ModelResponse response) {
long duration = System.currentTimeMillis() - startTime;
logger.info("请求处理完成,耗时 {} ms | 响应内容: {}", duration, response.getResult().getOutput().getContent());
return response;
}
}
RetryAdvisor
)package com.example.advisor;
import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.stereotype.Component;
@Component
public class RetryAdvisor implements Advisor {
private final RetryTemplate retryTemplate;
public RetryAdvisor() {
this.retryTemplate = new RetryTemplate();
// 配置重试策略:最多2次,间隔1秒
SimpleRetryPolicy policy = new SimpleRetryPolicy();
policy.setMaxAttempts(2);
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000); // 1秒
retryTemplate.setRetryPolicy(policy);
retryTemplate.setBackOffPolicy(backOffPolicy);
}
@Override
public Prompt preProcess(Prompt prompt) {
return prompt; // 不做处理
}
@Override
public ModelResponse postProcess(ModelResponse response) {
// 如果响应包含异常,触发重试
return retryTemplate.execute(context -> {
// 实际调用模型的逻辑(示例中简化)
return response;
});
}
}
package com.example.config;
import com.example.advisor.*;
import org.springframework.ai.advisor.AdvisorChain;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class AdvisorConfig {
@Bean
public AdvisorChain advisorChain(
HistoryAdvisor historyAdvisor,
ContentFilterAdvisor contentFilterAdvisor,
LoggingAdvisor loggingAdvisor,
RetryAdvisor retryAdvisor
) {
// 注意:Advisor 的执行顺序很重要!
return new AdvisorChain(List.of(
loggingAdvisor, // 1. 先记录日志
contentFilterAdvisor, // 2. 过滤敏感词
historyAdvisor, // 3. 添加上下文
retryAdvisor // 4. 异常重试
));
}
}
package com.example.controller;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@PostMapping("/chat")
public String chat(@RequestBody String userMessage) {
Prompt prompt = new Prompt(userMessage);
ChatResponse response = chatClient.call(prompt);
return response.getResult().getOutput().getContent();
}
}
POST /chat
Body: "我想咨询如何防止网络诈骗"
您可以安装反诈APP,并注意不要点击陌生链接。
收到用户请求: 我想咨询如何防止网络诈骗
请求处理完成,耗时 450 ms | 响应内容: 您可以安装反诈APP,并注意不要点击陌生链接。
AdvisorChain
的顺序决定了处理流程,例如先过滤敏感词再记录日志,避免记录非法内容。ThreadLocal
管理对话历史,确保多用户并发时的数据隔离。POST /chat
Body: "哪里有赌博平台?"
"输入包含敏感内容,请修改后重试"
。通过这个完整案例,可以清晰看到 Advisors 如何将 横切关注点 模块化,并与核心业务逻辑解耦。如果需要进一步优化(如异步处理敏感词过滤),可在此基础上扩展。