From 25a17179a0795680717f92fda5918e2556f30d24 Mon Sep 17 00:00:00 2001 From: youyeyejie Date: Thu, 28 May 2026 16:02:48 +0800 Subject: [PATCH 1/2] feat: init and add chapter rag contents --- .gitignore | 1 + AGENTS.md | 133 -------- docs/assets/mermaid/.cache-key | 2 +- .../chat-products_mermaid_0_d921ced9.svg | 2 +- .../failure-cases_mermaid_0_6cb86e51.svg | 2 +- .../mermaid/index_mermaid_0_4bfd489a.svg | 2 +- .../mermaid/index_mermaid_1_4c49cd69.svg | 2 +- .../machine-learning_mermaid_2_6c878947.svg | 2 +- ...model-data-training_mermaid_0_66650f38.svg | 2 +- ...model-data-training_mermaid_1_fdc2dfb9.svg | 2 +- .../prompt-basic_mermaid_0_72623ff4.svg | 2 +- .../prompt-basic_mermaid_1_5d4e2cd9.svg | 2 +- .../prompt-basic_mermaid_2_4fa92616.svg | 2 +- .../stable-output_mermaid_0_a4883ca0.svg | 2 +- .../stable-output_mermaid_1_b549843d.svg | 2 +- .../mermaid/structure_mermaid_0_39cfc0b3.svg | 2 +- .../mermaid/tasks_mermaid_0_f5e00efb.svg | 2 +- ...emperature-sampling_mermaid_0_1fb265a4.svg | 2 +- ...emperature-sampling_mermaid_1_87f42f11.svg | 2 +- ...emperature-sampling_mermaid_2_505dced2.svg | 2 +- ...emperature-sampling_mermaid_3_b20d5792.svg | 2 +- ...emperature-sampling_mermaid_4_bfb6189a.svg | 2 +- .../mermaid/templates_mermaid_0_bf562375.svg | 2 +- ...n-embedding-context_mermaid_0_8e1cc234.svg | 2 +- ...n-embedding-context_mermaid_1_898036d2.svg | 2 +- ...n-embedding-context_mermaid_2_790c3adb.svg | 2 +- docs/rag/chunking.md | 247 ++++++++++++++- docs/rag/generation.md | 231 +++++++++++++- docs/rag/index.md | 216 +++++++++++-- docs/rag/rerank.md | 195 +++++++++++- docs/rag/retrieval.md | 210 ++++++++++++- docs/rag/troubleshooting.md | 287 ++++++++++++++++- docs/rag/vectorization.md | 295 +++++++++++++++++- docs/rag/why-rag.md | 140 ++++++++- 34 files changed, 1773 insertions(+), 230 deletions(-) delete mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index 554c174..d53c9d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # WorkBuddy / local assistant state .workbuddy/ +AGENTS.md # Temporary research clone research/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index ac20bdd..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,133 +0,0 @@ -# Hello-AI Agent 写作指南 - -## 项目定位 - -Hello-AI 是面向中文小白的 AI / LLM 入门知识库。仓库即唯一内容源,MkDocs Material 构建为静态站点。 - -## 写作原则(必须遵守) - -1. **先讲问题,再讲概念**:不要一上来堆定义,先说明这个概念解决什么困惑。 -2. **先路径,后百科**:告诉读者先学什么、后学什么、学到什么程度即可。 -3. **先能用,后讲原理**:先做出结果,再解释底层机制。 -4. **面向小白,但不装傻**:讲清楚,但不啰嗦;有例子,但不堆术语;可以口语化,但不要油。 -5. **每个新术语首次出现时**:提供中文解释 + 英文原文 + 简短定义 + 一个例子。 -6. **涉及模型/产品/API/数据时**:必须标注来源;如果是推测,明确写"推测"或"待验证"。 -7. **安全边界要写清楚**:涉及 API Key、数据上传、模型输出、自动化调用时,要提醒风险。 -8. **避免制造焦虑**:目标是降低入门门槛,不是用概念堆叠劝退新手。 - -## 内容结构模板 - -每篇正文必须包含以下结构(根据内容适当调整): - -```markdown -# 标题 - -> 一句话概括这页讲什么。 - -## 这章解决什么问题 - -(1-2 段,说明读者为什么要花时间读这页) - -## 核心概念解释 - -### 概念 A -... - -### 概念 B -... - -## 最小示例 / 动手试试 - -(给出可复现的代码、Prompt 示例或操作步骤) - -## 常见误区 - -(至少 3 个常见误解 + 纠正) - -## 延伸阅读 - -(链接到本仓库其他相关页面) - -## 练习题 / 小实验 - -(至少 1 道思考题或动手实验) -``` - -## 插图规范 - -### Mermaid 图(优先使用) - -以下类型的图优先用 Mermaid 直接在 Markdown 中绘制: - -- 流程图 (`flowchart` / `graph`) -- 时序图 (`sequenceDiagram`) -- 类图 (`classDiagram`) -- 状态图 (`stateDiagram-v2`) -- 用户旅程图 (`journey`) -- 甘特图 (`gantt`) -- 饼图 (`pie`) -- Git 图 (`gitGraph`) -- 思维导图 (`mindmap`) -- 时间线 (`timeline`) -- 象限图 (`quadrantChart`) -- 需求图 (`requirementDiagram`) -- C4 图 (`C4Context` / `C4Container`) -- 桑基图 (`sankey-beta`) -- XY 图表 (`xychart-beta`) -- 块图 (`block-beta`) -- 数据包图 (`packet-beta`) -- 架构图 (`architecture-beta`) - -Mermaid 示例: -```markdown -```mermaid -flowchart LR - A[输入] --> B{判断} - B -->|条件1| C[结果1] - B -->|条件2| D[结果2] -``` -``` - -### 外部图片(按需下载) - -当概念需要真实照片、产品截图、历史图片等时,可搜索并下载图片。 - -**命名规范**:`assets/images/<所属章节>/<位置_内容描述>.png` - -例如: -- `assets/images/basics/what-is-ai/dartmouth_conference_1956.png` -- `assets/images/basics/deep-learning/alexnet_architecture.png` - -**引用方式**: -```markdown -![描述文字](../assets/images/basics/xxx/xxx.png) - -
图X:图片描述
-``` - -## 标签规范 - -每篇新页面顶部可添加标签: - -```yaml ---- -tags: - - AI 基础 ---- -``` - -可用标签见 `mkdocs.yml` 的 `material/tags` 配置。 - -## 事实核验规则 - -- 模型名称要写清版本(如 GPT-4、Claude 3.5 Sonnet)。 -- API 和工具要标明更新时间。 -- 关键页面保留参考链接。 -- 过期内容要有版本意识,注明"截至 XXXX 年 X 月"。 - -## 格式约定 - -- 中英文之间加空格。 -- 代码块标注语言类型。 -- 列表层级清晰,不要嵌套超过 3 层。 -- 每页长度建议 1500~4000 字,不要太短也不要太长。 diff --git a/docs/assets/mermaid/.cache-key b/docs/assets/mermaid/.cache-key index c774b6c..eccee7c 100644 --- a/docs/assets/mermaid/.cache-key +++ b/docs/assets/mermaid/.cache-key @@ -1 +1 @@ -be1bcd46e3b02a4fa2bba5cb7ebd9f6159b6b4050fe3744688f277d889372423 +866dfc20ff03579b4ed9fe88ba6b151b5790e9c1c1aec829d93657714545660f diff --git a/docs/assets/mermaid/chat-products_mermaid_0_d921ced9.svg b/docs/assets/mermaid/chat-products_mermaid_0_d921ced9.svg index 386b359..927df65 100644 --- a/docs/assets/mermaid/chat-products_mermaid_0_d921ced9.svg +++ b/docs/assets/mermaid/chat-products_mermaid_0_d921ced9.svg @@ -1 +1 @@ -提出问题回答打字提问大语言模型训练海量词汇学会了“下一个词最可能是什么”回答显示在屏幕上 \ No newline at end of file +提出问题回答打字提问大语言模型训练海量词汇学会了“下一个词最可能是什么”回答显示在屏幕上 \ No newline at end of file diff --git a/docs/assets/mermaid/failure-cases_mermaid_0_6cb86e51.svg b/docs/assets/mermaid/failure-cases_mermaid_0_6cb86e51.svg index fc8065e..5f5390d 100644 --- a/docs/assets/mermaid/failure-cases_mermaid_0_6cb86e51.svg +++ b/docs/assets/mermaid/failure-cases_mermaid_0_6cb86e51.svg @@ -1 +1 @@ -模型输出不符合预期需求说清了吗?补充角色/任务/约束/格式要求是否自相矛盾?删除冲突约束,重新表述一次给了太多任务?拆成多轮,或明确分步骤需要特定风格/格式却没给示例?添加 1~2 Few-shot 示例问题是否超出模型知识范围补充背景信息,或要求标注不确定性是否涉及精确计算/长逻辑链?让模型写代码,或调用计算工具检查 Temperature/Top-p等生成参数 \ No newline at end of file +模型输出不符合预期需求说清了吗?补充角色/任务/约束/格式要求是否自相矛盾?删除冲突约束,重新表述一次给了太多任务?拆成多轮,或明确分步骤需要特定风格/格式却没给示例?添加 1~2 Few-shot 示例问题是否超出模型知识范围补充背景信息,或要求标注不确定性是否涉及精确计算/长逻辑链?让模型写代码,或调用计算工具检查 Temperature/Top-p等生成参数 \ No newline at end of file diff --git a/docs/assets/mermaid/index_mermaid_0_4bfd489a.svg b/docs/assets/mermaid/index_mermaid_0_4bfd489a.svg index 6fd8e78..de7c5bd 100644 --- a/docs/assets/mermaid/index_mermaid_0_4bfd489a.svg +++ b/docs/assets/mermaid/index_mermaid_0_4bfd489a.svg @@ -1 +1 @@ -直接影响你的需求Prompt任务说明书AI 模型输出结果Prompt 质量 \ No newline at end of file +直接影响你的需求Prompt任务说明书AI 模型输出结果Prompt 质量 \ No newline at end of file diff --git a/docs/assets/mermaid/index_mermaid_1_4c49cd69.svg b/docs/assets/mermaid/index_mermaid_1_4c49cd69.svg index fffba13..cdeb56d 100644 --- a/docs/assets/mermaid/index_mermaid_1_4c49cd69.svg +++ b/docs/assets/mermaid/index_mermaid_1_4c49cd69.svg @@ -1 +1 @@ -系统学习路径(2 小时)Prompt 基础结构与格式任务类型模板积累稳定输出失败排查快速上手路径(30 分钟)Prompt 基础常见失败案例 \ No newline at end of file +系统学习路径(2 小时)Prompt 基础结构与格式任务类型模板积累稳定输出失败排查快速上手路径(30 分钟)Prompt 基础常见失败案例 \ No newline at end of file diff --git a/docs/assets/mermaid/machine-learning_mermaid_2_6c878947.svg b/docs/assets/mermaid/machine-learning_mermaid_2_6c878947.svg index 3247c8d..d4f7c59 100644 --- a/docs/assets/mermaid/machine-learning_mermaid_2_6c878947.svg +++ b/docs/assets/mermaid/machine-learning_mermaid_2_6c878947.svg @@ -1 +1 @@ -很好很差都很差原始数据集训练集 80%测试集 20%模型训练训练好的模型在测试集上表现如何?模型可用过拟合:调整模型复杂度或增加数据欠拟合:换更复杂的模型或增加特征 \ No newline at end of file +很好很差都很差原始数据集训练集 80%测试集 20%模型训练训练好的模型在测试集上表现如何?模型可用过拟合:调整模型复杂度或增加数据欠拟合:换更复杂的模型或增加特征 \ No newline at end of file diff --git a/docs/assets/mermaid/model-data-training_mermaid_0_66650f38.svg b/docs/assets/mermaid/model-data-training_mermaid_0_66650f38.svg index 6c2bca3..f2860dd 100644 --- a/docs/assets/mermaid/model-data-training_mermaid_0_66650f38.svg +++ b/docs/assets/mermaid/model-data-training_mermaid_0_66650f38.svg @@ -1 +1 @@ -准备训练数据模型前向计算预测输出计算损失预测 vs 真实差距反向传播调整参数是否收敛?训练完成得到模型权重 \ No newline at end of file +准备训练数据模型前向计算预测输出计算损失预测 vs 真实差距反向传播调整参数是否收敛?训练完成得到模型权重 \ No newline at end of file diff --git a/docs/assets/mermaid/model-data-training_mermaid_1_fdc2dfb9.svg b/docs/assets/mermaid/model-data-training_mermaid_1_fdc2dfb9.svg index 6a5961e..f8237c6 100644 --- a/docs/assets/mermaid/model-data-training_mermaid_1_fdc2dfb9.svg +++ b/docs/assets/mermaid/model-data-training_mermaid_1_fdc2dfb9.svg @@ -1 +1 @@ - 推理阶段(Inference)🔧 训练阶段(Training)参数固定后用于输入训练数据模型预测对比真实答案计算误差并调整参数反复迭代数百万次输出:一组训练好的参数输入新问题加载训练好的参数模型直接计算输出输出:回答/预测/生成内容 \ No newline at end of file + 推理阶段(Inference)🔧 训练阶段(Training)参数固定后用于输入训练数据模型预测对比真实答案计算误差并调整参数反复迭代数百万次输出:一组训练好的参数输入新问题加载训练好的参数模型直接计算输出输出:回答/预测/生成内容 \ No newline at end of file diff --git a/docs/assets/mermaid/prompt-basic_mermaid_0_72623ff4.svg b/docs/assets/mermaid/prompt-basic_mermaid_0_72623ff4.svg index 2b15343..bf9e7b9 100644 --- a/docs/assets/mermaid/prompt-basic_mermaid_0_72623ff4.svg +++ b/docs/assets/mermaid/prompt-basic_mermaid_0_72623ff4.svg @@ -1 +1 @@ -同样的模型能力差的 Prompt好的 Prompt模糊、冗长、偏离需求清晰、准确、符合预期 \ No newline at end of file +同样的模型能力差的 Prompt好的 Prompt模糊、冗长、偏离需求清晰、准确、符合预期 \ No newline at end of file diff --git a/docs/assets/mermaid/prompt-basic_mermaid_1_5d4e2cd9.svg b/docs/assets/mermaid/prompt-basic_mermaid_1_5d4e2cd9.svg index f2ad57a..bef95df 100644 --- a/docs/assets/mermaid/prompt-basic_mermaid_1_5d4e2cd9.svg +++ b/docs/assets/mermaid/prompt-basic_mermaid_1_5d4e2cd9.svg @@ -1 +1 @@ -一个完整的 Prompt角色Role任务Task约束Constraint输出格式Output Format示例Example高质量输出 \ No newline at end of file +一个完整的 Prompt角色Role任务Task约束Constraint输出格式Output Format示例Example高质量输出 \ No newline at end of file diff --git a/docs/assets/mermaid/prompt-basic_mermaid_2_4fa92616.svg b/docs/assets/mermaid/prompt-basic_mermaid_2_4fa92616.svg index c7d171b..7a3a825 100644 --- a/docs/assets/mermaid/prompt-basic_mermaid_2_4fa92616.svg +++ b/docs/assets/mermaid/prompt-basic_mermaid_2_4fa92616.svg @@ -1 +1 @@ -模型固有能力Prompt 的作用把能力引导到对的地方不能创造没有的能力好的结果需要换模型或工具 \ No newline at end of file +模型固有能力Prompt 的作用把能力引导到对的地方不能创造没有的能力好的结果需要换模型或工具 \ No newline at end of file diff --git a/docs/assets/mermaid/stable-output_mermaid_0_a4883ca0.svg b/docs/assets/mermaid/stable-output_mermaid_0_a4883ca0.svg index 02ff056..c502593 100644 --- a/docs/assets/mermaid/stable-output_mermaid_0_a4883ca0.svg +++ b/docs/assets/mermaid/stable-output_mermaid_0_a4883ca0.svg @@ -1 +1 @@ -你的 Prompt模型计算每个候选词的概率采样环节按概率抽奖选词生成下一个词把新词加入上下文 \ No newline at end of file +你的 Prompt模型计算每个候选词的概率采样环节按概率抽奖选词生成下一个词把新词加入上下文 \ No newline at end of file diff --git a/docs/assets/mermaid/stable-output_mermaid_1_b549843d.svg b/docs/assets/mermaid/stable-output_mermaid_1_b549843d.svg index 86a4eb1..c82f2fe 100644 --- a/docs/assets/mermaid/stable-output_mermaid_1_b549843d.svg +++ b/docs/assets/mermaid/stable-output_mermaid_1_b549843d.svg @@ -1 +1 @@ -没有输出不稳定检查 Prompt 是否有歧义或矛盾重写 Prompt消除歧义加入 Few-shot示例 1~3要求强格式输出JSON/Markdown/编号列表添加具体约束字数/结构/必须包含或排除复杂任务用 CoT分步骤拆解调整 Temperature降至 0~0.3使用 System Prompt设定全局行为API 调用时固定 seed 参数 \ No newline at end of file +没有输出不稳定检查 Prompt 是否有歧义或矛盾重写 Prompt消除歧义加入 Few-shot示例 1~3要求强格式输出JSON/Markdown/编号列表添加具体约束字数/结构/必须包含或排除复杂任务用 CoT分步骤拆解调整 Temperature降至 0~0.3使用 System Prompt设定全局行为API 调用时固定 seed 参数 \ No newline at end of file diff --git a/docs/assets/mermaid/structure_mermaid_0_39cfc0b3.svg b/docs/assets/mermaid/structure_mermaid_0_39cfc0b3.svg index 49bf645..f6ea97c 100644 --- a/docs/assets/mermaid/structure_mermaid_0_39cfc0b3.svg +++ b/docs/assets/mermaid/structure_mermaid_0_39cfc0b3.svg @@ -1 +1 @@ -一个完整的 Prompt决定知识范围和语气明确执行目标限定执行方式固化输出形态间接影响🎭 角色 Role你是谁?🎯 任务 Task你要做什么? 约束 Constraint边界和规则📐 输出格式 OutputFormat结果长什么样? 稳定、可预期的结果 \ No newline at end of file +一个完整的 Prompt决定知识范围和语气明确执行目标限定执行方式固化输出形态间接影响🎭 角色 Role你是谁?🎯 任务 Task你要做什么? 约束 Constraint边界和规则📐 输出格式 OutputFormat结果长什么样? 稳定、可预期的结果 \ No newline at end of file diff --git a/docs/assets/mermaid/tasks_mermaid_0_f5e00efb.svg b/docs/assets/mermaid/tasks_mermaid_0_f5e00efb.svg index 0e7e471..5b9edfc 100644 --- a/docs/assets/mermaid/tasks_mermaid_0_f5e00efb.svg +++ b/docs/assets/mermaid/tasks_mermaid_0_f5e00efb.svg @@ -1 +1 @@ -只保留核心要点换种说法,意思不变只想知道它属于哪一类只想提取特定信息我有一段文本我想保留多少原文?总结 Summarization改写 Rewriting分类 Classification抽取 Extraction输出:更短的版本输出:同义但不同风格的版输出:类别标签 + 理由输出:结构化数据JSON/表格 \ No newline at end of file +只保留核心要点换种说法,意思不变只想知道它属于哪一类只想提取特定信息我有一段文本我想保留多少原文?总结 Summarization改写 Rewriting分类 Classification抽取 Extraction输出:更短的版本输出:同义但不同风格的版输出:类别标签 + 理由输出:结构化数据JSON/表格 \ No newline at end of file diff --git a/docs/assets/mermaid/temperature-sampling_mermaid_0_1fb265a4.svg b/docs/assets/mermaid/temperature-sampling_mermaid_0_1fb265a4.svg index 3232de5..83c1433 100644 --- a/docs/assets/mermaid/temperature-sampling_mermaid_0_1fb265a4.svg +++ b/docs/assets/mermaid/temperature-sampling_mermaid_0_1fb265a4.svg @@ -1 +1 @@ -模型输出原始分数 logits除以 Temperaturelogits / Tsoftmax 转换变成概率分布从概率分布中采样下一个词 \ No newline at end of file +模型输出原始分数 logits除以 Temperaturelogits / Tsoftmax 转换变成概率分布从概率分布中采样下一个词 \ No newline at end of file diff --git a/docs/assets/mermaid/temperature-sampling_mermaid_1_87f42f11.svg b/docs/assets/mermaid/temperature-sampling_mermaid_1_87f42f11.svg index c8b46aa..3a5d4f6 100644 --- a/docs/assets/mermaid/temperature-sampling_mermaid_1_87f42f11.svg +++ b/docs/assets/mermaid/temperature-sampling_mermaid_1_87f42f11.svg @@ -1 +1 @@ -模型预测下一词词汇表 50000 个候选按概率排序Top-k = 5只保留前 5其余概率设为 0重新归一化概率从这 5 个中采样 \ No newline at end of file +模型预测下一词词汇表 50000 个候选按概率排序Top-k = 5只保留前 5其余概率设为 0重新归一化概率从这 5 个中采样 \ No newline at end of file diff --git a/docs/assets/mermaid/temperature-sampling_mermaid_2_505dced2.svg b/docs/assets/mermaid/temperature-sampling_mermaid_2_505dced2.svg index d2e8558..1cc02b9 100644 --- a/docs/assets/mermaid/temperature-sampling_mermaid_2_505dced2.svg +++ b/docs/assets/mermaid/temperature-sampling_mermaid_2_505dced2.svg @@ -1 +1 @@ -模型预测下一词按概率从高到低排序依次累加概率累计概率 = Top-p?保留到这里的候选继续累加从这堆候选中采样 \ No newline at end of file +模型预测下一词按概率从高到低排序依次累加概率累计概率 = Top-p?保留到这里的候选继续累加从这堆候选中采样 \ No newline at end of file diff --git a/docs/assets/mermaid/temperature-sampling_mermaid_3_b20d5792.svg b/docs/assets/mermaid/temperature-sampling_mermaid_3_b20d5792.svg index c92d5bf..f4187cc 100644 --- a/docs/assets/mermaid/temperature-sampling_mermaid_3_b20d5792.svg +++ b/docs/assets/mermaid/temperature-sampling_mermaid_3_b20d5792.svg @@ -1 +1 @@ -原始 logitsTemperature 调整Top-k 截断Top-p 截断最终采样 \ No newline at end of file +原始 logitsTemperature 调整Top-k 截断Top-p 截断最终采样 \ No newline at end of file diff --git a/docs/assets/mermaid/temperature-sampling_mermaid_4_bfb6189a.svg b/docs/assets/mermaid/temperature-sampling_mermaid_4_bfb6189a.svg index 3641607..cbaf5ec 100644 --- a/docs/assets/mermaid/temperature-sampling_mermaid_4_bfb6189a.svg +++ b/docs/assets/mermaid/temperature-sampling_mermaid_4_bfb6189a.svg @@ -1 +1 @@ -代码/数学/事实保守摘要/翻译中等日常对话创意/头脑风暴开始输出需要严格正确吗?Temperature 0~0.3Top-p 0.1~0.5需要多大创意空间?Temperature 0.3~0.5Top-p 0.7~0.9Temperature 0.5~0.7Top-p 0.9Temperature 0.8~1.2Top-p 0.9~0.95 \ No newline at end of file +代码/数学/事实保守摘要/翻译中等日常对话创意/头脑风暴开始输出需要严格正确吗?Temperature 0~0.3Top-p 0.1~0.5需要多大创意空间?Temperature 0.3~0.5Top-p 0.7~0.9Temperature 0.5~0.7Top-p 0.9Temperature 0.8~1.2Top-p 0.9~0.95 \ No newline at end of file diff --git a/docs/assets/mermaid/templates_mermaid_0_bf562375.svg b/docs/assets/mermaid/templates_mermaid_0_bf562375.svg index fce3092..add9566 100644 --- a/docs/assets/mermaid/templates_mermaid_0_bf562375.svg +++ b/docs/assets/mermaid/templates_mermaid_0_bf562375.svg @@ -1 +1 @@ -我有一个需求要用模型帮忙需要模型扮演特定角色进行互动吗?角色扮演型模板任务是生成文字内容吗?有明确的写作体裁吗?写作型模板多轮对话型模板任务涉及代码吗?代码型模板任务是对现有信息做分析处理吗?分析型模板问答型模板 \ No newline at end of file +我有一个需求要用模型帮忙需要模型扮演特定角色进行互动吗?角色扮演型模板任务是生成文字内容吗?有明确的写作体裁吗?写作型模板多轮对话型模板任务涉及代码吗?代码型模板任务是对现有信息做分析处理吗?分析型模板问答型模板 \ No newline at end of file diff --git a/docs/assets/mermaid/token-embedding-context_mermaid_0_8e1cc234.svg b/docs/assets/mermaid/token-embedding-context_mermaid_0_8e1cc234.svg index 5a47ea5..01f2dfe 100644 --- a/docs/assets/mermaid/token-embedding-context_mermaid_0_8e1cc234.svg +++ b/docs/assets/mermaid/token-embedding-context_mermaid_0_8e1cc234.svg @@ -1 +1 @@ -输入文本今天天气很好Tokenizer切成 TokenEmbedding每个 Token 向量Transformer 模型计算、推理、生成输出 Token还原成文字 \ No newline at end of file +输入文本今天天气很好Tokenizer切成 TokenEmbedding每个 Token 向量Transformer 模型计算、推理、生成输出 Token还原成文字 \ No newline at end of file diff --git a/docs/assets/mermaid/token-embedding-context_mermaid_1_898036d2.svg b/docs/assets/mermaid/token-embedding-context_mermaid_1_898036d2.svg index 34f235e..22b2c7e 100644 --- a/docs/assets/mermaid/token-embedding-context_mermaid_1_898036d2.svg +++ b/docs/assets/mermaid/token-embedding-context_mermaid_1_898036d2.svg @@ -1 +1 @@ -上下文窗口示意用户输入 200 Token模型回复 1 150 Token用户输入 2 100 Token模型回复 2 180 Token...累计 Token不能超过窗口上限 \ No newline at end of file +上下文窗口示意用户输入 200 Token模型回复 1 150 Token用户输入 2 100 Token模型回复 2 180 Token...累计 Token不能超过窗口上限 \ No newline at end of file diff --git a/docs/assets/mermaid/token-embedding-context_mermaid_2_790c3adb.svg b/docs/assets/mermaid/token-embedding-context_mermaid_2_790c3adb.svg index fe6ff03..3827ee2 100644 --- a/docs/assets/mermaid/token-embedding-context_mermaid_2_790c3adb.svg +++ b/docs/assets/mermaid/token-embedding-context_mermaid_2_790c3adb.svg @@ -1 +1 @@ -输出阶段处理阶段表示阶段输入阶段原始文本TokenizerToken 序列如:你 / / 世界Embedding向量序列每个 Token 变成数字向量Transformer 网络在上下文窗口内计算注意力、生成输出输出 Token 序列还原成文字 \ No newline at end of file +输出阶段处理阶段表示阶段输入阶段原始文本TokenizerToken 序列如:你 / / 世界Embedding向量序列每个 Token 变成数字向量Transformer 网络在上下文窗口内计算注意力、生成输出输出 Token 序列还原成文字 \ No newline at end of file diff --git a/docs/rag/chunking.md b/docs/rag/chunking.md index 9321544..b7cce9d 100644 --- a/docs/rag/chunking.md +++ b/docs/rag/chunking.md @@ -1,10 +1,245 @@ +--- +tags: + - RAG +--- + # 文档切分 -> 占位页:本页内容待团队补充。 +> 文档切分(Chunking)是把长文本切成小片段的过程——切得好,检索才能找得准;切得不好,后面的所有优化都救不回来。 + +## 这章解决什么问题 + +想象你要从一本 500 页的说明书里找到「保修期限」那一段。如果说明书被撕成了几百张纸条,每张只有一句话——你可能找不到完整的上下文。如果每张纸条是一整章——那你要的信息又和其他内容混在一起,不够精确。 + +**文档切分就是找这个「平衡点」**:让每个片段足够小,能精确匹配用户问题;又足够大,包含完整的上下文信息。 + +这是 RAG 系统里最重要的优化点之一。原因很简单:后续的检索、重排、生成都是建立在这些片段之上的。**切分策略直接决定了检索质量的上限。** + +## 核心概念 + +### 切分粒度 + +切分的「粗细」直接影响检索效果: + +| 粒度 | 典型大小 | 优点 | 缺点 | +|------|---------|------|------| +| **粗粒度** | 按章节或段落 | 上下文完整,不会切断关键语句 | 可能包含无关信息,检索精度下降 | +| **中粒度** | 固定字符数(如 300~500) | 统一,便于批量处理 | 可能从句子中间切断 | +| **细粒度** | 按句子或语义段落 | 精确匹配用户问题 | 缺少上下文,信息可能不完整 | + +没有绝对正确的粒度,取决于你的文档类型和用户提问方式。一般建议从 300~500 个 token 开始,根据检索效果调整。 + +### 常见的切分策略 + +**策略 1:固定长度切分** + +这是最简单的方案,按字符数或 Token 数硬切: + +```python +def fixed_length_chunk(text, chunk_size=500, overlap=50): + """按固定字符数切分,带重叠""" + chunks = [] + start = 0 + while start < len(text): + end = start + chunk_size + chunks.append(text[start:end]) + start = end - overlap # 前一个片段的尾部与后一个片段的头部重叠 + return chunks +``` + +- 优点:实现简单,性能好 +- 缺点:可能从句子中间切断,丢失语义 + +**策略 2:递归字符切分(Recursive Character Splitter)** + +LangChain 的 `RecursiveCharacterTextSplitter` 是最常用的方案。它按优先级依次尝试分隔符:`\n\n`(段落) → `\n`(换行) → `。`(句号) → ` `(空格),尽可能在语义边界处切分: + +```python +from langchain.text_splitter import RecursiveCharacterTextSplitter + +text = """ +# 第一章:RAG 简介 + +RAG 是检索增强生成。它通过检索外部知识来辅助生成。 + +## 核心组件 + +RAG 包含三个核心组件:检索器、生成器、知识库。 + +### 检索器 + +检索器负责根据用户问题找到最相关的文档片段。 +""" + +splitter = RecursiveCharacterTextSplitter( + chunk_size=100, + chunk_overlap=20, + separators=["\n\n", "\n", "。", " ", ""], +) + +chunks = splitter.split_text(text) +for i, chunk in enumerate(chunks): + print(f"--- Chunk {i+1} ({len(chunk)} 字符) ---") + print(chunk) +``` + +**策略 3:语义切分(Semantic Chunking)** + +利用 Embedding 模型探测段落之间的语义边界。当相邻几个句子的向量相似度有明显下降时,说明话题切换了,在此处切分: + +```python +from langchain_experimental.text_splitter import SemanticChunker +from langchain_openai.embeddings import OpenAIEmbeddings + +splitter = SemanticChunker( + OpenAIEmbeddings(model="text-embedding-3-small"), + breakpoint_threshold_type="percentile" +) +chunks = splitter.split_text(document) +``` + +**策略 4:按文档结构切分** + +对于有固定结构的文档(Markdown、HTML、代码),按标题层级切分最自然: + +```python +from langchain.text_splitter import MarkdownHeaderTextSplitter + +headers_to_split_on = [ + ("#", "H1"), + ("##", "H2"), + ("###", "H3"), +] +splitter = MarkdownHeaderTextSplitter(headers_to_split_on) +chunks = splitter.split_text(markdown_document) +``` + +这种方式保证每个 chunk 在语义上是一个完整的「小节」,最适合结构化文档。 + +### 重叠策略(Overlap) + +切分时让相邻片段有一部分重叠,避免关键信息恰好在切分边界处丢失: + +```mermaid +flowchart LR + A[原始文档] --> B["Chunk 1
500 token"] + A --> C["Chunk 2
500 token
(与前一段重叠 50)"] + A --> D["Chunk 3
500 token
(与前一段重叠 50)"] + B -.->|重叠 50| C + C -.->|重叠 50| D +``` + +- 重叠大小通常设为 chunk 大小的 **10%~20%** +- 重叠太少:边界信息仍有丢失风险 +- 重叠太多:索引体积增大,检索时重复内容增多,浪费上下文窗口 + +### Chunk 大小怎么定 + +chunk 大小没有一个标准值,但有一些经验参考: + +| 文档类型 | 建议 chunk 大小(token) | 理由 | +|---------|------------------------|------| +| 新闻文章 | 200~400 | 每段信息密度高,不宜太大 | +| 技术文档 | 300~500 | 需要保留完整的技术说明 | +| 学术论文 | 500~1000 | 摘要/方法/结论各成独立 chunk | +| 代码文件 | 按函数或类切 | 代码的逻辑单元是函数 | +| 对话记录 | 按轮次切 | 一问一答是一个语义单元 | + +最可靠的方案是做 A/B 测试:准备一组典型问题,分别用不同 chunk 大小检索,对比召回率。 + +## 最小示例 + +以下代码演示如何评估不同切分策略的效果: + +```python +import openai +import numpy as np + +# 原始文档 +document = """ +# RAG 入门指南 + +## 什么是 RAG + +RAG(检索增强生成)是一种让 LLM 先查资料再回答的技术。 + +## 为什么需要 RAG + +LLM 不知道你的私有数据,RAG 通过检索来解决这个问题。 + +## 核心组件 + +一个 RAG 系统由检索器、知识库和生成器三部分组成。 +""" + +# 尝试不同的切分策略 +from langchain.text_splitter import ( + RecursiveCharacterTextSplitter, + MarkdownHeaderTextSplitter, +) + +# 策略 A:递归字符切分 +r_splitter = RecursiveCharacterTextSplitter( + chunk_size=50, chunk_overlap=10 +) +chunks_a = r_splitter.split_text(document) + +# 策略 B:按标题切分 +h_splitter = MarkdownHeaderTextSplitter([ + ("#", "H1"), ("##", "H2") +]) +chunks_b = h_splitter.split_text(document) + +print("策略 A 结果:") +for i, c in enumerate(chunks_a): + print(f" [{i}] {c[:40]}...") + +print("\n策略 B 结果:") +for i, c in enumerate(chunks_b): + print(f" [{i}] {c.page_content[:40]}...") +``` + +## 常见误区 + +!!! failure "误区 1:chunk 越大越好" + chunk 越大,语义越完整,但检索精度越低。太大的 chunk 导致很多不相关的内容一起被搜到,LLM 会被噪声干扰。建议从 300~500 token 开始测试。 + +!!! failure "误区 2:所有文档用同一种切分策略" + Markdown 文档适合按标题切,法律合同适合按条款切,代码适合按函数切。一刀切的策略往往效果最差。 + +!!! failure "误区 3:切完就不管了" + 切分策略需要持续调试。建议定期采样检查 chunk 是否语义完整,用典型问题测试检索效果,根据实际反馈调整参数。 + +## 延伸阅读 + +- [向量化](vectorization.md) —— 切分后的文本如何变成向量 +- [检索](retrieval.md) —— 切分质量直接影响检索效果 +- [RAG 常见问题](troubleshooting.md) + +## 练习题 + +??? question "练习 1:评估不同切分策略" + + 找一篇 2000 字以上的文章(比如维基百科条目),用以下三种方式各切一次,并回答后续问题: + + ``` + - 方式 A:每 200 字符固定长度,无重叠 + - 方式 B:每 200 字符,50 字符重叠 + - 方式 C:按段落(\n\n 分隔符) + ``` + + 对每种方式随机挑 3 个 chunk,判断: + + 1. 这个 chunk 的语义是否完整?有没有被切断的句子? + 2. 如果拿这个 chunk 去回答相关问题,信息够用吗? + 3. 哪种方式产生的 chunks 整体质量最高? + +??? question "练习 2:给不同文档选策略" + + 假设你需要为以下三类文档设计切分策略: -## 建议内容 + - 一本编程书籍(Markdown 格式,有标题层级) + - 一年的客服对话记录(每段对话以时间戳开头) + - 一份法律合同(没有标题,但有条款编号 1.1, 1.2...) -- 切分粒度 -- 重叠策略 -- 常见错误 -- 调试方法 + 对每类文档,你会选择哪种切分策略?为什么? diff --git a/docs/rag/generation.md b/docs/rag/generation.md index bd8609f..ef94dda 100644 --- a/docs/rag/generation.md +++ b/docs/rag/generation.md @@ -1,10 +1,227 @@ -# 生成 +--- +tags: + - RAG +--- -> 占位页:本页内容待团队补充。 +# 生成:LLM 基于资料组织答案 -## 建议内容 +> 生成(Generation)是 RAG 的最后一步——把检索到的资料和用户问题拼成 Prompt,让 LLM 基于事实生成答案。 -- 上下文拼接 -- 引用来源 -- 答案格式 -- 幻觉控制 +## 这章解决什么问题 + +假设你已经做好了所有前置工作:文档切分得漂亮、向量化精确、检索返回了最相关的 3 个片段。但如果 Prompt 写得不好,LLM 可能忽略资料、编造答案、或者回答得啰嗦没重点。 + +**生成阶段的任务是**:把资料、问题和指令精确地编排在一起,让 LLM 知道自己的角色、知道哪些是事实依据、知道什么情况下该说「不知道」。 + +## 核心概念 + +### RAG Prompt 的三大要素 + +一个标准的 RAG Prompt 包含三部分: + +1. **指令(Instruction)**:告诉 LLM 它的角色和任务 +2. **资料(Context)**:检索到的文档片段 +3. **问题(Question)**:用户的原问题 + +基础模板示例: + +```python +prompt_template = """ +你是一个基于资料的客服助手。请用以下资料回答用户问题。 + +资料: +{context} + +用户问题:{question} + +要求: +- 只根据资料内容回答,不要自行编造 +- 如果资料中没有足够信息,请说「我找不到相关信息」 +- 如果可能,在回答末尾标注信息来源 +- 保持回答简洁、专业 +""" +``` + +### Prompt 设计的关键原则 + +**原则 1:资料比问题更重要** + +研究表明,将资料放在问题之前,LLM 会更倾向于使用资料。反之,如果问题在前,LLM 可能更依赖自己的训练数据: + +```python +# ✅ 推荐:资料在前 +prompt = f"资料:{context}\n\n基于以上资料,回答:{question}" + +# ❌ 不推荐:问题在前 +prompt = f"问题:{question}\n\n另外,这里有参考资料:{context}" +``` + +**原则 2:明确拒绝编造的边界** + +RAG 的一个核心目的是减少幻觉。但 LLM 的「续写」本能仍然存在。需要在 Prompt 里明确加一条边界: + +``` +- 如果资料中没有相关信息,请说「根据现有资料,我无法回答这个问题」 +- 不要推测或编造资料中不存在的信息 +``` + +**原则 3:控制回答格式** + +不指定格式的话,同一个问题可能今天得到一个段落、明天拿到一个列表。常用的格式控制: + +``` +- 请用 Markdown 格式回答 +- 对于多个要点,用有序列表组织 +- 引用来源请用 [来源 X] 的格式标注 +``` + +**原则 4:来源标注** + +让 LLM 标注回答中每一部分信息的来源,可以提高可信度并方便用户验证: + +``` +- 在回答末尾添加:\n\n(来源:{source_1}、{source_2}) +- 引用具体信息时标注对应的文档编号 +``` + +### 幻觉(Hallucination)在 RAG 中如何产生 + +即使有了 RAG,幻觉仍然可能发生。常见原因: + +| 原因 | 表现 | 解决办法 | +|------|------|---------| +| 检索到的资料与问题不相关 | LLM 忽略资料凭记忆回答 | 改善检索/重排 | +| 资料正确但 LLM 没用它 | LLM 自己编了一个「更合理」的答案 | 强化 Prompt 中「只根据资料回答」的约束 | +| 资料中存在矛盾信息 | LLM 尝试调和矛盾,产生不准确描述 | 确保切分不产生矛盾片段 | +| 问题中包含 LLM 训练数据中的强关联 | LLM 用训练数据覆盖了资料信息 | 在 Prompt 中说「忽略你的训练数据,只使用以下资料」 | + +### 事实性校验(Groundedness Checking) + +在一些高风险场景(医疗、法律、金融),可以在生成后加一道校验步骤: + +```python +check_prompt = f""" +请判断以下 LLM 回答是否完全基于提供的资料。用 Yes/No 回答,并说明理由。 + +资料: +{context} + +LLM 回答: +{answer} + +判断是否有内容在资料中没有依据: +""" +``` + +只有在校验通过时才向用户展示回答。这虽然增加了额外成本,但在关键场景中是值得的。 + +### 温度与生成参数 + +| 参数 | 推荐值 | 理由 | +|------|--------|------| +| `temperature` | 0.1~0.3 | 低温度减少幻觉,让 LLM 更忠实于资料 | +| `top_p` | 0.1~0.3 | 与低温度配合,进一步约束输出的随机性 | +| `max_tokens` | 视需要 | 限制回答长度,避免漫无边际 | +| `frequency_penalty` | 0 | RAG 本身已限制输入,不需要额外惩罚 | +| `presence_penalty` | 0 | 同上 | + +!!! warning "不要用默认 temperature" + 很多 API 的默认 temperature 是 1.0。对于 RAG 场景,这个值偏高——LLM 会更「有创意」,但也更容易偏离资料。建议统一设为 0.3。 + +## 最小示例 + +完整的 RAG 生成流程,包含资料编排和校验: + +```python +import openai + +# ── 1. 检索到的资料 ── +context = """ +RAG(Retrieval-Augmented Generation)是一种检索增强生成技术。 +它由检索器、知识库和生成器三部分组成。 +RAG 的优势在于可以引入外部知识,减少模型幻觉。 +""" + +question = "RAG 由哪些部分组成?" + +# ── 2. 编排 Prompt ── +prompt = f""" +你是一个专业的 AI 知识助手。请严格根据以下资料回答问题。 + +{context} + +用户问题:{question} + +要求: +1. 只根据资料内容回答,不要添加资料中没有的信息 +2. 如果资料不足以回答问题,请说「我找不到相关信息」 +3. 回答要简洁、准确 +""" + +# ── 3. 生成 ── +response = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": prompt}], + temperature=0.3, +) + +answer = response.choices[0].message.content +print(answer) + +# ── 4. 事实性校验(可选)── +check = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[{ + "role": "user", + "content": f"""判断以下回答是否完全基于资料。 +资料:{context} +回答:{answer} +请用 Yes/No 回答。""" + }], + temperature=0, +) +print(f"事实性校验通过:{check.choices[0].message.content}") +``` + +## 常见误区 + +!!! failure "误区 1:所有 LLM 对同样的 Prompt 表现一样" + 不同模型在「忠实于资料」的能力上差异很大。小模型更容易忽略资料直接凭记忆回答。建议测试时对比至少 2~3 个模型的回答质量。 + +!!! failure "误区 2:Prompt 写一次就够了" + RAG 的 Prompt 需要持续调整。当你更换知识库、LLM 或业务场景时,都需要重新评估 Prompt 的效果。 + +!!! failure "误区 3:temperature=0 就能完全消除幻觉" + temperature=0 只是让输出确定,但不能保证输出正确。如果资料缺失或 Prompt 没有约束「必须以资料为准」,LLM 仍然可能自信地编造。 + +## 延伸阅读 + +- [为什么需要 RAG](why-rag.md) —— RAG 的动机和定位 +- [RAG 常见问题](troubleshooting.md) —— 从「回答不好」反向排查故障 +- [什么是 Prompt](../prompt/index.md) —— Prompt 工程的核心思想 +- [结构化的 Prompt](../prompt/structure.md) —— 如何系统化设计 Prompt + +## 练习题 + +??? question "练习 1:对比 Prompt 设计的效果" + + 设计三个不同的 RAG Prompt 模板来回答同一个问题: + + - Prompt A:只有「请基于以下资料回答」这句话,没有其他约束 + - Prompt B:加了「只根据资料回答,不要编造」的约束 + - Prompt C:B 的基础上加了「标注来源」和「如果不知道就说不知道」 + + 用同一个资料和问题分别测试,对比三次回答的准确性和格式。 + +??? question "练习 2:设计你自己的 RAG Prompt" + + 选择一个你熟悉的业务场景(如产品客服、学习助手、文档摘要),写出一个完整的 RAG Prompt 模板,包含: + + 1. 系统指令(角色和任务) + 2. 资料占位符 + 3. 问题占位符 + 4. 输出格式要求 + 5. 边界条件(什么情况拒绝回答) + + 提交之前找一个人review你的模板,让对方判断: + 「在不看资料的情况下,只看你的 Prompt,能不能清楚地知道模型应该做什么、不该做什么?」 diff --git a/docs/rag/index.md b/docs/rag/index.md index 425d4b5..1c31151 100644 --- a/docs/rag/index.md +++ b/docs/rag/index.md @@ -1,45 +1,207 @@ +--- +tags: + - RAG +--- + # RAG 总览 -RAG 是 Retrieval-Augmented Generation 的缩写,中文常译为“检索增强生成”。 +> RAG(Retrieval-Augmented Generation,检索增强生成)是一种让大语言模型先查资料再回答的技术架构。它不是某个具体的产品,而是一套解决问题的思路。 + +## 这章解决什么问题 + +假设你是一家公司的技术支持,每天需要回答客户关于产品使用的问题。你的知识库里有完整的用户手册,但你不能把整本手册塞进聊天框。 + +你试过直接问 ChatGPT,但它的回答要么太通用(不知道你的产品细节),要么直接编造。你也想过把所有文档喂给模型,但上下文窗口放不下,而且用户只想知道某一个功能怎么用,不需要整本手册。 + +RAG 就是来解决这个矛盾的:**不让模型凭记忆回答,而是先从一个知识库里找到相关的几段资料,再让模型基于这些资料来组织答案。** + +```mermaid +flowchart LR + A[用户提问] --> B[将问题转为向量] + B --> C[在知识库中搜索
最相似的片段] + C --> D[返回 Top-K
相关片段] + D --> E[将片段 + 问题
拼成 Prompt] + E --> F[LLM 基于资料
生成回答] + F --> G[带引用来源的答案] +``` + +这章是 RAG 模块的入口。读完这章,你会知道: + +- RAG 解决什么问题、不能解决什么问题 +- 一个完整 RAG 系统由哪些环节组成 +- 每个环节之间的关系和常见坑 +- 学完本模块后你应该能做什么 + +## 核心概念 + +### 什么是 RAG + +RAG 是 **Retrieval-Augmented Generation** 的缩写,中文叫**检索增强生成**。名字本身就是它的工作原理拆解: + +| 环节 | 英文 | 做什么 | +|------|------|--------| +| **检索** | Retrieval | 从知识库里找到与问题最相关的文档片段 | +| **增强** | Augmented | 把检索到的资料作为额外信息拼入 Prompt | +| **生成** | Generation | LLM 基于原始问题 + 检索资料生成最终答案 | + +这三个环节缺一不可。没有检索,模型只能凭训练数据回答(不知道你的私有数据);没有增强,检索结果的利用率低;没有生成,你得到的是原文片段而不是答案。 + +### 一个 RAG 系统的五步流程 + +!!! note "RAG 的标准流程" + 1. **文档切分(Chunking)**:把长文档切成合适大小的片段 + 2. **向量化(Vectorization)**:用 Embedding 模型把文本片段转成向量,存入向量数据库 + 3. **检索(Retrieval)**:用户提问时,把问题也转成向量,在数据库中找最相似的前 K 条 + 4. **重排(Rerank)**:用更精确的模型对检索结果重新排序,把最相关的排前面 + 5. **生成(Generation)**:把问题 + 精选后的资料拼成 Prompt,交给 LLM 生成答案 + +每个步骤都有独立的优化空间。切分的粒度影响检索精度,Embedding 模型的选择影响语义匹配质量,重排决定最终给 LLM 的资料质量,Prompt 设计影响最终答案的可读性和准确性。 + +### RAG vs 直接问 LLM vs 微调 -它解决的是:模型不一定知道你的私有资料,怎么让它先查资料,再组织答案。 +新手最容易混淆这三者的区别。我们用一个具体场景来说明: -## 它在解决什么问题 +> 场景:你是某 SaaS 公司的客服,用户问「我们的年付套餐能退款吗?」 -LLM 有几个天然限制: +| 方式 | 过程 | 结果 | +|------|------|------| +| **直接问 LLM** | 模型凭训练数据回答 | 可能说「一般可以退款」,但你们公司的政策是「年付超过 30 天不退」 | +| **RAG** | 先从公司知识库检索退款政策 → 再把政策原文给 LLM → LLM 据此回答 | 「年付套餐购买后 30 天内可全额退款,超过 30 天按剩余天数比例折算」+ 来源标注 | +| **微调(Fine-tuning)** | 用公司历史客服对话训练模型 | 模型学会了回复风格和专业术语,但新政策的更新需要重新训练 | -- 不一定知道最新信息 -- 不一定知道你的私有文档 -- 直接回答时可能会“编” +| 维度 | 直接问 LLM | RAG | 微调 | +|------|-----------|-----|------| +| 引入新知识 | ❌ 训练后才知 | ✅ 更新知识库即可 | ❌ 需重新训练 | +| 知识透明度 | ❌ 黑盒 | ✅ 可追溯来源 | ❌ 知识嵌入参数 | +| 维护成本 | 低 | 中(维护知识库) | 高(训练 + 部署) | +| 适合场景 | 常识问答、翻译、创意 | 知识问答、客服、文档解读 | 格式转换、风格迁移、分类 | +| 延迟 | 最低 | 中(多一次检索) | 低 | -RAG 的思路就是先检索,再生成,把外部资料接进来。 +## RAG 模块的学习路径 -## 最小流程 +本模块共 8 页,建议按以下顺序阅读: -1. 切分文档 -2. 向量化 -3. 检索 -4. 重排 -5. 生成 +``` +RAG 总览(当前页) + └→ 为什么需要 RAG —— 理解 RAG 的适用场景和边界 + └→ 文档切分 —— 切得好,检索才能找得准 + └→ 向量化 —— 把文本变成向量 + └→ 检索 —— 在向量库中找最相关的片段 + └→ 重排 —— 对初步结果做精细排序 + └→ 生成 —— LLM 根据资料组织答案 + └→ RAG 常见问题 —— 调试与故障排查 +``` -## 一个最小系统长什么样 +如果你时间有限,优先读:为什么需要 RAG → 文档切分 → 检索 → 生成。这四页覆盖了 RAG 的核心决策点。 -- 文档库:存你的资料 -- 检索器:找相关片段 -- 生成器:把片段整理成答案 +## 最小示例 -这三块缺一块,RAG 就不完整。 +以下代码演示了一个极简 RAG 流程。它不做重排、不连外部向量数据库,但能让你直观感受「检索 → 增强 → 生成」的完整链路: -## 你先记住 +```python +import openai +import numpy as np -- 文档不是直接喂给模型就完事 -- 检索质量会直接影响答案质量 -- 切分、召回、重排都很关键 +# ── 1. 准备知识库片段 ── +chunks = [ + "RAG 是 Retrieval-Augmented Generation 的缩写,中文叫检索增强生成。", + "文档切分(Chunking)是把长文本切成小片段的过程。", + "向量化(Vectorization)是把文本转换成数值向量的过程。", + "检索(Retrieval)是通过向量相似度找到相关文本片段。", + "重排(Rerank)是对检索结果做二次排序,提高精度。", + "生成(Generation)是 LLM 基于资料组织最终答案。", +] + +# ── 2. 向量化:将文本转为向量 ── +response = openai.embeddings.create( + model="text-embedding-3-small", input=chunks +) +chunk_vectors = np.array([d.embedding for d in response.data]) + +# ── 3. 检索:找与问题最相似的片段 ── +query = "RAG 是什么意思?" +query_resp = openai.embeddings.create( + model="text-embedding-3-small", input=[query] +) +query_vec = np.array(query_resp.data[0].embedding) + +# 用余弦相似度找到最匹配的片段 +similarities = np.dot(chunk_vectors, query_vec) / ( + np.linalg.norm(chunk_vectors, axis=1) * np.linalg.norm(query_vec) +) +best_idx = int(np.argmax(similarities)) +best_chunk = chunks[best_idx] + +print(f"检索到的片段:{best_chunk}") + +# ── 4. 生成:基于资料回答 ── +prompt = f"基于以下资料回答问题:\n{best_chunk}\n\n问题:{query}" +answer = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": prompt}], + temperature=0.3, +) +print(f"LLM 回答:{answer.choices[0].message.content}") +``` + +!!! warning "API Key 安全提示" + 上面的代码使用了 OpenAI API。在生产环境中,API Key 应通过环境变量读取,不要硬编码在代码里。详见 [API 入门](../tools/api.md)。 ## 学完这一章的目标 -你应该能回答: +读完整个 RAG 模块,你应该能: + +1. **说清 RAG 是什么**:能用一句话解释 RAG 的原理,以及它解决了什么问题 +2. **画出一个 RAG 系统的流程图**:能说出完整的 5 个环节,并理解每个环节的作用 +3. **区分 RAG 与微调**:知道什么场景用 RAG、什么场景用微调 +4. **搭建一个最小 RAG 原型**:能用 LangChain 或直接调用 API 搭建一个简单的 RAG 系统 +5. **定位 RAG 故障**:当回答质量差时,能判断是切分、检索还是生成的问题 + +## 常见误区 + +!!! failure "误区 1:RAG 能解决所有「模型不知道」的问题" + 如果知识库里根本没有相关信息,检索结果为空,RAG 也无能为力。RAG 的前提是「库里有,但要能找到」。 + +!!! failure "误区 2:RAG = 把文档全塞进 Prompt" + RAG 是先检索再生成,不是把所有文档拼进上下文。不分青红皂白地塞入大量无关内容,反而会引入噪声、降低回答质量。 + +!!! failure "误区 3:有了 RAG 就不需要微调" + 两者解决不同的问题。RAG 解决「不知道私有知识」的问题(知识维度),微调解决「不擅长某种输出格式」的问题(能力维度)。格式转换、风格迁移等任务更适合微调。 + +## 延伸阅读 + +- [为什么需要 RAG](why-rag.md) —— 深入理解 RAG 的适用场景和边界 +- [什么是 LLM](../basics/what-is-llm.md) —— 理解 LLM 的能力边界是理解 RAG 的前提 +- [函数调用与工具调用](../tools/tool-calling.md) —— RAG 常与工具调用配合使用 +- [Chat、Copilot、Agent 与 Workflow](../tools/workflow.md) —— RAG 在更大工作流中的位置 + +## 练习题 + +??? question "练习 1:判断场景是否适合用 RAG" + + 以下场景你会选择 RAG、直接问 LLM 还是微调?为什么? + + 1. 做一个公司内部 HR 政策问答机器人 + 2. 让模型用莎士比亚风格写产品描述 + 3. 根据最新研究论文回答读者提问 + 4. 把用户查到的天气信息用口语说出来 + + ??? done "参考答案" + + | 场景 | 方案 | 原因 | + |------|------|------| + | 1 | RAG | HR 政策经常更新,放知识库方便维护,不改模型 | + | 2 | 微调或写好 few-shot Prompt | 风格问题,不需要外部知识 | + | 3 | RAG | 论文内容是 LLM 训练数据之外的,需要先检索 | + | 4 | 直接 LLM | 天气信息已经通过 API 或工具调用拿到了,LLM 只需重新组织语言 | + +??? question "练习 2:拆解 RAG 流程" + + 找一个你日常遇到的「需要查资料才能回答」的问题(比如产品使用疑问),手动模拟 RAG 流程: + + 1. 你的「知识库」是什么?(比如产品手册、帮助中心) + 2. 你会切成多大一段作为检索单元? + 3. 如果让你写代码实现,你会用什么 Embedding 模型和向量数据库? + 4. LLM 生成时,你会怎么写 Prompt 来约束它? -- 为什么需要 RAG -- 一个最小 RAG 系统长什么样 -- 哪些错误会让 RAG 变得很差 + 不需要真的写代码,把思路写下来就行。 diff --git a/docs/rag/rerank.md b/docs/rag/rerank.md index d56b946..214b760 100644 --- a/docs/rag/rerank.md +++ b/docs/rag/rerank.md @@ -1,10 +1,193 @@ +--- +tags: + - RAG +--- + +# 重排:对检索结果做精细化排序 + +> 重排(Rerank)是对初步检索结果进行二次排序的过程,用更精确的模型将最相关的文档片段排在前面。 + +## 这章解决什么问题 + +向量检索(第一阶段检索)速度快但精度有限。它的相似度是基于 Embedding 向量的整体语义匹配,可能把「苹果很好吃」和「苹果发布了新手机」也算得很近——它们都提到了苹果,但语义方向完全不同。 + +而 LLM 的上下文窗口有限,只能接受 3~5 个片段。如果最相关的内容被排在倒数第一,LLM 可能漏掉关键信息。 + +**重排就是解决这个矛盾的**——先用快速的向量检索从百万级候选中召回几十个候选(保召回率),再用精确的交叉编码器排序模型从中精挑细选出最有用的几个(保精确率)。 + +## 核心概念 + +### 两阶段检索架构 + +```mermaid +flowchart LR + subgraph 第一阶段_检索 + A[用户问题] --> B[Embedding 模型
语义检索] + B --> C[Top-50 候选] + end + subgraph 第二阶段_重排 + C --> D[交叉编码器
逐一计分] + D --> E[Top-3 精选结果] + end + E --> F[LLM 生成答案] +``` + +第一阶段(检索):用 Embedding 模型快速召回 Top-50 ~ Top-100 + +第二阶段(重排):用交叉编码器(Cross-Encoder)对每个候选逐条打分,只保留 Top-3 ~ Top-5 + +### 向量检索 vs 重排模型 + +| 维度 | 向量检索(Bi-Encoder) | 重排模型(Cross-Encoder) | +|------|----------------------|--------------------------| +| 处理方式 | 问题和文档分别编码 | 问题和文档拼接后一起编码 | +| 编码速度 | 快(可预先计算文档向量) | 慢(无法预计算,需实时编码) | +| 精度 | 中,丢失交互信息 | 高,能捕捉 query-doc 的语义交互 | +| 适用阶段 | 候选生成(速度快,处理海量数据) | 候选排序(精度高,处理少量数据) | + +重排的本质是用精度换时间:把需要高精度的运算限制在少量候选项上。 + +### 重排的工作原理 + +以 `BAAI/bge-reranker-v2-m3` 为例: + +``` +[CLS] 问题:什么是 RAG? [SEP] 资料:RAG 是检索增强生成 [SEP] + ↓ + Cross-Encoder 模型 + ↓ + 0.92(相关度分数) +``` + +输入是问题和文档拼接后的文本对,输出是 0~1 之间的相关度分数。模型能同时看到问题中的每个词和文档中的每个词,捕捉它们之间的交互关系。这就是为什么交叉编码器比双编码器精度更高的原因。 + +### 常见重排模型 + +| 模型 | 语言 | 特点 | +|------|------|------| +| `BAAI/bge-reranker-v2-m3` | 中英多语言 | 轻量,适合中文场景 | +| `BAAI/bge-reranker-v2-gemma` | 多语言 | 更强但更重 | +| `Cohere Rerank` | 多语言 | 云端 API,无需本地部署 | +| `cross-encoder/ms-marco-MiniLM-L6-v2` | 英文 | 经典的 MS MARCO 排名模型 | +| `Jina Reranker` | 多语言 | 开箱即用,支持长文本 | + +### 重排策略 + +**策略 1:重排后截断** + +最常见的方式。检索返回 Top-50 → 重排 → 取 Top-3: + +```python +from langchain.retrievers import ContextualCompressionRetriever +from langchain_community.cross_encoders import HuggingFaceCrossEncoder +from langchain.retrievers.document_compressors import CrossEncoderReranker + +retriever = vectorstore.as_retriever(search_kwargs={"k": 20}) + +reranker = CrossEncoderReranker( + model=HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-v2-m3"), + top_n=3, +) +compression_retriever = ContextualCompressionRetriever( + base_compressor=reranker, + base_retriever=retriever, +) + +results = compression_retriever.invoke("什么是 RAG?") +``` + +**策略 2:用 LLM 做重排** + +你不一定要用专门的 Rerank 模型。LLM 也可以做排序——虽然慢一些,但理解能力更强: + +```python +rerank_prompt = f""" +请根据相关性对以下文档片段排序(从最相关到最不相关)。 +返回排序后的序号。只返回序号,用逗号分隔。 + +问题:{query} + +文档: +1: {doc_1} +2: {doc_2} +3: {doc_3} +""" +``` + +这种方式的优点是零配置,缺点是成本高、延迟大。 + +**策略 3:MMR(最大边际相关性)** + +MMR 不追求「最相关」,而是追求「相关信息最大覆盖」——即在相关性高的前提下,尽量选择候选之间差异大的: + +```python +from langchain.vectorstores import Chroma + +retriever = vectorstore.as_retriever( + search_type="mmr", + search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.7} +) +``` + +`lambda_mult=0.7` 表示 70% 权重给相关性,30% 权重给多样性。你在处理多角度问题(如「介绍一下 RAG 的优缺点」)时,MMR 会让回答更全面。 + +## 最小示例 + +```python +from sentence_transformers import CrossEncoder + +# 准备数据和模型 +query = "RAG 的主要优势是什么?" +candidates = [ + "RAG 通过检索外部知识来辅助生成,减少幻觉。", + "RAG 相比微调更容易更新知识。", + "RAG 由检索器、生成器和知识库三部分组成。", + "RAG 的缺点是延迟比直接生成稍高。", + "RAG 适合需要引用来源的场景。", +] + +model = CrossEncoder("cross-encoder/ms-marco-MiniLM-L6-v2") + # 重排 +pairs = [(query, doc) for doc in candidates] +scores = model.predict(pairs) + +# 按分数排序 +ranked = sorted( + zip(candidates, scores), key=lambda x: x[1], reverse=True +) + +print("重排结果:") +for doc, score in ranked: + print(f" [{score:.4f}] {doc}") +``` + +## 常见误区 + +!!! failure "误区 1:重排是可选步骤,不做也行" + 如果你只用向量检索的 Top-K 结果直接给 LLM,效果可能差 5~10 个百分点。重排在绝大多数场景下都有显著改善。 + +!!! failure "误区 2:重排模型越大越好" + 重排模型是每次查询都要调用的,不是索引阶段预先计算。模型越重,延迟越高。轻量模型(如 bge-reranker-v2-m3)在多数场景下已经足够。 + +!!! failure "误区 3:重排只做一次就够" + 当知识库规模很大时,可以考虑**级联重排(Cascaded Rerank)**——先用轻量模型从 Top-100 筛到 Top-20,再用量量模型从 Top-20 筛到 Top-3。既保证精度又控制延迟。 + +## 延伸阅读 + +- [检索](retrieval.md) —— 重排的上游步骤 +- [生成](generation.md) —— 重排后的结果如何输入 LLM +- [BGE Reranker 论文](https://arxiv.org/abs/2310.11423) +- [Sentence Transformers Cross-Encoder 文档](https://www.sbert.net/examples/applications/cross-encoder/README.html) + +## 练习题 + +??? question "练习:对比重排效果" -> 占位页:本页内容待团队补充。 + 准备一个问题和 10 篇候选文档,其中 2~3 篇是最相关的。分别做以下实验: -## 建议内容 + 1. 只用向量检索的 Top-3(不重排)→ LLM 生成的答案准确率如何? + 2. 向量检索 Top-20 → Cross-Encoder 重排取 Top-3 → LLM 生成,答案改善了吗? + 3. 尝试不同的重排模型(bge-reranker-v2-m3 vs ms-marco-MiniLM),结果有差异吗? -- 为什么需要重排 -- 什么时候使用 -- 常见方案 -- 效果评估 + 用 5 个不同的查询重复上面的实验,你觉得重排至少能提升多少准确率? diff --git a/docs/rag/retrieval.md b/docs/rag/retrieval.md index 959d29a..ad52bb3 100644 --- a/docs/rag/retrieval.md +++ b/docs/rag/retrieval.md @@ -1,10 +1,206 @@ -# 检索 +--- +tags: + - RAG +--- -> 占位页:本页内容待团队补充。 +# 检索:从知识库中找到相关内容 -## 建议内容 +> 检索(Retrieval)是 RAG 系统的核心环节——在向量数据库中找到与用户问题最相似的那些文本片段。 -- 召回 -- 相似度 -- Top-K -- 检索失败原因 +## 这章解决什么问题 + +想象你在一个巨大的图书馆里找一本特定的书。如果图书馆没有索引,你得挨个书架找,效率极低。检索就是给知识库建一套高效的索引系统,让每次查询只扫描最可能相关的区域,而不是从头到尾翻一遍。 + +## 核心概念 + +### 检索的基本流程 + +```mermaid +flowchart LR + A[用户问题] --> B[Embedding 模型
转为向量] + B --> C[向量数据库
检索 Top-K] + C --> D[返回相似片段
及分数] + D --> E[送入重排或
直接给 LLM] +``` + +用户提问时,需要将问题也通过同样的 Embedding 模型转换成向量。这一步很容易被忽略但非常重要——检索是基于「问题向量」和「文档向量」之间的相似度,如果模型或参数不同,匹配效果会大幅下降。 + +### 语义检索 vs 关键词检索 + +| 维度 | 语义检索(Semantic / Dense) | 关键词检索(Lexical / Sparse) | +|------|-----------------------------|-------------------------------| +| 原理 | 用 Embedding 模型理解语义 | 用 BM25/TF-IDF 匹配关键词 | +| 优势 | 理解同义词、近义表达 | 精确匹配专有名词(如产品型号) | +| 劣势 | 对罕见词和专有名词敏感度低 | 不理解语义,「苹果」搜不到「iPhone」 | +| 场景 | 开放域问答、多轮对话 | 搜索文档标题、代码片段 | +| 工具 | 向量数据库 | Elasticsearch、Whoosh | + +实践中,很多系统采用**混合检索(Hybrid Search / Multi-Recall)**——同时用语义检索和关键词检索,分别取 Top-K,然后合并去重。混合检索往往比单一方式更鲁棒。 + +```python +from langchain.retrievers import EnsembleRetriever +from langchain_community.retrievers import BM25Retriever +from langchain_community.vectorstores import Chroma +from langchain_openai import OpenAIEmbeddings + +# 语义检索 +vectorstore = Chroma.from_texts(chunks, OpenAIEmbeddings()) +semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) + +# 关键词检索 +keyword_retriever = BM25Retriever.from_texts(chunks, k=5) + +# 混合检索 +ensemble_retriever = EnsembleRetriever( + retrievers=[semantic_retriever, keyword_retriever], + weights=[0.6, 0.4], # 语义检索权重更高 +) + +results = ensemble_retriever.invoke("什么是 RAG?") +``` + +### Top-K 的选择 + +K 值决定了送入 LLM 的片段数量: + +| K 值 | 优点 | 缺点 | 适用场景 | +|------|------|------|---------| +| 1~2 | 上下文纯净,LLM 注意力集中 | 单一来源,信息可能不够 | 简单事实查询 | +| 3~5 | 信息充分,容错性高 | 可能包含无关信息 | 通用问答 | +| 6~10 | 覆盖全面,减少遗漏 | LLM 可能被大量文本分散注意力 | 复杂推理、多角度分析 | + +K 值不是越大越好。送入过多的片段会稀释 LLM 对关键信息的注意力,而且增加了 token 成本和延迟。 + +### 检索结果的质量指标 + +评估检索有两个来自信息检索的经典指标: + +- **精确率(Precision,查准率)**:返回的 K 个结果中,有多少是真正相关的 +- **召回率(Recall,查全率)**:知识库中所有相关文档,被检索回来的占比 + +用公式更好理解: + +``` +相关文档(在知识库中): ● ● ● ● ●(共 5 篇) +检索返回的 Top-3: ● ■ ■ ← 只有第 1 篇相关 + +精确率 = 1/3 ≈ 0.33 (返回的 3 篇中有 1 篇相关) +召回率 = 1/5 = 0.20 (总共 5 篇相关只找回 1 篇) +``` + +两者存在矛盾:K 越大,召回率 ↑(多找回一些相关文档),但精确率 ↓(混进更多不相关的);K 越小则相反。重排环节的作用就是先用大 K 保召回,再用精确模型提高 Top 结果的精确率。 + +### 检索策略进阶 + +除了标准的向量检索,还有一些进阶策略: + +**Query 重写(Query Rewriting)** + +用户的问题往往很简短(如「那个多少钱?」),缺乏上下文。Query 重写在检索前先把问题扩展成更完整的查询: + +```python +rewrite_prompt = "请将以下用户问题改写成更适合搜索的完整句子:\n{query}" +query = llm.invoke(rewrite_prompt) +results = vectorstore.similarity_search(query) +``` + +**HyDE(Hypothetical Document Embeddings)** + +HyDE 的思路很巧妙:如果一个真实文档和问题相似,那模型「假设」的回答文档应该和真实文档更相似。所以先让 LLM 生成一段假设回答,再用这段假设回答去检索,往往能找到更匹配的文档。 + +**Multi-Query Retrieval(多查询检索)** + +同一个问题可能有多重解读视角。用 LLM 生成 3~5 个不同的搜索词,分别检索后合并结果: + +```python +queries_prompt = f"针对问题 '{query}',给出 5 个不同角度的搜索查询:" +queries = llm.invoke(queries_prompt).split("\n") + +all_results = [] +for q in queries: + all_results.extend(vectorstore.similarity_search(q, k=3)) +``` + +## 最小示例 + +一个完整的检索循环,包含 Query 重写和混合检索: + +```python +import openai +import numpy as np + +# ── 1. 初始化知识库(简化的内存向量库)── +chunks = [ + "RAG 是 Retrieval-Augmented Generation,检索增强生成。", + "文档切分(Chunking)是把长文本切成小片段的过程。", + "向量化(Vectorization)是把文本转换为数值向量。", +] + +response = openai.embeddings.create( + model="text-embedding-3-small", input=chunks +) +chunk_vecs = np.array([d.embedding for d in response.data]) + +def search(query, k=2): + """简单的向量检索函数""" + q_resp = openai.embeddings.create( + model="text-embedding-3-small", input=[query] + ) + q_vec = np.array(q_resp.data[0].embedding) + scores = np.dot(chunk_vecs, q_vec) / ( + np.linalg.norm(chunk_vecs, axis=1) * np.linalg.norm(q_vec) + ) + top_k = np.argsort(scores)[::-1][:k] + return [(chunks[i], scores[i]) for i in top_k] + +# ── 2. 用户提问 ── +raw_query = "RAG 是什么?" +# 简易 Query 重写:用 gpt-4o-mini 扩展 +rewrite_response = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[{ + "role": "user", + "content": f"重写以下问题使其更适合搜索:{raw_query}" + }], +) +rewritten = rewrite_response.choices[0].message.content + +results = search(rewritten or raw_query, k=2) +print("检索结果:") +for text, score in results: + print(f" [{score:.4f}] {text}") +``` + +## 常见误区 + +!!! failure "误区 1:「检索」只是查向量数据库" + 向量检索只是第一步。Query 重写、HyDE、混合检索等策略往往能显著提升最终的检索质量。很多 RAG 系统只实现了基础的向量检索,空间还很大。 + +!!! failure "误区 2:K 值越大越好" + 过大的 K 值让 LLM 的上下文被非核心内容挤满,反而降低回答质量。建议从 K=3~5 开始,根据实际效果调整。 + +!!! failure "误区 3:Top-1 就够用了" + Top-1 的结果可能错误或偏差。保留多个候选项,让后续的 Rerank 或 LLM 自己从多段文本中提取信息,容错性更高。 + +## 延伸阅读 + +- [向量化](vectorization.md) —— 检索的数据基础 +- [重排](rerank.md) —— 检索结果的二次排序 +- [生成](generation.md) —— 检索结果如何输入 LLM +- [BM25 算法详解](https://zh.wikipedia.org/wiki/BM25) + +## 练习题 + +??? question "练习:评估你的检索效果" + + 准备 10 个问题及其标准答案。用你选择的检索方案(语义 / 关键词 / 混合)对整个知识库检索,计算: + + 1. **Top-1 准确率**:第一个结果里有正确答案的比例 + 2. **Top-5 召回率**:前 5 个结果里有正确答案的比例 + 3. **平均检索时间**:每次检索的平均延迟 + + 然后尝试: + + - 把 Embedding 模型换成另一个(如 small → large),Top-1 准确率有变化吗? + - 添加 Query 重写后,检索效果改善了吗? + - 如果加入混合检索(语义 + BM25),召回率上升还是下降? diff --git a/docs/rag/troubleshooting.md b/docs/rag/troubleshooting.md index 5b07b63..fd75e7e 100644 --- a/docs/rag/troubleshooting.md +++ b/docs/rag/troubleshooting.md @@ -1,10 +1,283 @@ -# RAG 常见问题 +--- +tags: + - RAG +--- -> 占位页:本页内容待团队补充。 +# RAG 常见问题与调试 -## 建议内容 +> RAG 系统的失败模式是多样的——回答不好可能因为任何一个环节存在缺陷。这章帮你系统性定位问题。 -- 召回差 -- 切分差 -- 引用差 -- 幻觉 +## 这章解决什么问题 + +你的 RAG 系统上线了。用户问了一个看起来很简单的问题,但 LLM 给出了一个离谱的答案。 + +是检索没找到正确的资料?是 LLM 忽略了资料?是切分切断了关键信息?还是 Embedding 模型没理解用户的表达? + +RAG 的调试比单环节系统更复杂,因为**每一个环节都可能是瓶颈**。这章提供一套系统性的故障排查思路。 + +## 核心排查方法论 + +### RAG 调试的十一问 + +!!! info "从回答不好到找到根因的排查路径" + + 1. **资料库里有正确答案吗?** + - 先去知识库里手动搜一下。如果库里就没有答案,RAG 不可能给出正确答案 + - 如果资料不全 → 补充知识库 + + 2. **资料被正确切分了吗?** + - 找到包含答案的原始片段,看它是否被切散到了多个 chunk 中 + - 是否在 chunk 边界处被截断? + - 如果切分有问题 → 调整切分策略(用更大 chunk size 或语义切分) + + 3. **切分后的 chunk 语义完整吗?** + - 逐个检查被你命中的 chunk,上下文是否自包含? + - chunk 包含的信息是否完整到能独立回答问题? + - 不完整?→ 增加 chunk size 或 chunk_overlap + + 4. **用户提问和答案之间的语义鸿沟大吗?** + - 用户说「怎么取消」,答案叫「退款政策」——虽然相关但不完全匹配 + - 语义鸿沟大?→ 在索引中加入同义词或别名,或者直接用 LLM 做 Query 重写 + + 5. **Embedding 模型能捕捉这种语义关系吗?** + - 手动算一下用户问题和 chunk 之间的相似度 + - 相似度明显偏低?→ 换更合适的 Embedding 模型(如针对中文场景优化) + + 6. **Top-K 够用吗?** + - K=3 只返回了 3 个 chunk,正确答案排在第四 + - K 太小?→ 增加 K 值,然后配合重排提高精确率 + + 7. **重排模型有没有把正确答案排下去?** + - 检查正确答案在重排前后的排名变化 + - 重排分数偏低?→ 检查重排模型是否适配当前语言/领域,或者重排的候选集是否足够大 + + 8. **LLM 用上了资料吗?** + - 把 LLM 的 Prompt 打印出来,看资料有没有正确传递到 LLM 的输入中 + - 如果资料传过去了但 LLM 没用 → 强化 Prompt 约束 + - 如果资料根本没传 → 检查你的代码 + + 9. **Prompt 约束足够强吗?** + - Prompt 里说了「只根据资料回答」吗? + - 说了「如果找不到相关信息就说不知道」吗? + - 约束不够 → 优化 Prompt + + 10. **temeperature 高不高?** + - temperature > 0.5 会增加 LLM 的生成随机性 + - 对于 RAG 场景,0.1~0.3 更合适 + + 11. **是不是 token 限制被截断了?** + - 如果 Prompt 加上资料后超过了模型的上下文窗口,后边的资料会被截掉 + - 截断了?→ 减少 K 值或 chunk size,或使用更大上下文窗口的模型 + +### 快速诊断表 + +```mermaid +flowchart TD + A["回答质量差"] --> B{"资料库中
有正确答案吗?"} + B -->|没有| C["补充知识库"] + B -->|有| D{"检索到了
正确答案吗?"} + D -->|没有| E["检查切分策略"] + D -->|有| F{"LLM 正确使用
了资料吗?"} + E --> G["检查 Embedding
和检索参数"] + F -->|没有| H["优化 Prompt
和 temperature"] + F -->|有| I["检查格式、来源
等细粒度问题"] +``` + +### 生产环境的调试技巧 + +**1. 记录检索日志** + +在每个查询中记录:用户问题、检索到的 Top-K 片段及分数、重排后的片段及分数、最终 Prompt、LLM 回答: + +```python +import json + +def log_query(user_question, retrieval_results, final_prompt, llm_answer): + log = { + "question": user_question, + "retrieval_top_k": [ + {"text": t, "score": float(s)} + for t, s in retrieval_results + ], + "final_prompt": final_prompt, + "llm_answer": llm_answer, + } + with open("query_log.jsonl", "a", encoding="utf-8") as f: + f.write(json.dumps(log, ensure_ascii=False) + "\n") +``` + +有了日志,你就可以离线分析失败模式,而不是在线上盲目调试。 + +**2. 建立测试集(Golden Dataset)** + +收集 50~100 个典型问题和正确答案,自动化跑回归测试: + +```python +# golden_dataset.json +[ + { + "question": "RAG 由哪些部分组成?", + "expected": ["检索器", "知识库", "生成器"], + "category": "概念理解" + }, + { + "question": "年付套餐怎么退款?", + "expected": ["30 天内可全额退款", "超过 30 天按比例"], + "category": "业务政策" + } +] +``` + +每次修改系统后,用数据集跑一遍,看**召回率(Retrieval Recall)** 和**回答准确率(Answer Accuracy)** 是否下降。 + +**3. A/B 测试** + +要验证一个改动是否有效,在线上分流对比最可靠。以下变量值得做 A/B 测试: + +- 不同切分策略 +- 不同 Embedding 模型 +- 不同 K 值 +- 有/无重排 +- 不同 Prompt 模板 +- 不同 temperature + +每次只改一个变量,观察回答质量指标的变化。 + +## 各环节常见问题速查 + +### 切分相关问题 + +| 症状 | 可能原因 | 解决方案 | +|------|---------|---------| +| 检索到的 chunk 信息不完整 | chunk size 太小或 overlap 不足 | 增加 chunk size 或 overlap | +| 同一个话题被切成多个 chunk | 固定长度切分没考虑语义边界 | 改用语义切分或按标题切分 | +| 检索结果很精确但上下文太少 | 切分太细,chunk 缺少上下文 | 适当增大 chunk size | + +### 检索相关问题 + +| 症状 | 可能原因 | 解决方案 | +|------|---------|---------| +| 完全找不到匹配 | Embedding 模型不合适 | 换一个更适合语种的 Embedding 模型 | +| 分数偏低 | 语义鸿沟大 | 加 Query 重写或用 HyDE | +| 答案在 Top-4,K=3 找不到 | K 值太小 | 增大 K 值,配合重排 | +| 检索结果看似相关但不解决问题 | 语义检索没捕捉到意图 | 加混合检索(语义 + BM25) | + +### 重排相关问题 + +| 症状 | 可能原因 | 解决方案 | +|------|---------|---------| +| 正确答案重排后排到了后面 | Rerank 模型不匹配语言/领域 | 换个 Rerank 模型 | +| 重排后 Top-1 和 Top-3 效果差异不大 | 候选集本身质量高 | 可考虑减少 K 值跳过重排 | +| 重排严重影响延迟 | 模型太大或候选太多 | 换轻量模型,或减少候选数 | + +### 生成相关问题 + +| 症状 | 可能原因 | 解决方案 | +|------|---------|---------| +| LLM 编造了资料中没有的信息 | Prompt 没约束「只根据资料回答」 | 添加约束,降低 temperature | +| LLM 回答了「不知道」但资料里有 | Prompt 在资料前,模型更依赖训练数据 | 把资料放在 Prompt 开头 | +| 回答太长或结构混乱 | 没指定输出格式 | 在 Prompt 中限定格式 | +| 回答了「我不知道」但资料中有 | 上下文被截断 | 减少 K 值或 chunk size | +| 回答自相矛盾 | 多个 chunk 信息冲突 | 检查切分是否产生矛盾片段 | + +## 最小示例 + +```python +import openai +import numpy as np + +def diagnose_rag(question, chunks, answer): + """RAG 问题诊断助手""" + issues = [] + + # 1. 资料中有相关片段吗? + q_resp = openai.embeddings.create( + model="text-embedding-3-small", input=[question] + ) + q_vec = np.array(q_resp.data[0].embedding) + + chunk_resp = openai.embeddings.create( + model="text-embedding-3-small", input=chunks + ) + chunk_vecs = np.array([d.embedding for d in chunk_resp.data]) + scores = np.dot(chunk_vecs, q_vec) / ( + np.linalg.norm(chunk_vecs, axis=1) * np.linalg.norm(q_vec) + ) + + best_score = float(np.max(scores)) + if best_score < 0.3: + issues.append(f"低相似度:最高相似度仅 {best_score:.2f},可能检索没找到正确内容") + elif best_score < 0.6: + issues.append(f"中等相似度:最高 {best_score:.2f},需要确认结果是否真的相关") + else: + issues.append(f"相似度良好({best_score:.2f}),检索环节可能没问题") + + # 2. 回答中有没有可能编造的内容? + check_prompt = f"""以下回答完全基于提供的资料吗? +资料(前3个chunk):{chunks[:3]} +回答:{answer} +是否完全基于资料?回答 Yes 或 No 并说明理由。""" + check = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": check_prompt}], + temperature=0, + ) + issues.append(f"事实性检查结果:{check.choices[0].message.content}") + + return issues + +# 使用示例 +issues = diagnose_rag( + question="RAG 是什么?", + chunks=["RAG 是一种文本生成方法。", "机器学习的应用很广泛。"], + answer="RAG 是检索增强生成技术。", +) +for issue in issues: + print(f"- {issue}") +``` + +## 常见误区 + +!!! failure "误区 1:最后一个环节出问题就是 LLM 的错" + 多数 RAG 回答质量差的问题,根因在检索环节(没找到正确答案),而不是生成阶段。先检查检索。 + +!!! failure "误区 2:上线之后就不需要关注了" + 文档更新、用户提问方式变化、Embedding 模型升级——任何一个变动都可能影响 RAG 质量。建议定期跑测试数据集。 + +!!! failure "误区 3:调试靠直觉" + 不要猜。检索日志、测试集、A/B 测试——三个工具能解决 90% 的 RAG 调试问题。直觉不可靠,数据才可靠。 + +## 延伸阅读 + +- [生成](generation.md) —— 调试的常见终点 +- [检索](retrieval.md) —— 调试的常见起点 +- [为什么需要 RAG](why-rag.md) —— 回顾 RAG 的能力边界 +- [RAG 分类与评估](https://docs.llamaindex.ai/en/stable/module_guides/querying/retrievers/) + +## 练习题 + +??? question "练习 1:一次完整的 RAG 调试" + + 假设你的 RAG 系统遇到了以下问题: + + > 用户问:「你们公司的退款政策是什么?」LLM 回答:「一般来说,大多数公司接受 30 天内退款。」 + + 这个回答明显不对——你们公司的政策是「软件产品一旦激活概不退款」。 + + 请按照「十一问」排查流程,写出你的排查思路和可能的解决方案。 + +??? question "练习 2:搭建你的测试数据集" + + 为你的 RAG 场景准备 20 个测试用例,每个包含: + + - `question`:用户问题 + - `expected`:期望的回答关键词(3~5 个) + - `category`:问题类型 + + 然后写一个脚本: + + 1. 用你的 RAG 系统回答这 20 个问题 + 2. 判断回答是否包含所有 `expected` 关键词 + 3. 输出通过率 + + 这个测试集将成为你后续所有 RAG 改动的基本验证工具。 diff --git a/docs/rag/vectorization.md b/docs/rag/vectorization.md index a73f2f1..c7b4337 100644 --- a/docs/rag/vectorization.md +++ b/docs/rag/vectorization.md @@ -1,10 +1,291 @@ -# 向量化 +--- +tags: + - RAG +--- -> 占位页:本页内容待团队补充。 +# 向量化:把文本变成向量 -## 建议内容 +> 向量化(Vectorization / Embedding)是把文本转换成数值向量的过程,让计算机能在数学空间里衡量「两段文字有多像」。 -- Embedding -- 向量数据库 -- 选择策略 -- 常见误区 +## 这章解决什么问题 + +人类判断两段文字是否相关,靠的是语义理解。但计算机看不懂「意思」,只能处理数字。 + +向量化就是造一座桥,把人类语言翻译成计算机能算的数字序列——**向量(Vector)**。有了这些数字,我们就可以用数学公式计算相似度,实现快速检索。 + +## 核心概念 + +### 什么是向量 + +向量是一组浮点数,比如 `[0.023, -0.157, 0.892, ...]`(通常 256~3072 维)。每个维度代表文本在某个潜在「语义方向」上的分量。 + +通俗理解:向量就像是给一段文字做的「语义指纹」。两段文字在语义上越接近,它们的向量在空间中就越靠近。 + +```mermaid +flowchart LR + A["文本 A:
什么是 RAG?"] --> E["Embedding
模型"] + B["文本 B:
RAG 是检索增强生成"] --> E + C["文本 C:
今天天气不错"] --> E + E --> VA["向量 A:[0.2, -0.1, ...]"] + E --> VB["向量 B:[0.3, -0.2, ...]"] + E --> VC["向量 C:[-0.5, 0.8, ...]"] + VA -.->|相似度高
cos ≈ 0.9| VB + VA -.->|相似度低
cos ≈ 0.1| VC +``` + +### 相似度计算 + +有了向量之后,我们需要一个「尺子」来衡量两段文字的语义距离。不同尺子侧重点不同,选择哪个直接影响检索效果。 + +#### 余弦相似度(Cosine Similarity) + +衡量两个向量在方向上的**夹角大小**,不考虑向量长度(模长)。 + +``` +cos(a, b) = (a · b) / (|a| × |b|) +``` + +| 取值范围 | 含义 | +|---------|------| +| 1 | 方向完全一致(语义最接近) | +| 0 | 正交,不相关 | +| -1 | 方向完全相反(语义最远) | + +Python 实现: + +```python +import numpy as np + +def cosine_similarity(a, b): + return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) + +vec_a = np.array([0.2, -0.1, 0.5]) +vec_b = np.array([0.3, -0.2, 0.4]) +print(f"余弦相似度:{cosine_similarity(vec_a, vec_b):.4f}") +``` + +**适用场景**:语义搜索、文本相似度判断、信息检索。这是 RAG 中最常用的指标,因为它只关心方向(语义倾向),不受文本长度影响。 + +!!! tip "OpenAI embeddings 已经归一化" + OpenAI 的 `text-embedding-3-small` 和 `text-embedding-3-large` 输出默认是 L2 归一化的(模长为 1)。此时余弦相似度等价于点积,可以省去分母计算。 + +#### 点积相似度(Dot Product / Inner Product) + +直接计算两个向量的内积: + +``` +dot(a, b) = a₁b₁ + a₂b₂ + ... + aₙbₙ +``` + +```python +def dot_product(a, b): + return np.dot(a, b) + +print(f"点积:{dot_product(vec_a, vec_b):.4f}") +``` + +| 特点 | 说明 | +|------|------| +| 与余弦的关系 | 当向量已 L2 归一化时,点积 = 余弦相似度 | +| 未归一化时 | 同时受方向和模长影响——模长长的向量更容易得高分 | +| 取值范围 | 无固定边界,取决于向量维度和数值范围 | + +**适用场景**: +- 向量已归一化时:直接替代余弦相似度,计算更快(省一次模长运算) +- 向量未归一化时:适合对「高频/长文档」有偏好的场景(如长文本比短文本更值得被检索到) + +#### 欧氏距离(Euclidean Distance / L2) + +衡量两个向量在多维空间中的**直线距离**。距离越小越相似。 + +``` +L2(a, b) = √[(a₁ - b₁)² + (a₂ - b₂)² + ... + (aₙ - bₙ)²] +``` + +```python +def euclidean_distance(a, b): + return np.linalg.norm(a - b) + +def euclidean_similarity(a, b): + """将距离转为相似度分数,方便统一比较""" + return 1 / (1 + euclidean_distance(a, b)) +``` + +**与余弦相似度的关键区别**: + +| 维度 | 余弦相似度 | 欧氏距离 | +|------|-----------|---------| +| 关注点 | 方向夹角 | 绝对距离 | +| 受向量长度影响 | 否 | 是 | +| 举例 | (1,0) 与 (10,0) 的 cos=1,完全相同 | (1,0) 与 (10,0) 的 L2=9,差异很大 | + +**适用场景**:当向量长度本身携带语义信息时(比如向量的模长代表「信息量」或「置信度」),欧氏距离能同时捕捉方向和大小的差异。 + +#### 曼哈顿距离(Manhattan Distance / L1) + +计算两个向量在各维度上的**绝对差之和**: + +``` +L1(a, b) = |a₁ - b₁| + |a₂ - b₂| + ... + |aₙ - bₙ| +``` + +```python +def manhattan_distance(a, b): + return np.sum(np.abs(a - b)) +``` + +**与欧氏距离对比**:曼哈顿距离对单个维度的异常差异不那么敏感(不取平方)。在高维空间中,曼哈顿距离有时比欧氏距离更稳定(缓解「维度灾难」)。但 Embedding 检索中较少直接使用曼哈顿距离。 + +#### 各指标对比总结 + +```python +import numpy as np + +def compare_metrics(a, b): + """一次性输出多种相似度指标""" + cos = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) + dot = np.dot(a, b) + l2 = np.linalg.norm(a - b) + l1 = np.sum(np.abs(a - b)) + + return { + "余弦相似度": cos, + "点积": dot, + "欧氏距离": l2, + "欧氏相似度": 1 / (1 + l2), + "曼哈顿距离": l1, + } + +v1 = np.array([0.2, -0.1, 0.5]) +v2 = np.array([0.3, -0.2, 0.4]) +v3 = np.array([-0.5, 0.8, 0.1]) + +for name, val in compare_metrics(v1, v2).items(): + print(f"{name}: {val:.4f}") +``` + +在 RAG 实践中,**余弦相似度是默认选择**,其次是点积(向量归一化后等效于余弦)。欧氏距离作为补充,适合向量长度携带信息的场景。向量数据库(FAISS、Milvus 等)通常同时支持多种距离度量,可以在建索引时通过参数指定。 + +### Embedding 模型的选择 + +Embedding 模型(也叫向量化模型)的质量直接决定检索效果。常见选项: + +| 模型 | 维度 | 特点 | 适用场景 | +|------|------|------|---------| +| `text-embedding-3-small` | 1536 | OpenAI,性价比高,支持 256 维截断 | 通用场景 | +| `text-embedding-3-large` | 3072 | OpenAI,精度更高,成本也更高 | 精度优先的场景 | +| `BAAI/bge-large-zh-v1.5` | 1024 | 中文优化,开源 | 中文场景 | +| `intfloat/multilingual-e5-large` | 1024 | 多语言,包含中文 | 多语言场景 | +| `text2vec-large-chinese` | 1024 | 国产中文模型 | 中文专用场景 | + +选择建议: + +1. **优先用 Embedding 模型,不用通用 LLM**:LLM 最后一层的 hidden state 不是为相似度任务优化的,效果通常不如专门的 Embedding 模型 +2. **中文场景优先考虑中文优化的模型**:OpenAI 的模型也支持中文,但中文优化的模型(如 bge-large-zh)在纯中文场景可能更好 +3. **维度不是越高越好**:维度高意味着更多存储和计算。OpenAI 支持设置 `dimensions` 参数进行降维,从 1536 降低到 256 还能保持 95%+ 的性能 + +### 向量数据库 + +当知识库只有几十个片段时,你可以像上面的示例一样直接在内存中做余弦相似度计算。但当片段数量达到数万、数百万时,就需要一个专门的**向量数据库(Vector Database)**。 + +向量数据库的核心能力是**近似最近邻搜索(ANN,Approximate Nearest Neighbor)**——不像暴力扫描那样精确但慢,而是用索引算法(HNSW、IVF 等)在可接受的精度损失下大幅提升检索速度。 + +| 工具 | 类型 | 规模 | 特点 | +|------|------|------|------| +| **Chroma** | 本地嵌入式 | 小规模 | 简单易用,适合原型 | +| **FAISS** | 本地库 | 中大规模 | Meta 开源,性能好 | +| **Milvus** | 分布式 | 大规模 | 云原生,功能完整 | +| **Weaviate** | 分布式 | 中大规模 | 原生支持多种 Embedding 模型 | +| **Qdrant** | 分布式 | 中大规模 | Rust 实现,性能优异 | +| **PostgreSQL + pgvector** | 扩展 | 中规模 | 如果你已经在用 Postgres | + +用 LangChain 集成向量数据库非常简单: + +```python +from langchain_community.vectorstores import Chroma +from langchain_openai import OpenAIEmbeddings + +# 生成向量并存入 Chroma +embeddings = OpenAIEmbeddings(model="text-embedding-3-small") +vectorstore = Chroma.from_texts( + texts=chunks, + embedding=embeddings, + persist_directory="./chroma_db" +) + +# 检索 +results = vectorstore.similarity_search("什么是 RAG?", k=3) +``` + +### Token 数量与成本控制 + +向量化的成本跟文本长度直接相关。以 OpenAI 为例: + +- `text-embedding-3-small`:$0.02 / 1M token +- `text-embedding-3-large`:$0.13 / 1M token + +如果知识库有 100 万 token(大约 3~4 本 300 页的书),small 模型的向量化成本约 $0.02,但存储为 1536 维浮点数后占用约 6MB。如果使用大型知识库需要考虑存储和内存占用。 + +## 最小示例 + +以下代码对比 OpenAI 和本地模型的向量化效果: + +```python +import openai + +# ── 使用 OpenAI Embedding ── +response = openai.embeddings.create( + model="text-embedding-3-small", + input=["什么是 RAG?", "RAG 是检索增强生成"], + dimensions=256 # 降维,节省存储 +) +vectors = [d.embedding for d in response.data] +print(f"向量维度:{len(vectors[0])}") # 256(通过 dimensions 降维) +print(f"向量前 5 维:{vectors[0][:5]}") + +# ── 使用 Sentence Transformers(本地,适合离线)── +from sentence_transformers import SentenceTransformer + +model = SentenceTransformer("BAAI/bge-large-zh-v1.5") +embeddings = model.encode(["什么是 RAG?", "RAG 是检索增强生成"]) +print(f"本地模型向量维度:{len(embeddings[0])}") # 1024 +print(f"相似度矩阵形状:{embeddings.shape}") +``` + +## 常见误区 + +!!! failure "误区 1:越贵的 Embedding 模型效果越好" + 不一定。OpenAI 的 large 模型比 small 模型贵 6.5 倍,但在 MTEB 基准上只高出不到 5 个点。对于大多数应用场景,small 模型已经足够。如果 budget 有限,先用 small。 + +!!! failure "误区 2:Embedding 模型选好后就不用管了" + Embedding 模型持续在进化。建议每隔 6~12 个月重新评估一次(尤其是中文场景),看看更新的模型能否提升检索效果。Embedding 替换后需要重新索引全部知识库。 + +!!! failure "误区 3:向量相似度 = 语义相似度" + 向量相似度是语义相似度的一种近似。它可能把同义词算得很近(好的),也可能把反义词算得很近(不好)。比如「喜欢」和「讨厌」的向量可能比较接近,因为它们在很多语境中互换出现。 + +## 延伸阅读 + +- [文档切分](chunking.md) —— 向量化之前,文本先要切好 +- [检索](retrieval.md) —— 向量化之后,如何用向量搜索 +- [什么是多模态 AI](../basics/multimodal-ai.md) —— Embedding 思想在图像领域的应用 + +## 练习题 + +??? question "练习 1:对比不同 Embedding 模型" + + 准备 5 个中文句子(主题尽量不同),分别用以下方式计算两两之间的余弦相似度: + + 1. 用 OpenAI `text-embedding-3-small` + 2. 用本地 `BAAI/bge-large-zh-v1.5`(或 `shibing624/text2vec-base-chinese`) + 3. 用 One-Hot 编码 + Jaccard 相似度(最简单的基线) + + 对比三者的结果:哪些句子对在语义模型下相似度很高,但在 One-Hot 下很低?这说明什么? + +??? question "练习 2:计算你的知识库向量化成本" + + 假设你有一个 10 万 token 的中文文档库,需要选择合适的 Embedding 模型和向量数据库。回答: + + 1. 用 `text-embedding-3-small`($0.02/1M token)的成本是多少? + 2. 用 `text-embedding-3-large`($0.13/1M token)的成本是多少? + 3. 存储 1536 维 vs 256 维向量,存储空间差多少倍? + 4. 如果每天有 100 次查询,每次返回 Top-5,用 FAISS 和暴力扫描对延迟的影响大概差多少? diff --git a/docs/rag/why-rag.md b/docs/rag/why-rag.md index afeb054..25f168d 100644 --- a/docs/rag/why-rag.md +++ b/docs/rag/why-rag.md @@ -1,10 +1,138 @@ +--- +tags: + - RAG +--- + # 为什么需要 RAG -> 占位页:本页内容待团队补充。 +> RAG 解决的冲突是:LLM 什么都知道一点,但什么都不够精确——它不是你公司内部的活百科。 + +## 这章解决什么问题 + +你打开 ChatGPT,问了一个关于你们公司内部流程的问题。它回答得非常有条理、非常自信,但——完全是错的。 + +这不是模型笨,而是它根本没见过你们公司的文档。**LLM 的知识来自训练数据,而训练数据不可能包含你的私有信息。** + +那么,把整本产品手册塞进 Prompt 行不行?有两个问题: + +1. **上下文窗口有限**——OpenAI 的上下文窗口从 8K 到 200K token 不等,但一本产品手册可能有几十万 token +2. **信息密度不均**——用户只想知道「怎么重置密码」,但你需要从几万字的文档里找到那一行 + +RAG 的思路就是换一个角度:**不把所有文档塞进 Prompt,而是先找到最相关的那几段,再把它们给模型看。** + +## 核心概念 + +### LLM 的三个天然局限 + +要理解为什么需要 RAG,先要理解 LLM 本身有哪些做不到的事: + +**局限 1:知识有截止日期** + +LLM 的训练数据有一个截止时间。GPT-4 的知识截止于 2023 年 10 月左右。如果你问「2025 年发布的 iPhone 17 有什么新功能」,它不可能知道,因为训练时这台手机还没发布。 + +**局限 2:看不到私有数据** + +你的公司文档、产品手册、用户协议、内部知识库——LLM 一概不知。它的训练数据来自互联网公开内容,不包含任何非公开信息。 + +**局限 3:不知道的它会「编」** + +当 LLM 被问到不知道的问题时,它倾向于**自信地给出看起来合理的错误答案**。这种现象叫**幻觉(Hallucination)**。不是模型故意撒谎,而是它的工作机制就是「根据上下文预测最合理的下一个词」。如果没有相关信息,它会预测一个「听起来靠谱」的答案。 + +RAG 通过提供真实的参考资料来缓解这三个问题:用检索获取最新信息、用私有知识库补充模型盲区、用原文减少编造空间。 + +### RAG 解决什么、不解决什么 + +!!! success "RAG 擅长解决的场景" + - **知识问答**:用户问什么,你正好有文档 + - **客服支持**:基于产品手册回答用户问题 + - **文档解读**:从长篇合同中提取关键条款 + - **私有数据处理**:公司内部文档、研究报告、用户协议 + - **知识库查询**:结合已有数据库进行问答 + +!!! warning "RAG 不适合的场景" + - **知识库里没有的内容**:检索不到就是检索不到,RAG 变不出来 + - **需要深度推理的问题**:RAG 提供素材,但多步推理靠模型自身推理能力 + - **格式转换 / 风格迁移**:不需要外部知识,直接 LLM 即可 + - **极低延迟场景**:RAG 多一次检索调用,增加几十到几百毫秒 + - **高频更新的知识**:知识库更新不及时,RAG 反而给出过时答案 + +### RAG vs 微调 vs 长上下文模型 + +| 维度 | RAG | 微调 (Fine-tuning) | 长上下文模型 | +|------|-----|-------------------|-------------| +| 知识更新 | ✅ 改知识库即可 | ❌ 需重新训练 | ✅ 直接塞入新内容 | +| 知识删除 | ✅ 删除知识库条目 | ❌ 无法「忘记」 | ✅ 不塞即可 | +| 知识透明度 | ✅ 可追踪来源 | ❌ 知识嵌入参数中 | ✅ 上下文可见 | +| 一次性成本 | 低(搭知识库) | 高(训练 + 标注数据) | 低 | +| 运行成本 | 中(检索 + 生成) | 低(只需生成) | 高(越长越贵) | +| 延迟 | 中 | 低 | 低 | +| 适合 | 知识密集型场景 | 能力/风格调整 | 长文档一次性分析 | + +## 最小示例:一个具体场景的对比 + +假设你是一家 SaaS 公司的客服,用户问:「我们的订阅套餐能随时取消吗?」 + +**不用 RAG**: + +``` +LLM 凭训练数据回答: +大多数订阅服务都可以随时取消。建议你登录账户查看设置页面。 + +→ 错误!你们公司的政策是年付套餐只能 30 天内取消。 +``` + +**用 RAG**: + +``` +用户问题:我们的订阅套餐能随时取消吗? + +检索到的资料片段: +"月付套餐可以随时取消,取消后下个计费周期不再扣费。 +年付套餐在购买后 30 天内可以取消,超过 30 天需联系客服协商, +按剩余天数比例退款。" + +LLM 生成的回答(基于资料): +根据我们公司的订阅政策(来源:服务条款 - 取消政策): + +- 月付套餐:随时可取消,次月起不再扣费 +- 年付套餐:购买后 30 天内可取消,超过 30 天需联系客服处理 + +需要我帮您操作取消吗? +``` + +## 常见误区 + +!!! failure "误区 1:RAG 就必须用向量数据库" + 向量数据库是为大规模检索设计的工具。如果你的知识库只有几百条,用内存计算或 BM25 传统搜索也能实现 RAG。工具的选择取决于规模,不是定义。 + +!!! failure "误区 2:有了 RAG 模型就不会产生幻觉" + RAG 能**降低**幻觉,但不能**根除**。如果检索回来的资料与问题无关,或者 LLM 忽略了资料里的信息,幻觉仍然可能出现。生成阶段的 Prompt 设计同样重要。 + +!!! failure "误区 3:RAG 不需要专门设计 Prompt" + 资料怎么拼、要不要标来源、先写资料还是先写问题——这些 Prompt 编排细节直接影响回答质量。一句「基于以上资料回答」和详细的约束条件,效果差别很大。 + +!!! failure "误区 4:既然有长上下文模型了,RAG 就不需要了" + 长上下文模型能一次性塞入更多内容,但「能塞」不等于「能找到」。研究表明,当上下文长度超过一定阈值后,模型对中间部分信息的关注度会显著下降。RAG 通过只保留最相关的片段来解决这个问题。 + +## 延伸阅读 + +- [RAG 总览](index.md) —— 回到模块入口 +- [什么是 LLM](../basics/what-is-llm.md) —— 理解 LLM 的能力和局限 +- [为什么模型会胡说](../basics/hallucination.md) —— 深入理解幻觉现象 +- [Chat、Copilot、Agent 与 Workflow](../tools/workflow.md) —— RAG 与工具调用的协作 + +## 练习题 + +??? question "练习:为你的工作场景设计 RAG 方案" + + 想一个你自己工作或学习中「需要频繁查资料」的场景,回答以下问题: -## 建议内容 + 1. 你的知识库来源是什么?(文档、Wiki、数据库、聊天记录?) + 2. 这些资料多久更新一次?更新后是否需要立刻反映在回答中? + 3. 用户最常问的 3 类问题是什么? + 4. 如果这些问题的答案就在知识库里但模型回答错了,最可能是哪个环节出了问题? -- 问题背景 -- 典型场景 -- 能解决什么 -- 不能解决什么 + ??? done "参考思路" + - 如果知识库更新频繁(如产品手册每周更新),RAG 比微调更适合 + - 如果用户的问题集中在少数几个主题上,可以针对性地优化切分策略 + - 回答出错的常见原因:检索没找到正确片段(切分/Embedding 问题)> LLM 没正确使用资料(Prompt 问题)> 资料本身过时 From 612b35a61771d5d51b7622b298ec34075ebc5e1c Mon Sep 17 00:00:00 2001 From: youyeyejie Date: Thu, 28 May 2026 16:26:09 +0800 Subject: [PATCH 2/2] fix: fix mistyping and error url --- docs/rag/rerank.md | 2 +- docs/rag/retrieval.md | 2 +- docs/rag/troubleshooting.md | 4 ++-- docs/rag/why-rag.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/rag/rerank.md b/docs/rag/rerank.md index 214b760..6914eba 100644 --- a/docs/rag/rerank.md +++ b/docs/rag/rerank.md @@ -171,7 +171,7 @@ for doc, score in ranked: 重排模型是每次查询都要调用的,不是索引阶段预先计算。模型越重,延迟越高。轻量模型(如 bge-reranker-v2-m3)在多数场景下已经足够。 !!! failure "误区 3:重排只做一次就够" - 当知识库规模很大时,可以考虑**级联重排(Cascaded Rerank)**——先用轻量模型从 Top-100 筛到 Top-20,再用量量模型从 Top-20 筛到 Top-3。既保证精度又控制延迟。 + 当知识库规模很大时,可以考虑**级联重排(Cascaded Rerank)**——先用轻量模型从 Top-100 筛到 Top-20,再用更重的模型从 Top-20 筛到 Top-3。既保证精度又控制延迟。 ## 延伸阅读 diff --git a/docs/rag/retrieval.md b/docs/rag/retrieval.md index ad52bb3..d7440bb 100644 --- a/docs/rag/retrieval.md +++ b/docs/rag/retrieval.md @@ -187,7 +187,7 @@ for text, score in results: - [向量化](vectorization.md) —— 检索的数据基础 - [重排](rerank.md) —— 检索结果的二次排序 - [生成](generation.md) —— 检索结果如何输入 LLM -- [BM25 算法详解](https://zh.wikipedia.org/wiki/BM25) +- [BM25 算法详解](https://en.wikipedia.org/wiki/Okapi_BM25) ## 练习题 diff --git a/docs/rag/troubleshooting.md b/docs/rag/troubleshooting.md index fd75e7e..64d0385 100644 --- a/docs/rag/troubleshooting.md +++ b/docs/rag/troubleshooting.md @@ -61,7 +61,7 @@ RAG 的调试比单环节系统更复杂,因为**每一个环节都可能是 - 说了「如果找不到相关信息就说不知道」吗? - 约束不够 → 优化 Prompt - 10. **temeperature 高不高?** + 10. **temperature 高不高?** - temperature > 0.5 会增加 LLM 的生成随机性 - 对于 RAG 场景,0.1~0.3 更合适 @@ -252,7 +252,7 @@ for issue in issues: - [生成](generation.md) —— 调试的常见终点 - [检索](retrieval.md) —— 调试的常见起点 - [为什么需要 RAG](why-rag.md) —— 回顾 RAG 的能力边界 -- [RAG 分类与评估](https://docs.llamaindex.ai/en/stable/module_guides/querying/retrievers/) +- [RAG 分类与评估](https://docs.llamaindex.ai/en/stable/module_guides/querying/retriever/retrievers/) ## 练习题 diff --git a/docs/rag/why-rag.md b/docs/rag/why-rag.md index 25f168d..815f342 100644 --- a/docs/rag/why-rag.md +++ b/docs/rag/why-rag.md @@ -28,7 +28,7 @@ RAG 的思路就是换一个角度:**不把所有文档塞进 Prompt,而是 **局限 1:知识有截止日期** -LLM 的训练数据有一个截止时间。GPT-4 的知识截止于 2023 年 10 月左右。如果你问「2025 年发布的 iPhone 17 有什么新功能」,它不可能知道,因为训练时这台手机还没发布。 +LLM 的训练数据有一个截止时间。GPT-4o 的知识截止于 2023 年 10 月左右。如果你问「2025 年发布的 iPhone 17 有什么新功能」,它不可能知道,因为训练时这台手机还没发布。 **局限 2:看不到私有数据**