在 Spring AI 的 ChatClient 体系中,Advisor(顾问)是核心扩展点,能够在 AI 请求发送前、响应返回后插入自定义逻辑,比如日志记录、提示词增强、内容安全校验等。本文将通过完整的实战案例,讲解如何自定义和使用 Advisor,实现对 AI 交互全流程的精细化控制。
一、Advisor 核心概念
Advisor 是 Spring AI 为 ChatClient 设计的**AOP(面向切面编程)**扩展机制,主要分为两类:
- CallAdvisor :针对同步调用(
call())的切面增强 - StreamAdvisor :针对流式调用(
stream())的切面增强 - BaseAdvisor:通用型顾问,可同时作用于 call/stream 两种调用方式
通过 Advisor,我们可以无侵入地实现:
- 请求 / 响应日志记录
- 提示词预处理(增强、格式化)
- 内容安全校验(敏感词过滤)
- 响应结果后处理
- 调用链路监控等
二、实战案例:自定义 Advisor 实现
1. 案例 1:通用日志记录 Advisor
实现一个同时支持同步和流式调用的日志记录顾问,完整打印请求和响应信息。
java
package com.easyai.advisor;
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;
/**
* 日志记录Advisor:记录AI调用的请求和响应信息
* 同时实现CallAdvisor(同步)和StreamAdvisor(流式)接口
*/
@Slf4j
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
// 定义Advisor名称,用于日志和调试
@Override
public String getName() {
return this.getClass().getSimpleName();
}
// 执行顺序(0为最高优先级)
@Override
public int getOrder() {
return 0;
}
/**
* 同步调用(call())的增强逻辑
*/
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
log.info("===== 同步调用(call)日志开始 =====");
// 打印请求信息
logRequest(chatClientRequest);
// 执行下一个Advisor(或核心调用逻辑)
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
// 打印响应信息
logResponse(chatClientResponse);
log.info("===== 同步调用(call)日志结束 =====\n");
return chatClientResponse;
}
/**
* 流式调用(stream())的增强逻辑
*/
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
log.info("===== 流式调用(stream)日志开始 =====");
// 打印请求信息
logRequest(chatClientRequest);
// 执行下一个Advisor,获取流式响应
Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);
// 聚合流式响应并打印(避免逐行打印碎片化日志)
return new ChatClientMessageAggregator()
.aggregateChatClientResponse(chatClientResponses, this::logResponse)
.doFinally(signalType -> log.info("===== 流式调用(stream)日志结束 =====\n"));
}
/**
* 打印请求详情
*/
private void logRequest(ChatClientRequest request) {
log.info("AI请求内容:{}", request);
}
/**
* 打印响应详情
*/
private void logResponse(ChatClientResponse chatClientResponse) {
log.info("AI响应内容:{}", chatClientResponse);
}
}
2. 案例 2:提示词增强 Advisor(重读提示)
实现一个自定义 Advisor,自动为用户输入添加 "再读一遍" 的提示词增强,提升 AI 回答的准确性。
java
package com.easyai.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.PromptTemplate;
import java.util.Map;
/**
* 提示词增强Advisor:自动为用户输入添加"再读一遍"的增强逻辑
* 继承BaseAdvisor,通用适配call/stream调用
*/
public class ReReadingAdvisor implements BaseAdvisor {
// 默认提示词模板
private static final String DEFAULT_RE2_ADVISE_TEMPLATE = """
{re2_input_query}
再读一遍: {re2_input_query}
""";
private final String re2AdviseTemplate;
private int order = 0;
// 无参构造器,使用默认模板
public ReReadingAdvisor() {
this(DEFAULT_RE2_ADVISE_TEMPLATE);
}
// 自定义模板构造器
public ReReadingAdvisor(String re2AdviseTemplate) {
this.re2AdviseTemplate = re2AdviseTemplate;
}
/**
* 请求发送前的增强逻辑:修改用户提示词
*/
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
// 获取原始用户输入
String originalUserText = chatClientRequest.prompt().getUserMessage().getText();
// 使用模板增强提示词
String augmentedUserText = PromptTemplate.builder()
.template(this.re2AdviseTemplate)
.variables(Map.of("re2_input_query", originalUserText))
.build()
.render();
// 修改请求中的用户提示词并返回新的请求对象
return chatClientRequest.mutate()
.prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText))
.build();
}
/**
* 响应返回后的处理逻辑(此处无需修改,直接返回)
*/
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
return chatClientResponse;
}
@Override
public int getOrder() {
return this.order;
}
// 链式设置执行顺序
public ReReadingAdvisor withOrder(int order) {
this.order = order;
return this;
}
}
三、Advisor 使用示例(Controller 层)
在 Controller 中集成自定义 Advisor 和 Spring AI 内置的SafeGuardAdvisor(敏感词校验),实现不同场景的 AI 调用。
java
package com.easyai.advisor;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SafeGuardAdvisor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.List;
/**
* Advisor使用示例控制器
* 演示不同Advisor的组合使用方式
*/
@RestController
@RequestMapping("/advisor")
@Slf4j
public class AdvisorController {
// 注入之前配置的Ollama ChatClient
@Resource
private ChatClient ollamaChatClient;
/**
* 示例1:流式调用 + 日志Advisor
*/
@GetMapping(value = "/chat1", produces = "text/stream;charset=utf-8")
public Flux<String> chat1() {
String userInput = "你是谁?";
return ollamaChatClient.prompt()
.advisors(new SimpleLoggerAdvisor()) // 添加日志顾问
.user(userInput)
.stream() // 流式响应
.content(); // 提取响应内容
}
/**
* 示例2:同步调用 + 日志Advisor
*/
@GetMapping(value = "/chat2")
public String chat2() {
String userInput = "你是谁?";
return ollamaChatClient.prompt()
.advisors(new SimpleLoggerAdvisor()) // 添加日志顾问
.user(userInput)
.call() // 同步响应
.content();
}
/**
* 示例3:同步调用 + 内置敏感词校验Advisor
* SafeGuardAdvisor:检测到敏感词时直接返回指定提示
*/
@GetMapping(value = "/chat3")
public String chat3(String userInput) {
return ollamaChatClient.prompt()
// 配置敏感词列表、触发敏感词后的提示语、执行顺序
.advisors(new SafeGuardAdvisor(
List.of("老板","Boss"), // 敏感词列表
"提示词里有敏感词,禁止回答", // 敏感词触发提示
0 // 执行顺序
))
.user(userInput)
.call()
.content();
}
/**
* 示例4:同步调用 + 提示词增强Advisor
* ReReadingAdvisor:自动为用户输入添加"再读一遍"增强
*/
@GetMapping(value = "/chat4")
public String chat4(String userInput) {
return ollamaChatClient.prompt()
.advisors(new ReReadingAdvisor()) // 添加提示词增强顾问
.user(userInput)
.call()
.content();
}
/**
* 扩展示例:多Advisor组合使用(日志 + 敏感词 + 提示词增强)
*/
@GetMapping(value = "/chat5")
public String chat5(String userInput) {
return ollamaChatClient.prompt()
// 多个Advisor按顺序执行(order越小越先执行)
.advisors(
new SimpleLoggerAdvisor(), // 日志记录(order=0)
new SafeGuardAdvisor(List.of("老板","Boss"), "敏感词禁止回答", 1), // 敏感词校验(order=1)
new ReReadingAdvisor().withOrder(2) // 提示词增强(order=2)
)
.user(userInput)
.call()
.content();
}
}
四、关键说明
1. Advisor 执行顺序
- 通过
getOrder()方法控制执行顺序,数值越小优先级越高 - 多个 Advisor 组合时,按 order 从小到大依次执行
- 可通过链式方法(如
withOrder(2))动态调整顺序
2. 流式响应处理
- 流式调用需使用
ChatClientMessageAggregator聚合响应,避免打印碎片化日志 produce = "text/stream;charset=utf-8"必须配置,防止中文乱码
3. 内置 Advisor
Spring AI 提供了多个开箱即用的 Advisor:
SafeGuardAdvisor:敏感词校验TokenCountingAdvisor:令牌计数RetryAdvisor:失败重试TimeoutAdvisor:超时控制
五、测试效果
-
日志 Advisor :调用
/advisor/chat1或/advisor/chat2,控制台会完整打印请求和响应日志 -
敏感词校验 :调用
/advisor/chat3?userInput=老板在吗,直接返回 "提示词里有敏感词,禁止回答" -
提示词增强 :调用
/advisor/chat4?userInput=1+1等于几,AI 实际收到的提示词是:plaintext
1+1等于几 再读一遍: 1+1等于几
总结
- Spring AI 的 Advisor 是 ChatClient 的核心扩展机制,可无侵入实现请求 / 响应的全流程增强;
- 自定义 Advisor 可实现日志记录、提示词增强等个性化需求,内置 Advisor(如 SafeGuard)可快速实现敏感词校验等通用功能;
- 多个 Advisor 可组合使用,通过
getOrder()控制执行顺序,流式调用需注意响应聚合处理。