本课目标

走完 RAG 全链路:文档切分、向量化入库、检索增强问答,并能调优检索质量。


你的用户问:"上个季度华北区退货率是多少?"通用模型能回答吗?不能。它不知道你的退货数据存在哪张表里。

给它接上知识库——这就是 RAG(Retrieval-Augmented Generation)。模型不是靠记忆回答,而是"现翻书"回答。本讲带你走完这条链路,从一篇 PDF 到一个能准确回答业务问题的 API。

核心内容

RAG 三步走

第一步:ETL 入库——把文档变成模型能检索的东西

// 1. 读文档:Tika 支持 PDF、Word、HTML、Markdown 等几乎所有格式
TikaDocumentReader reader = new TikaDocumentReader("classpath:/docs/product-manual.pdf");
List<Document> docs = reader.get();

// 2. 切分:按 token 长度切块,带重叠避免语义断裂
TokenTextSplitter splitter = new TokenTextSplitter(800, 350, 5, 10000, true);
List<Document> chunks = splitter.apply(docs);

// 3. 写入向量库:EmbeddingModel 自动把文本转向量,你只管 add
vectorStore.add(chunks);

这里有一个容易被跳过的关键:Tika 的解析质量决定了下游的天花板。PDF 里的表格、多栏排版、扫描件——Tika 处理不好这些,你的 RAG 从一开始就是歪的。脏文档先进人工清洗。

第二步:检索问答——挂上 Advisor,检索结果自动注入

ChatClient chatClient = builder
    .defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
        .searchRequest(SearchRequest.builder()
            .topK(5)
            .similarityThreshold(0.6)
            .build())
        .build())
    .build();

QuestionAnswerAdvisor 的职责:拦截每次请求 → 以用户问题去向量库检索 → 把召回的文档片段拼进 prompt → 模型基于片段作答。你不需要手动拼 prompt,Advisor 帮你做了。

这跟你在 /blog 搜索框里搜文章是一个道理——只不过搜索结果是给模型看的,不是给用户看的。

第三步:向量库选型

Spring AI 支持 20+ 种向量库。选型建议:

规模 推荐 理由
开发/小项目 PGVector 跟 PostgreSQL 一体,运维零成本
中型/几十万向量 Redis Stack 内存检索,快
大型/亿级向量 Milvus 专为向量检索设计,水平扩展

检索质量 = RAG 质量的 70%

很多人把时间花在调 Prompt 上,但 RAG 的瓶颈在检索端:

  • chunk 大小:800 token + 15% 重叠是通用起点。技术文档可调到 1200,对话记录可能要小到 300。没有万能值,在你自己数据上测。
  • 切分策略:按结构切(标题、段落)优于按长度硬切。Markdown 文档尤其如此——# 标题是天然的切分锚点。
  • similarityThreshold:低于这个分数的检索结果直接丢弃。设太低 → 噪音灌入 → 模型被带偏。设太高 → 漏掉有用信息。0.6 是起点,在你自己数据上校准。
  • 混合检索:纯向量检索对专有名词、产品代码不敏感。加上 BM25 关键词检索,命中率能提升 20%-40%。Spring AI 不支持原生混合检索,需要在应用层自己合并两路结果。

"宁缺毋滥"是 RAG 的铁律

检索到不相关的内容比什么都没检索到更糟糕。模型拿到无关上下文后会强行"联想"——这就是 RAG 场景下的幻觉之源。宁可返回"未找到相关信息",也别把垃圾塞进 prompt。

动手练习

  1. 拿一份你自己的 PDF 文档走通全链路,观察不同 chunk 大小对答案的影响
  2. 把 similarityThreshold 从 0.6 调到 0.3,看看幻觉率怎么变化