Spring AI Advisor为什么不生效?defaultAdvisors、执行顺序与流式调用完整排查
2026/7/27 13:43:36 网站建设 项目流程

文章摘要

Spring AI项目中,Advisor常被用于日志、Memory、RAG、权限、内容审核和工具执行,但开发者经常遇到“明明注册了却没有执行”“请求生效但响应处理顺序不对”“同步接口正常、流式接口失效”等问题。本文从ChatClient实例、defaultAdvisors与运行时advisors、getOrder顺序、CallAdvisor与StreamAdvisor、Context参数和日志观测六个方面给出完整排查方法。

一、先确认你调用的是不是同一个ChatClient

最常见的问题不是Advisor代码错误,而是:

Advisor注册在A客户端,业务调用的却是B客户端。

例如:

@BeanChatClientragChatClient(ChatClient.Builderbuilder,QuestionAnswerAdvisoradvisor){returnbuilder.defaultAdvisors(advisor).build();}

业务类却重新使用Builder:

@ServicepublicclassAiService{privatefinalChatClientchatClient;publicAiService(ChatClient.Builderbuilder){this.chatClient=builder.build();}}

这里创建的是一个新ChatClient,没有注册RAG Advisor。

正确做法:

@ServicepublicclassAiService{privatefinalChatClientragChatClient;publicAiService(@Qualifier("ragChatClient")ChatClientragChatClient){this.ragChatClient=ragChatClient;}}

多ChatClient项目必须明确命名。

二、defaultAdvisors和advisors有什么区别

defaultAdvisors

在构建ChatClient时注册,对这个客户端的所有调用生效:

ChatClientchatClient=builder.defaultAdvisors(loggingAdvisor,memoryAdvisor).build();

适合:

  • 通用日志;
  • 安全检查;
  • 租户上下文;
  • 默认Memory;
  • 默认RAG;
  • 成本统计。

advisors

在单次请求中注册或传递参数:

Stringanswer=chatClient.prompt().advisors(advisorSpec->advisorSpec.advisors(customAdvisor).param("tenantId",tenantId)).user(message).call().content();

适合:

  • 某一次请求临时启用;
  • 传递conversationId;
  • 动态RAG过滤条件;
  • 用户级策略;
  • 临时审核规则。

常见错误是只传参数,却没有注册对应Advisor。

三、Advisor执行顺序不是“越大越先执行”

Spring AI按照getOrder()排序:

数值越小 → 优先级越高 → 请求阶段越早执行

例如:

@OverridepublicintgetOrder(){returnOrdered.HIGHEST_PRECEDENCE+100;}

请求阶段:

安全Advisor → 租户Advisor → Memory Advisor → RAG Advisor → 模型

响应阶段会像栈一样反向返回。

如果两个Advisor返回相同order,执行顺序不保证稳定。

推荐集中定义:

publicfinalclassAdvisorOrders{publicstaticfinalintSECURITY=-1000;publicstaticfinalintTENANT=-800;publicstaticfinalintMEMORY=-500;publicstaticfinalintRAG=-200;publicstaticfinalintLOGGING=1000;privateAdvisorOrders(){}}

四、同步接口和流式接口需要不同能力

Spring AI Advisor核心接口包括:

CallAdvisorStreamAdvisor

如果自定义Advisor只实现CallAdvisor,它只会参与.call(),不会自动参与.stream()

一个简化的同步Advisor:

@ComponentpublicclassRequestLoggingAdvisorimplementsCallAdvisor{privatestaticfinalLoggerlog=LoggerFactory.getLogger(RequestLoggingAdvisor.class);@OverridepublicChatClientResponseadviseCall(ChatClientRequestrequest,CallAdvisorChainchain){log.info("AI request context={}",request.context());ChatClientResponseresponse=chain.nextCall(request);log.info("AI response received");returnresponse;}@OverridepublicStringgetName(){return"requestLoggingAdvisor";}@OverridepublicintgetOrder(){returnAdvisorOrders.LOGGING;}}

最容易遗漏的是:

chain.nextCall(request)

如果没有调用后续链,又没有自己构造响应,请求就会被阻断。

五、Advisor可能主动阻断请求

安全Advisor可以直接返回受控响应,因此当模型没有被调用时,要检查:

  • 是否有Advisor提前返回;
  • 是否抛出异常;
  • 是否命中缓存;
  • 是否触发安全策略;
  • 是否错误判断输入为空;
  • 是否忘记调用下一条链。

建议在每个Advisor中记录:

advisor_name request_enter request_exit response_enter response_exit blocked duration_ms

六、运行时参数名称是否一致

传参:

.advisors(spec->spec.param("tenantId",tenantId).param("conversationId",conversationId))

Advisor读取:

StringtenantId=(String)request.context().get("tenantId");

名称不一致时不会自动报错,只会得到null。

建议使用常量。

七、Context更新后是否传入下一条链

自定义Advisor如果增加上下文,需要创建更新后的请求,再传给后续链。

伪代码:

ChatClientRequestupdatedRequest=request.mutate().context(AdvisorContextKeys.TENANT_ID,tenantId).build();returnchain.nextCall(updatedRequest);

不要只修改局部Map,然后仍然把旧request传给下一条链。

八、Prompt修改是否真的写回请求

错误做法:

StringenhancedPrompt=originalPrompt+"\n补充上下文";returnchain.nextCall(request);

虽然生成了新字符串,但没有更新request。

核心原则是:

修改结果必须进入传给下一条链的ChatClientRequest。

九、TemplateRenderer不会自动影响Advisor内部模板

ChatClient可以配置:

.templateRenderer(customRenderer)

但它只影响直接通过ChatClient链定义的usersystem模板,不会自动影响QuestionAnswerAdvisor或自定义RAG模板。

如果主Prompt正常、RAG增强内容异常,需要单独检查Advisor模板配置。

十、Memory Advisor不生效的常见原因

  • conversationId没有传递;
  • 每次请求生成新的conversationId;
  • ChatMemoryRepository没有持久化;
  • Advisor顺序不合理;
  • 历史消息被上下文裁剪。

稳定会话ID示例:

.advisors(spec->spec.param(ChatMemory.CONVERSATION_ID,conversationId))

十一、RAG Advisor不生效的常见原因

检查:

VectorStore是否有数据 Embedding维度是否一致 检索过滤条件是否过严 相似度阈值是否过高 tenantId是否正确 检索结果是否进入Prompt Advisor是否注册到实际ChatClient

建议把检索结果数量写入Context,便于从Trace判断问题发生在哪一层。

十二、开启可观测性

生产项目建议接入:

  • Actuator;
  • Micrometer;
  • OpenTelemetry;
  • Trace ID;
  • 日志MDC。

日志示例:

traceId=abc123 advisor=tenantAdvisor phase=request order=-800 durationMs=2

不要记录完整敏感Prompt,可以记录Prompt哈希、字符数、Token估算、模型、Advisor名称与执行耗时。

十三、最小排查清单

□ 业务调用的是注册Advisor的ChatClient □ defaultAdvisors确实执行 □ 单次advisors参数名称正确 □ getOrder没有重复 □ 数值越小优先级越高 □ 同步Advisor用于call □ 流式Advisor用于stream □ 调用了nextCall或nextStream □ 修改后的request传入下一条链 □ Context更新被正确写回 □ TemplateRenderer作用范围正确 □ Memory使用稳定conversationId □ RAG检索结果非空 □ Trace中能看到Advisor

总结

Advisor“不生效”通常集中在四类问题:

注册错ChatClient 执行顺序理解错误 同步与流式接口不匹配 修改结果没有写回请求链

先沿着ChatClient实例、Advisor注册、order、Context和链式调用逐层检查,比反复修改Prompt更有效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询