知识总结 02:RAG 检索增强
本文以 RAG 知识 为主线,项目代码为佐证(标注为「📌 本项目」)。配套的落地细节、验证数据、踩坑见
01-迭代记录-RAG知识问答.md。
一、什么是 RAG
RAG(Retrieval-Augmented Generation,检索增强生成) 是一种让大模型在生成回答前,先从外部知识库中检索相关信息,再结合检索到的内容来生成答案的技术。
它主要解决两个问题:
- 幻觉:模型不再"凭自身经验瞎说",而是"查完资料再说"。
- 专业性 / 时效性:让模型能回答训练数据里没有的私有知识、领域知识、最新知识。
一句话抓住本质:
RAG 的核心 = 检索 + 生成。模型回答从「凭记忆答」变成「拿着资料答」。
关键认知:RAG 不改模型本身------不训练、不微调,只是在提问前多做一次检索、把结果塞进上下文。所以它见效快、可随时更新知识(改文档即可),但也天然受限于"检索得准不准"。
📌 本项目:
prompt/游戏知识问答.md里强制"只依据【参考资料】回答,没有就说不知道",就是把"查完资料再说"写成了硬约束。实测问"树枝手镯有什么用"(模型其实知道、但知识库里没写)时,它老实答"知识库里没有",没有用参数知识补位------这就是 RAG 抑制幻觉的直接体现。
二、RAG 的使用场景
RAG 适合那些需要事实性、专业性、时效性知识支撑的场景,让大模型具备"知识更新"和"领域专精"的能力。常见应用:
| 场景 | 为什么适合 RAG |
|---|---|
| 企业内部知识库问答 | 私有资料,模型预训练时没见过 |
| 客服 / 产品文档助手 | 答案必须来自官方文档,且文档常更新 |
| 法律 / 医疗 / 金融问答 | 要求可溯源、不能编,错了代价高 |
| 最新资讯 / 实时数据问答 | 超出模型训练数据的时间截止点 |
| 个人知识管理 | 私人笔记,模型绝无可能知道 |
📌 本项目属于最后一类:知识库是"杀戮尖塔个人攻略",其中还有一节纯私有的"爬塔口诀",专门用来验证------模型答对它,只可能是检索生效,不可能是预训练记住。
反过来,什么时候不该用 RAG:知识量小到能直接塞进提示词、需要全局理解整个文档集("总结所有文档的共性")、或纯推理/计算任务------这些 RAG 帮不上或属于过度设计。
三、RAG 的整体流程(最核心)
以「用户提问」为分界点,RAG 分为两个阶段:
| 阶段 | 时机 | 做什么 | 类比 |
|---|---|---|---|
| ① 构建索引 | 提问前(离线) | 把知识库文档变成可检索的向量索引 | 打地基 |
| ② 检索生成 | 提问时(在线) | 实时处理问题,检索资料并生成回答 | 盖房子 |
【① 构建索引 · 离线,只在启动/更新时跑】
原始文档 →[预处理]→ 干净文本 →[分片]→ chunks →[向量化]→ 向量 →[生成索引]→ 向量库
│
──────────────────────── 用户提问,分界线 ──────────────────────────────────┤
│
【② 检索生成 · 在线,每轮提问都跑】 ▼
用户问题 →[向量化]→ 查询向量 →[相似度检索 Top-K]→ 相关 chunks →[注入上下文]→ 大模型 → 回答
第一步:构建索引(提问前,离线)
对知识库文档做一系列操作:文档预处理 → 文档分片 → 向量化 → 生成索引 。这是后续检索生成的基础,索引质量的好坏,极大影响后续检索效果。
四个子环节:
- 文档预处理:把原始文档清洗成"只剩语义、没有排版噪声"的干净文本(去掉 markdown 标记、页眉页脚、去重等)。
- 文档分片(Chunking):把长文档切成一个个片段(chunk)。检索和注入的最小单位都是 chunk。切太大→混主题、稀释相似度;切太小→缺上下文。
- 向量化(Embedding):把每个 chunk 用 embedding 模型转成定长浮点数组。语义越近的文本,向量方向越接近。
- 生成索引:把向量存入向量库并建立索引结构,让后续检索不必全量扫描。
⚠️ 核心警示:垃圾进,垃圾出(Garbage In, Garbage Out, GIGO)
无论后续检索环节做了多高深的优化,数据源头的质量才是影响最终检索效果的最重要因素。 构建索引时喂进去的是垃圾(乱排版、混主题的大 chunk、脏数据),检索出来的也只能是垃圾。
📌 本项目亲身踩到 GIGO 的两个变体(详见01踩坑记录):
- 切分坏了 :一开始直接用框架的
DocumentSplitters.recursive,它会把相邻小块合并 成主题混杂的大 chunk,导致检索排序失真(问铁甲战士,排第一的竟是静默猎手那段)。改成按 markdown##小节切、一节一段才修好。- 预处理缺失 :
**加粗**、>这些排版符号原样进向量是噪声。补了清洗后(在早期那份文档上实测),向量化 token 从 736 降到 704;纯符号不带语义,进向量只会稀释相似度。
第二步:检索生成(提问时,在线)
实时处理用户的问题:
- 问题向量化 :用同一个 embedding 模型把问题转成查询向量(必须同一模型,否则不在一个向量空间,相似度无意义)。
- 相似度检索:在向量库里算查询向量与各 chunk 向量的相似度,取最相似的前 K 个(Top-K)。
- 注入上下文:把命中的 chunk 拼进提示词,作为模型回答的"参考资料"。
- 生成:模型基于问题 + 参考资料生成回答。
📌 本项目的检索生成链路:
EmbeddingStoreContentRetriever(Top-K + 阈值)→DefaultContentInjector(把命中片段拼成【参考资料】+ 来源文件名)→ 模型流式输出。当前知识库 6 段、Top-K=3。
四、构建索引四环节的两个实测陷阱(📌 本项目)
这两点是抽象知识里不会讲、但真做才会撞到的,单列出来:
陷阱 A:检索分数不是余弦相似度
很多框架(含本项目用的 LangChain4j)返回的"分数"是相关度分数(relevance score),不是原始余弦:
relevanceScore = (cosineSimilarity + 1) / 2 // 把余弦的 [-1,1] 线性拉到 [0,1]
没看清这层会把 minScore 阈值调废 :看着"中等"的 0.5,实际是 cos ≥ 0,几乎什么都不过滤。本项目第一版就栽在这里。
陷阱 B:阈值必须贴着"本文档 + 本模型"重新校准
📌 本项目实测(当前知识库):库内提问命中落在 0.744~0.85 ,库外提问(问塞尔达)最高 0.728 ,中间只有约 0.015 的缝隙,因此阈值卡在 0.735。
校准方法 :分别问几个库内问题和库外问题,把阈值卡在"库内最低分"与"库外最高分"之间。缝隙极窄,阈值高一点就出现"库里明明有却答不知道"的假拒答。换文档或换 embedding 模型后必须重测,不能抄经验值。
另外两个实测点:① DashScope 的 OpenAI 兼容模式忽略text_type参数 ,query 和 document 编出的向量逐位相同(对称编码);② DashScope embeddings 单次请求最多 10 段文本,超了直接 400,靠框架自动分批解决。
五、RAG 的三种范式:Naive / Advanced / Modular
RAG 从"能用"到"好用"再到"可扩展",演化出三代范式。本项目实现的是最基础的 Naive RAG。
5.1 Naive RAG(基础 RAG)------ 📌 本项目就是这一种
最基础、最标准的实现,完整体现"检索 + 生成"的核心思想,不加复杂优化策略。流程简单直观,是理解 RAG 机制的最佳起点:
切分 → 向量化 → Top-K 检索 → 注入 → 生成
📌 本项目正是这条最短链路:按
##切、text-embedding-v3向量化、余弦 Top-K、拼进提示词、模型生成。没有混合检索、没有 rerank、没有 query 改写。选它是刻意的------先把 RAG 最本质的一环吃透,再谈优化。
Naive RAG 的天生短板(也正是后两代要解决的):
- 纯向量检索对专有名词弱("树枝手镯""灵动步法"这类词,关键词匹配其实更稳)。
- 用户口语和文档措辞差距大时,召回会掉。
- Top-K 是"粗排",可能混入相关度不高的噪声片段。
5.2 Advanced RAG(进阶 RAG)
在 Naive RAG 基础上引入混合检索与各种优化策略 ,让召回更准、回答更稳。目前企业生产环境用得最多的范式。 常见增强手段:
| 手段 | 作用 |
|---|---|
| 混合检索(Hybrid Retrieval) | 结合关键词检索(BM25/ES)与向量检索,取长补短,提升精确度 |
| 重排序(Re-ranking) | 对召回结果用更精的模型做语义相关性打分再重排,过滤噪声,"精筛选" |
| 上下文压缩(Context Compression) | 对长文本做摘要 / 关键信息提取,降低上下文压力 |
| 多轮记忆(Conversation Memory) | 支持多轮对话,让模型理解上下文语义 |
| 问题重写(Query Rewriting) | 把用户原始 Query 改写成语义更完整、信息更丰富的检索条件 |
Advanced RAG 是让 RAG 从"能用"变"好用"的关键阶段。
📌 对本项目而言,收益最大的一项是混合检索 ------本项目文档全是"主宰/灵动步法/雷暴"这类专有名词,恰是向量检索的软肋,BM25 能明显补强。这是当前最该补的下一步(见01待办)。
5.3 Modular RAG(模块化 RAG)
较新的工程化 形态,强调把 RAG 流程的各组件解耦为可独立配置、替换、组合的模块 ,通过配置或编排灵活拼装。相比 Naive 的"单流程"、Advanced 的"多优化",Modular 更强调可扩展性、灵活性、可维护性。典型模块划分:
| 模块 | 职责 |
|---|---|
| 查询转换模块(Query Transformation) | 对原始查询改写、扩展、分解 |
| 检索模块(Retrieval) | 从知识库/外部源检索候选,支持向量/关键词/混合等多种策略 |
| 重排序模块(Re-ranking) | 基于语义相关性对候选再排序 |
| 上下文构建模块(Context Builder) | 筛选、拼接、压缩检索结果,给模型最优上下文 |
| 生成模块(Generation) | 大模型执行答案生成 |
| 后处理模块(Post-processing) | 事实校验、格式优化、引用标注 |
| 路由模块(Routing) | 按任务类型动态选择模块组合 |
这种"积木式"设计可针对不同任务自由组装,例如:安全情报场景加时效性筛选、法律/医疗场景加事实校验、长文档场景加上下文压缩。
Modular RAG 的优势:
- 每个模块可单独优化/升级,不必重构整个系统。
- 模块间标准化接口交互,职责清晰,便于多人协作与版本管理。
- 支持多源异构数据(联网、知识图谱、API)、动态路由与复杂逻辑组合。
Modular RAG 让 RAG 从"好用"走向"可扩展",代表企业级、系统化 RAG 的未来方向。
📌 一个有意思的连接点:本项目用的 LangChain4j,其DefaultRetrievalAugmentor已经把链路拆成了QueryTransformer(查询转换)/QueryRouter(路由)/ContentAggregator(聚合重排)/ContentInjector(上下文构建)四个可插拔组件 ------这就是 Modular 思想的雏形。本项目目前只替换了ContentInjector(换成中文注入模板),其余全用默认值。也就是说,从 Naive 走向 Advanced/Modular,在本项目里就是逐个填上这几个扩展点。
三种范式对比
| Naive RAG | Advanced RAG | Modular RAG | |
|---|---|---|---|
| 定位 | 能用 | 好用 | 可扩展 |
| 核心 | 检索 + 生成 | 多种优化策略 | 组件解耦 + 编排 |
| 检索 | 纯向量 Top-K | 混合检索 + 重排 | 可插拔多策略 |
| 复杂度 | 低 | 中 | 高 |
| 适用 | 学习 / 原型 / 小知识库 | 企业生产主流 | 大型系统 / 多任务 |
| 📌 本项目 | ✅ 就是这一档 | 差混合检索/rerank/query改写 | 框架已备扩展点,未填 |
六、本项目落地对照(📌 代码为辅)
| 阶段 | 环节 | 本项目实现 | 类 / 方法 |
|---|---|---|---|
| 构建索引 | 加载 | FileSystemDocumentLoader + TextDocumentParser 读 rag-docs/*.md 成 Document |
RagConfig.loadDocuments |
| 构建索引 | 预处理 | 弱化 markdown 标记,保留标题文字与列表内容 | RagConfig.normalizeMarkdown |
| 构建索引 | 分片 | 按 ## 二级标题切,一节 = 一段 |
RagConfig.splitBySection |
| 构建索引 | 向量化 | text-embedding-v3,1024 维,自动分批(≤10) |
OpenAiEmbeddingModel |
| 构建索引 | 生成索引 | 灌进 InMemoryEmbeddingStore |
knowledgeStore |
| 检索生成 | 检索 | 余弦相似度 Top-K + 阈值 | EmbeddingStoreContentRetriever |
| 检索生成 | 注入 | 命中片段拼进【参考资料】+ 文件名 | DefaultContentInjector |
| 检索生成 | 生成 | 提示词强约束"只依据资料回答" | prompt/游戏知识问答.md |
| 全程 | 可观测 | 打印命中片段+分数 / 打印注入后的完整上下文 | LoggingContentRetriever / ChatContextLogger |
⚠️ 关于"生成索引"这一环的诚实说明:本项目用的
InMemoryEmbeddingStore是 O(N) 暴力线性扫描,没有真正的 ANN 索引 (无 HNSW/IVF),且进程退出即丢。8 段规模下建索引收益为负,这样最简单。上万段以上才需要 pgvector / Milvus 这类真正的向量数据库,那时"生成索引"才名副其实(用 HNSW 图把检索降到近似 O(log N))。
七、RAG 与 Function Calling / MCP 的关系(本项目三个模块)
三个模块解决三件正交的事,可叠加、互不替代:
| 模块 | 解决的问题 | 给模型多了什么 | 改模型吗 |
|---|---|---|---|
| Prompt + Function Calling | 让模型做事 | 一个可调用的动作(工具) | 否 |
| MCP | 让工具脱离宿主进程 | 工具从哪来、在哪执行(协议层) | 否 |
| RAG | 让回答有据可依 | 一段问题相关的资料(上下文) | 否 |
共同点:三者都不改模型本身,都是在"喂给模型的输入"上做文章。
📌 本项目工程上也复用同一套骨架:RAG 形态开关用
ObjectProvider<RetrievalAugmentor>,与 MCP 那轮的ObjectProvider<ToolProvider>完全同构------Bean 在不在决定走哪条分支,原有逻辑一行不删。且 RAG 形态刻意不挂工具:知识问答不需要"做事",挂着只是白烧 token。
八、需要内化的三件事
- RAG 分两阶段:构建索引在提问前(预处理→分片→向量化→生成索引),检索生成在提问时(问题向量化→Top-K 检索→注入→生成);GIGO 说的是第一阶段质量决定一切。
- 三种范式是一条演化线:Naive(检索+生成,能用)→ Advanced(混合检索+重排+改写,好用)→ Modular(组件解耦+编排,可扩展)。本项目是 Naive,往上走就是逐个填检索/重排/改写这些扩展点。
- RAG 不改模型,只改上下文------和 Function Calling / MCP 一样都在模型输入侧做文章,三者正交可叠加;RAG 的效果上限由"检索准不准"决定,而检索准不准的根子在构建索引的质量(GIGO)。