用 Spring AI(1.0 GA 稳定版)做 RAG,最早我是在业务代码里手写"先检索、拼 prompt、再调模型"三段式。逻辑散落、难复用、难插拔。后来发现 Spring AI 的 Advisor 机制就是为这事设计的——把"检索增强"做成可串联的拦截器。
Advisor 是什么:请求响应之间的链
Advisor 本质是 CallAroundAdvisor,在"用户请求进模型"和"模型响应回用户"之间插一脚。一个 ChatClient 可以挂一串 Advisor,按顺序执行,像 Servlet 的 FilterChain。RAG 正是典型用例:在请求前做检索、把知识塞进 prompt;在响应后做日志或后处理。
用 QuestionAnswerAdvisor 快速搭
最简形态,Spring AI 自带 QuestionAnswerAdvisor,给它一个向量库就能用:
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().topK(5).build())
.build())
.build();
String ans = client.prompt("我们的退款政策是怎样的?").call().content();
它自动把 query 向量化、检索 top5、拼成 CONTEXT 注入系统提示。十分钟跑通原型,但生产我们要求更可控,于是写了自定义 Advisor。
自定义检索增强流程
标准 QuestionAnswerAdvisor 只做"向量召回 + 拼接",我们要加"关键词召回"和"重排",就得自己实现 CallAroundAdvisor:
public class HybridRagAdvisor implements CallAroundAdvisor {
public AdvisedResponse aroundCall(AdvisedRequest req, CallInterceptor chain) {
String q = req.userText();
List<Document> docs = new ArrayList<>();
docs.addAll(vectorStore.similaritySearch(q)); // 向量召回
docs.addAll(bm25Store.search(q, 5)); // 关键词召回
docs = reranker.rerank(q, docs).subList(0, 5); // 重排取前5
String context = docs.stream().map(Document::getText).collect(joining("\n"));
AdvisedRequest enriched = req.mutate()
.systemMessage("参考以下资料回答:\n" + context).build();
return chain.next(enriched); // 交给下一个 Advisor 或模型
}
}
注册多个 Advisor 时要注意顺序:先 HybridRagAdvisor 注入上下文,再挂一个 LoggingAdvisor 记录检索了哪些文档,便于复盘"为什么答错"。
把检索结果透传给前端
调试时发现模型答非所问,想看它到底检索到了什么。Advisor 可以在响应里附带元数据:
@Override
public AdvisedResponse aroundCall(AdvisedRequest req, CallInterceptor chain) {
AdvisedResponse resp = chain.next(req);
resp.adviseContext().put("retrievedDocs", docs); // 透传给后续
return resp;
}
配合一个后置 Advisor,把 retrievedDocs 写进响应头或日志,前端能展示"参考来源",用户也能点开看依据。这对建立信任很关键。
踩坑
- 上下文爆炸:topK 设 10 时 prompt 太长,模型反而忽略重点。调到 5 后答案聚焦度提升,且延迟从 4.2s 降到 2.8s;
- Advisor 顺序错:先日志后检索,日志里永远是空的——链的执行顺序是入参顺序,先注册先执行;
- 同步阻塞:检索是 IO,放在 Advisor 里同步跑会拖慢整个调用。我们接到虚拟线程池,Advisor 内
executor.execute异步检索,主流程不卡。
写在后面
现在回头看,《Spring AI Advisor 机制与 RAG 管道搭建》本身不算多难,难的是线上真出问题那十分钟里的判断。经验都是这么来的。