上集回顾:老张通过MCP协议接入了企业所有系统,工具调用变得简单了。但老李(总监)提出了新要求:"代码太乱了,检索、记忆、安全校验都堆在一起,不好维护。"
一、故事继续:代码混乱的代价
周三下午,老张的客服系统代码:
java
@Service
public class CustomerServiceAgent {
// 1. 检索知识库
List<Document> docs = vectorStore.similaritySearch(
SearchRequest.builder().query(query).topK(5).build()
);
// 2. 获取对话历史(最近10条)
List<Message> history = chatMemory.get(userId, 10);
// 3. 安全检查
if (isSensitive(query)) {
return "这个问题我不能回答";
}
// 4. 调用工具
String toolResult = orderTools.queryOrder(orderId);
// 5. 构建Prompt
String prompt = buildPrompt(history, docs, toolResult, query);
// 6. 调用AI
String answer = chatClient.prompt().user(prompt).call().content();
// 7. 保存历史(逐条 add)
chatMemory.add(userId, new UserMessage(query));
chatMemory.add(userId, new AssistantMessage(answer));
return answer;
}
老李的批评:"小张啊,这代码能跑,但维护起来太痛苦了。换个向量库、改个安全策略,都要动这里。"
二、技术引入:什么是Advisor?
2.1 Advisor的本质
Advisor = 对请求/响应的增强器
原始请求 → [Advisor1] → [Advisor2] → [Advisor3] → AI → [Advisor3] → [Advisor2] → [Advisor1] → 响应
老张的类比:"就像Spring MVC的拦截器链,每个Advisor做一件事,组合起来就是完整功能。"
2.2 Advisor vs 手写代码
| 方式 | 优点 | 缺点 |
|---|---|---|
| 手写逻辑 | 灵活 | 耦合严重、难测试 |
| Advisor链 | 解耦、可复用、易测试 | 需要理解链式执行 |
三、内置Advisor一览
3.1 Spring AI内置Advisor
| Advisor | 作用 | 类比 |
|---|---|---|
| QuestionAnswerAdvisor | RAG检索增强 | 给AI一本参考书 |
| MessageChatMemoryAdvisor | 会话记忆 | 给AI一个记事本 |
| ToolCallingAdvisor | 工具调用 | 给AI一双操作手 |
| SafeGuardAdvisor | 内容安全 | 给AI一个过滤器 |
| ReReadingAdvisor | RE2重读策略 | 让AI多读一遍 |
| SimpleLoggerAdvisor | 请求日志 | AI的日志本 |
3.2 使用示例
java
@Service
public class EnhancedAgent {
@Autowired
private ChatClient chatClient;
@Autowired
private VectorStore vectorStore;
@Autowired
private ChatMemory chatMemory;
@Autowired
private OrderTools orderTools;
public String chat(String userId, String question) {
return chatClient.prompt()
.user(question)
// 第1层:RAG检索
.advisors(new QuestionAnswerAdvisor(vectorStore))
// 第2层:对话记忆
.advisors(new MessageChatMemoryAdvisor(chatMemory, userId))
// 第3层:工具调用
.advisors(new ToolCallingAdvisor(List.of(
FunctionToolCallback.builder("queryOrder", orderTools::queryOrder)
.description("查询订单状态")
.inputType(String.class)
.build()
)))
// 第4层:内容安全
.advisors(new SafeGuardAdvisor())
.call()
.content();
}
}
老张的发现:"哇!代码从30行变成10行,而且每层职责清晰!"
四、自定义Advisor
4.1 场景:业务日志记录
java
@Component
public class BusinessLogAdvisor implements CallAdvisor {
@Override
public ChatClientResponse adviseCall(
ChatClientRequest request,
CallAdvisorChain chain
) {
long startTime = System.currentTimeMillis();
// 执行后续链
ChatClientResponse response = chain.nextAdviseCall(request);
// 记录日志
log.info("用户{}提问:{},耗时:{}ms,结果长度:{}",
request.getContext().getUserId(),
request.getPrompt().getTextContents(),
System.currentTimeMillis() - startTime,
response.getResult().getOutput().getText().length()
);
return response;
}
}
4.2 场景:敏感词过滤
java
@Component
public class SensitiveWordAdvisor implements CallAdvisor {
private static final List<String> SENSITIVE_WORDS =
List.of("政治敏感词", "广告关键词", "...");
@Override
public ChatClientResponse adviseCall(
ChatClientRequest request,
CallAdvisorChain chain
) {
// 检查输入
for (String word : SENSITIVE_WORDS) {
if (request.getPrompt().getTextContents().contains(word)) {
return ChatClientResponse.builder()
.result(new ChatResponse(List.of(
new Generation(new AssistantMessage("抱歉,该问题涉及敏感内容")
)))
.build();
}
}
// 执行后续
ChatClientResponse response = chain.nextAdviseCall(request);
// 检查输出
String content = response.getResult().getOutput().getText();
for (String word : SENSITIVE_WORDS) {
if (content.contains(word)) {
return ChatClientResponse.builder()
.result(new ChatResponse(List.of(
new Generation(new AssistantMessage("抱歉,该回答包含敏感内容")
)))
.build();
}
}
return response;
}
}
五、Advisor链的执行顺序
5.1 顺序规则
Advisor顺序:值越小越先处理请求,越后处理响应(栈式)
java
// 定义顺序
@Bean
public Advisor ragAdvisor(VectorStore store) {
return new QuestionAnswerAdvisor(store) {
@Override
public int getOrder() { return Integer.MIN_VALUE; } // 最先执行
};
}
@Bean
public Advisor memoryAdvisor(ChatMemory memory) {
return new MessageChatMemoryAdvisor(memory) {
@Override
public int getOrder() { return 0; }
};
}
@Bean
public Advisor toolAdvisor() {
return new ToolCallingAdvisor(...) {
@Override
public int getOrder() { return Integer.MAX_VALUE; } // 最后执行
};
}
5.2 执行流程图
请求进入
↓
[SafeGuardAdvisor] 安全检查(最外层)
↓
[BusinessLogAdvisor] 日志记录
↓
[QuestionAnswerAdvisor] RAG检索
↓
[MessageChatMemoryAdvisor] 加载记忆
↓
[ToolCallingAdvisor] 工具调用(最内层)
↓
AI模型
↓
[ToolCallingAdvisor] 处理工具结果
↓
[MessageChatMemoryAdvisor] 保存记忆
↓
[QuestionAnswerAdvisor] 增强Prompt
↓
[BusinessLogAdvisor] 记录响应
↓
[SafeGuardAdvisor] 最终检查
↓
返回给用户
六、实战:老张的重构
6.1 重构后的代码
java
@Configuration
public class AgentConfig {
@Bean
public Advisor[] advisors(
VectorStore vectorStore,
ChatMemory chatMemory,
OrderTools orderTools,
McpClient mcpClient
) {
return new Advisor[] {
// 顺序从高到低
new SafeGuardAdvisor(), // 安全
new BusinessLogAdvisor(), // 日志
new QuestionAnswerAdvisor(vectorStore), // RAG
new MessageChatMemoryAdvisor(chatMemory), // 记忆
new ToolCallingAdvisor(mcpClient), // 工具
};
}
}
@Service
public class CustomerServiceAgent {
@Autowired
private ChatClient chatClient;
public String chat(String userId, String question) {
return chatClient.prompt()
.user(question)
.advisors(advisors) // 一次性注入所有Advisor
.call()
.content();
}
}
老张的感慨:"以前30行的代码,现在5行搞定。以后加新功能,加个Advisor就行,不用动主逻辑。"
七、下一集预告
老张的代码终于优雅了,但总监老李又提了挑战:"现在AI只会按固定流程走,能不能让它自己规划、自主决策?"
第10集《Agent不是遥不可及:ChatClient + @Tool 实现你的第一个Agent》将讲述如何让AI从"被动回答"变成"主动行动"。
📦 完整代码
GitHub分支:https://github.com/yinclei/SpringAITutorial/chapter09-advisor-chain
💬 互动环节
老兵问答:
你在项目中用过AOP或拦截器吗?
A. 用过Spring AOP
B. 用过MyBatis拦截器
C. 知道概念但没实操过
D. 完全没用过
下周一晚9点,我们继续!
作者:Java老兵搞AI | 关注我,看Java如何玩转AI