Administrator
发布于 2024-05-19 / 990 阅读
16

Spring AI Advisor 机制与 RAG 管道搭建

用 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 管道搭建》本身不算多难,难的是线上真出问题那十分钟里的判断。经验都是这么来的。

参考