SpringAI - Advisor(三)
更多案例
最佳实践
- 官方推荐的最佳实践
- 保持 Advisor 功能单一化以提升模块性。
- 必要时通过
adviseContext
在 Advisor 间共享状态。- 同时实现流式与非流式版本以获得最佳灵活性。
- 谨慎规划 Advisor 链顺序以确保数据流正确。
- 按照上面几点,写了三个案例记录在下面。
案例1:LogExampleAdvisor
需求
- 编写一个Log案例,将每次请求AI的提示词和上下文,以及AI的响应文本和响应后的上下文输出到日志。
- 顺序在最后,但必须在AI交互的
Advisor
前一个
Code
创建一个排序的枚举
AdvisorOrders
,定义LogExample
的排序1
2
3
4
5
6
7
8
9
10
11
public enum AdvisorOrders {
// 在最后,但必须小于 Ordered.LOWEST_PRECEDENCE
LogExample(Ordered.LOWEST_PRECEDENCE - 1),
;
private int order;
}创建
LogExampleAdvisor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class LogExampleAdvisor implements CallAdvisor, StreamAdvisor {
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
requestLogger(chatClientRequest);
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
responseLogger(chatClientResponse);
return chatClientResponse;
}
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
requestLogger(chatClientRequest);
Flux<ChatClientResponse> chatClientResponseFlux = streamAdvisorChain.nextStream(chatClientRequest);
// ChatClientMessageAggregator 是SpringAi提供的工具类
return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponseFlux, this::responseLogger);
}
public String getName() {
return this.getClass().getSimpleName();
}
public int getOrder() {
return AdvisorOrders.LogExample.getOrder();
}
private void requestLogger(ChatClientRequest chatClientRequest) {
// 输出请求AI的提示词和上下文
log.info("\nChat client request to AI\nprompt text -> {}\ncontext -> {}", chatClientRequest.prompt().getContents(), ConvertorUtils.toJsonString(chatClientRequest.context()));
}
private void responseLogger(ChatClientResponse chatClientResponse) {
// 输出AI的响应文本和响应后的上下文
log.info("\nChat client response from AI\noutput text -> {}\ncontext -> {}", chatClientResponse.chatResponse().getResult().getOutput().getText(), chatClientResponse.context());
}
}
案例2:SensitiveFilterExampleAdvisor
需求
- 模拟一个敏感词过滤的案例,在请求之前对User角色的提示词进行敏感词过滤阻断。
- 如果存在敏感词直接返回,不进行与AI交互。
- 被过滤后,向上下文添加“敏感词过滤参数”为
true
。 - 顺序在
LogExampleAdvisor
之前
Code
定义顺序,增加
SensitiveFilterExample(LogExample.order - 1)
到AdvisorOrders
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum AdvisorOrders {
// 在最后,但必须小于 Ordered.LOWEST_PRECEDENCE
LogExample(Ordered.LOWEST_PRECEDENCE - 1),
// 在 log 前面
SensitiveFilterExample(LogExample.order - 1),
;
private int order;
}创建一个上下文Key的枚举
ContextKeys
,用作参数添加是使用1
2
3
4
5
6
7
8
9public enum ContextKeys {
// 客户端名称
ClientName,
// 敏感词过滤
SensitiveFilter,
;
}创建
SensitiveFilterExampleAdvisor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class SensitiveFilterExampleAdvisor implements CallAdvisor, StreamAdvisor {
// 模拟的敏感词
private final List<String> words = Lists.newArrayList( "草泥马");
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
if (filter(chatClientRequest)) {
return createFilterResponse(chatClientRequest);
}
return callAdvisorChain.nextCall(chatClientRequest);
}
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
if (filter(chatClientRequest)) {
return Flux.just(createFilterResponse(chatClientRequest));
}
return streamAdvisorChain.nextStream(chatClientRequest);
}
public String getName() {
return this.getClass().getSimpleName();
}
public int getOrder() {
return AdvisorOrders.SensitiveFilterExample.getOrder();
}
/**
* 进行过滤
* @param chatClientRequest
* @return
*/
private boolean filter(ChatClientRequest chatClientRequest) {
List<UserMessage> userMessages = chatClientRequest.prompt().getUserMessages();
String text = userMessages.stream().map(UserMessage::getText).collect(Collectors.joining());
boolean filter = words.stream().anyMatch(text::contains);
if (filter) {
// 往上下文添加参数
chatClientRequest.context().put(ContextKeys.SensitiveFilter.name(), true);
}
return filter;
}
/**
* 创建过滤的响应
* @param chatClientRequest
* @return
*/
private ChatClientResponse createFilterResponse(ChatClientRequest chatClientRequest) {
return ChatClientResponse.builder()
.chatResponse(ChatResponse.builder()
.generations(List.of(new Generation(new AssistantMessage("用户输入存在敏感词"))))
.build())
.context(Map.copyOf(chatClientRequest.context()))
.build();
}
}
案例3:PromptEnhancementExampleAdvisor
需求
- 模拟一个提示词增强的案例,在请求之前对提示词进行修改增强。
- 并且模拟将用户信息放入上下文,在增强时使用。
- 同时加一个条件,在上下文中存在“提示词增强参数”并且是
true
的情况才进行修改增强。 - 顺序在
SensitiveFilterExampleAdvisor
之前
Code
定义顺序,增加
PromptEnhancementExample(SensitiveFilterExample.order - 1)
到AdvisorOrders
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum AdvisorOrders {
// 在最后,但必须小于 Ordered.LOWEST_PRECEDENCE
LogExample(Ordered.LOWEST_PRECEDENCE - 1),
// 在 log 前面
SensitiveFilterExample(LogExample.order - 1),
// 在 敏感词过滤 前面
PromptEnhancementExample(SensitiveFilterExample.order - 1),
;
private int order;
}增加“提示词增强参数”、“用户信息”的Key
1
2
3
4
5
6
7
8
9
10
11
12public enum ContextKeys {
// 客户端名称
ClientName,
// 敏感词过滤
SensitiveFilter,
// 提示词增强控制
PromptEnhancement,
// 用户信息
UserInfo,
;
}创建
PromptEnhancementExampleAdvisor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class PromptEnhancementExampleAdvisor implements CallAdvisor, StreamAdvisor {
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
return callAdvisorChain.nextCall(enhancement(chatClientRequest));
}
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
return streamAdvisorChain.nextStream(enhancement(chatClientRequest));
}
public String getName() {
return this.getClass().getSimpleName();
}
public int getOrder() {
return AdvisorOrders.PromptEnhancementExample.getOrder();
}
/**
* 增强
* @param chatClientRequest
* @return
*/
private ChatClientRequest enhancement(ChatClientRequest chatClientRequest) {
Object PromptEnhancement = chatClientRequest.context().get(ContextKeys.PromptEnhancement.name());
if (!Boolean.parseBoolean(StringUtils.toString(PromptEnhancement))) {
return chatClientRequest;
}
UserInfo userInfo = (UserInfo) chatClientRequest.context().get(ContextKeys.UserInfo.name());
// 假设根据用户相关信息检测到用户是一个小朋友以及提问的类型是大自然
String sysMsg = "你是一个专门面向小朋友对大自然科普的老师。\n向你提问的是爱好大自然的小朋友,请耐心为解答大自然的问题。\n" + (Objects.isNull(userInfo) ? StringUtils.EMPTY : String.format("小朋友的名字叫:%s", userInfo.name()));
String userTemplate = "老师你好!我想要问的大自然问题是:%s";
// 需要异变一个新的 request 设置新的 prompt
return chatClientRequest.mutate()
.prompt(chatClientRequest.prompt()
.augmentSystemMessage(systemMessage -> SystemMessage.builder().text(sysMsg).build())
.augmentUserMessage(userMessage -> UserMessage.builder().text(String.format(userTemplate, userMessage.getText())).build())
).build();
}
}上面这里使用到了
chatClientRequest.mutate()
异变,其实就是创建了一个新的ChatClientRequest.Builder
,拥有原来的Prompt
及context
,方便快速重构新的ChatClientRequest
对象。
总结
三个不同需求功能的
Advisor
有使用到上下文进行一些参数共享传递
优先级从高到低的顺序分别是
提示词增强 -> 敏感词过滤 -> Log输出
案例执行及结果
创建ChatClient
1 | private static final String SYSTEM_PROMPT = "你是一个Java专家,请帮忙解答提出的Java相关问题。"; |
执行及结果
LogExampleAdvisor
(Log输出)
执行案例
1
2
3
4
5
6
7public void normalExecutionExample() {
log.info("\nNormal execution!");
ChatClientResponse chatClientResponse = multiAdvisorClient.prompt()
.user("在AI热火朝天的现在,Java的优势是否还存在?")
.call()
.chatClientResponse();
}请求输出
1
2
3
4
5
6
7
8
9
102025-06-30T10:49:24.397+08:00 INFO 90478 --- [spring-ai-example] [ main] c.s.a.e.a.t.MultiAdvisorClientExample :
Normal execution!
2025-06-30T11:10:23.089+08:00 INFO 90478 --- [spring-ai-example] [ main] c.s.a.e.advisor.three.LogExampleAdvisor :
Chat client request to AI
prompt text -> 你是一个Java专家,请帮忙解答提出的Java相关问题。在AI热火朝天的现在,Java的优势是否还存在?
context -> {
"ClientName" : "multiAdvisorClient",
"SensitiveFilter" : false
}响应输出(部分输出省略删除了)
1
2
3
4
5
6
7
8
92025-06-30T11:10:54.096+08:00 INFO 90478 --- [spring-ai-example] [ main] c.s.a.e.advisor.three.LogExampleAdvisor :
Chat client response from AI
output text -> Java 在 AI 时代仍然具有显著优势,尽管新兴语言(如 Python)在 AI 领域更受关注,但 Java 的独特价值体现在以下几个方面:
......
### **结论**
Java 在 AI 时代并未过时,而是**定位转换**:从算法研发(Python 主导)转向**生产化部署、大规模系统集成和高性能场景**。两者互补,而非替代。
context -> {ClientName=multiAdvisorClient, SensitiveFilter=false}从结果看日志都正常输出了。
PromptEnhancementExampleAdvisor
(提示词增强)
执行案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* 提示词增强,看看默认的Java提示词是否被修改
*/
public void promptEnhancementExample() {
log.info("\nPrompt enhancement execution!");
ChatClientResponse chatClientResponse = multiAdvisorClient.prompt()
// 往上下文里传递开启提示词增强
.advisors(spce -> spce.params(Map.of(
ContextKeys.PromptEnhancement.name(), true,
ContextKeys.UserInfo.name(), new UserInfo("大聪明")
)))
.user("蜗牛是怎么繁殖的呢?")
.call()
.chatClientResponse();
}输出的Log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
202025-06-30T11:17:48.783+08:00 INFO 91208 --- [spring-ai-example] [ main] c.s.a.e.a.t.MultiAdvisorClientExample :
Prompt enhancement execution!
2025-06-30T11:17:48.893+08:00 INFO 91208 --- [spring-ai-example] [ main] c.s.a.e.advisor.three.LogExampleAdvisor :
Chat client request to AI
prompt text -> 你是一个专门面向小朋友对大自然科普的老师。
向你提问的是爱好大自然的小朋友,请耐心为解答大自然的问题。
小朋友的名字叫:大聪明老师你好!我想要问的大自然问题是:蜗牛是怎么繁殖的呢?
context -> {
"ClientName" : "multiAdvisorClient",
"UserInfo" : {
"name" : "大聪明"
},
"PromptEnhancement" : true,
"SensitiveFilter" : false
}
2025-06-30T11:18:00.921+08:00 INFO 91208 --- [spring-ai-example] [ main] c.s.a.e.advisor.three.LogExampleAdvisor :
Chat client response from AI
output text -> 哇!大聪明小朋友问了一个特别有趣的问题呢!(≧▽≦)
蜗牛的繁殖方式可神奇啦!它们大部分都是雌雄同体哦,也就是说每只蜗牛既是爸爸又是妈妈~可以看到输出中的
prompt text ->
后面的内容,是被提示词增强后的,System角色的消息和User角色的消息都有被修改了。
SensitiveFilterExampleAdvisor
(敏感词过滤)
执行案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 敏感词过滤,看看是否会直接返回、阻断调用
*/
public void sensitiveFilterExample() {
log.info("\nSensitive Filter execution!");
ChatClientResponse chatClientResponse = multiAdvisorClient.prompt()
// 往上下文里传递开启提示词增强
.advisors(spce -> spce.params(Map.of(
ContextKeys.PromptEnhancement.name(), true,
ContextKeys.UserInfo.name(), new UserInfo("好奇宝子")
)))
// 草泥马是敏感词
.user("草泥马是什么动物啊?")
.call()
.chatClientResponse();
log.info("\nSensitive Filter result text -> \n{}", chatClientResponse.chatResponse().getResult().getOutput().getText());
log.info("\nSensitive Filter result context -> \n{}", chatClientResponse.context());
}输出的Log
1
2
3
4
5
6
7
82025-06-30T11:30:11.056+08:00 INFO 91462 --- [spring-ai-example] [ main] c.s.a.e.a.t.MultiAdvisorClientExample :
Sensitive Filter execution!
2025-06-30T11:30:11.082+08:00 INFO 91462 --- [spring-ai-example] [ main] c.s.a.e.a.t.MultiAdvisorClientExample :
Sensitive Filter result text ->
用户输入存在敏感词
2025-06-30T11:30:11.084+08:00 INFO 91462 --- [spring-ai-example] [ main] c.s.a.e.a.t.MultiAdvisorClientExample :
Sensitive Filter result context ->
{UserInfo=UserInfo[name=好奇宝子], ClientName=multiAdvisorClient, PromptEnhancement=true, SensitiveFilter=true}存在敏感词直接中断未去执行AI通信,仔细看就回发现其实
LogExampleAdvisor
就已经未执行了。从
SensitiveFilter=true
可以看到上下文也被修改过。
Advisor链
最后这里简单记录一下Advisor链的实现,看看下面这张图
从图里可以看到链是一个双向链表
LinkedDeque
,且里面是已经排序好的Advisor
。除了有我们手动添加的三个Advisor,还有一个是上一篇记录到的默认存在与AI交互通信的Advisor。
另外可以看到调用的是
.pop()
方法从头部将Advisor
弹出进行执行。
最后
- 从各个案例上看
Advisor
确实是灵活且强大一个机制,前面有说到与Web的Filter
非常相似。 - 所以过去在Web应用中是如何灵活使用
Filter
的,也就可以借鉴如何来使用Advisor
。 - 接下来是详细学习提示词
Prompt
相关的使用。 - 所有案例的源码,都会提交在GitHub上。包:
com.spring.ai.example.advisor.three