本课目标
走完 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。
动手练习
- 拿一份你自己的 PDF 文档走通全链路,观察不同 chunk 大小对答案的影响
- 把 similarityThreshold 从 0.6 调到 0.3,看看幻觉率怎么变化