本章学习目标
- 理解标准 RAG 单次固定管道的局限,以及高级 RAG 模式引入反馈循环的共同动机
- 实现 Self-RAG 的自反思机制,判断检索结果的相关性、充分性以及答案的忠实度
- 实现 CRAG 的三级降级策略,在内部知识不足时自动切换到网络搜索
- 用 HyDE 提升短查询和模糊查询的检索召回率
- 掌握 RAPTOR 的层次化索引思想与 Fusion Retrieval 的多路召回融合,并理解五种模式的组合与选型方法
在《第13章:RAG 系统调优实战》中,我们通过控制变量法对 chunk_size、Top-K、Embedding 模型和检索策略进行了系统调优,将 MRR、Hit Rate、Faithfulness 等指标推到了当前架构下的较优水平。但无论怎么调参,标准 RAG 的管道结构始终没有改变,即它是一个 检索一次,生成一次 的固定流水线,成败完全取决于这一次检索是否命中。
这个结构性限制在调优初期并不明显,因为参数空间还有大量余地可挖。但一旦调优收敛,问题看可能就会就会集中暴露,比如检索到的文档明明和问题不相关,模型仍然会基于它生成答案;内部知识库根本没有覆盖到的问题,系统只能一本正经地编造;用户问题本身模糊不清,检索自然也无从下手。这些不是调参能解决的问题,而是管道形态的问题。
高级 RAG 模式的共同思路是给固定管道加上反馈循环和自适应决策——检索结果不好就重新检索,内部知识不够就查外部,问题模糊就先生成一个更具体的查询。本章介绍五种经过社区验证、工程上可落地的高级模式,它们分别在管道的不同环节引入自适应能力。
一、为什么需要高级 RAG 模式
标准 RAG 的管道可以概括为三步:接收查询、检索文档、基于文档生成答案。这个流程简单可靠,但存在三类典型失效场景。
(1)检索结果不相关或不充分时,生成环节没有感知。 标准 RAG 不会判断检索到的内容是否真的能回答问题,它只会把检索到的东西一股脑塞进 Prompt,让模型"尽力而为"。结果就是模型在不充分的上下文里编造细节,产生幻觉。
(2)内部知识库覆盖不到的问题,系统束手无策。 企业知识库通常只覆盖内部文档、产品手册这类静态内容,遇到需要获取最新版本特性相关的这种需要实时信息的问题,标准 RAG 检索到的要么是空的,要么是过时的内容。
(3)用户查询信息密度低时,向量检索效果差。 像"这个怎么配?"这样的短查询,缺乏具体术语,Embedding 很难把它和文档库中的具体内容对齐,检索精度自然上不去。
这三类问题分别对应本章要讲的 Self-RAG(自反思判断充分性)、CRAG(降级到外部搜索)和 HyDE(先生成假设答案再检索)。此外,RAPTOR 解决的是文档结构丢失的问题,Fusion Retrieval 解决的是单一检索策略召回不全的问题。五种模式看似独立,实际上都是在标准管道的某个环节插入了一次额外的判断或变换。
graph TD
A[标准 RAG 管道] --> B[检索环节]
A --> C[生成环节]
B --> D[检索质量不可判断<br/>使用Self-RAG]
B --> E[内部知识覆盖不足<br/>使用CRAG]
B --> F[查询信息密度低<br/>使用HyDE]
B --> G[文档结构丢失<br/>使用RAPTOR]
B --> H[单路检索召回不全<br/>使用Fusion Retrieval]
二、Self-RAG:自反思检索增强
1. 核心思想
Self-RAG 由 Asai 等人于 2023 年提出,核心创新是在检索和生成的每个环节引入自我反思机制,而不是盲目信任检索结果。
原始论文通过端到端训练,让模型学会输出四类特殊的"反思令牌"(reflection tokens),在推理阶段自主决策:
- Retrieve:是否需要检索?可选值为 yes / no / continue
- IsRel:检索到的文档与问题相关吗?可选值为 relevant / irrelevant
- IsSup:生成的内容有文档支撑吗?可选值为 fully supported / partially supported / no support
- IsUse:这个回答对用户有用吗?1~5 分评分
工程实践中,不需要专门训练带这些特殊 token 的模型,用普通 LLM 配合结构化 Prompt 模拟这四个判断节点,已经能取得不错的效果。
graph LR
A[用户查询] --> B{需要检索?}
B -->|否| C[直接生成答案]
B -->|是| D[检索文档]
D --> E{文档是否相关?}
E -->|否| F[改写查询重新检索]
F --> D
E -->|是| G[生成答案]
G --> H{答案是否有支撑?}
H -->|否| I[重新生成]
I --> G
H -->|是| J[输出答案]
Self-RAG 引入了三个关键判断:检索到的文档是否与问题相关,检索到的信息是否足以回答问题,生成的答案是否忠实于检索结果。任何一步判断为否,就触发对应的自适应行为。
2. 实现自反思检查
自反思的核心是让 LLM 扮演评估者的角色,对检索结果或生成答案做结构化打分。
// 1. 模型与结果定义
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
// 初始化 LLM,temperature 设为 0 保证评估结果稳定可复现
const llm = new ChatOpenAI({ modelName: "gpt-4o", temperature: 0 });
interface SelfCheckResult {
relevant: boolean; // 检索结果是否相关
sufficient: boolean; // 信息是否充分
missing?: string; // 缺少的信息
}
检查检索质量和检查答案忠实度用的是两套不同的 Prompt,因为评估对象不同:前者对比查询和文档,后者对比答案和文档。
// 2. 检查检索结果的质量
async function checkRetrieval(
query: string,
context: Document[]
): Promise<SelfCheckResult> {
const prompt = `请判断以下检索结果是否足以回答问题。
问题:${query}
检索结果:
${context.map(d => d.pageContent).join("\n\n")}
请判断:
1. 检索结果是否与问题相关?
2. 是否有足够的信息来回答问题?
3. 如果不够,还缺少什么信息?
仅输出 JSON:{"relevant": true/false, "sufficient": true/false, "missing": "缺少的信息描述"}`;
const messages = [
new SystemMessage("你是一个专业的评估助手,请严格基于给定信息进行判断。"),
new HumanMessage(prompt),
];
const response = await llm.invoke(messages);
try {
return JSON.parse(response.content as string);
} catch {
return { relevant: true, sufficient: false, missing: "无法判断" };
}
}
忠实度检查关注答案中的每个事实陈述是否都能在参考资料中找到支撑,这是防止模型在生成阶段"脑补"的最后一道关卡。实现思路是先将回答拆成独立的原子声明,再逐条核验是否被上下文支持。
// 3. 检查生成答案的忠实度
async function checkFaithfulness(
query: string,
context: Document[],
answer: string
): Promise<{ faithful: boolean; issues?: string }> {
const prompt = `请评估以下答案是否忠实于提供的参考资料。
问题:${query}
参考资料:
${context.map(d => d.pageContent).join("\n\n")}
生成答案:${answer}
请判断答案中的每个事实陈述是否都有参考资料支撑,是否存在编造或过度推断的内容。
仅输出 JSON:{"faithful": true/false, "issues": "如果有问题,说明具体问题"}`;
const response = await llm.invoke([new HumanMessage(prompt)]);
try {
return JSON.parse(response.content as string);
} catch {
return { faithful: false, issues: "解析失败" };
}
}
3. 完整流程编排
把检索、自反思、生成、忠实度检查串联起来,形成一个带重试上限的闭环。
class SelfRAGSystem {
private maxRetries = 2;
constructor(
private retriever: VectorStoreRetriever,
private generator: RAGGenerator
) {}
// 回答用户查询的主流程
async answer(query: string): Promise<{ answer: string; iterations: number }> {
let currentQuery = query;
for (let i = 1; i <= this.maxRetries; i++) {
// 第一步:检索文档
const docs = await this.retriever.invoke(currentQuery);
if (docs.length === 0) break;
// 第二步:自反思——检查检索质量
const check = await checkRetrieval(query, docs);
// 如果文档不相关,改写查询后重新检索
if (!check.relevant) {
currentQuery = await this.rewriteQuery(query, check.missing);
continue; // 跳过本轮,进入下一次检索
}
// 如果信息不充分,扩大检索范围(k 翻倍)后重新检索
if (!check.sufficient) {
this.retriever.k = Math.min(this.retriever.k * 2, 20); // 上限 20,避免一次拉太多
continue;
}
// 第三步:生成答案
const answer = await this.generator.generate(query, docs);
// 第四步:自反思——检查答案忠实度
const faithfulness = await checkFaithfulness(query, docs, answer);
// 如果答案忠实于参考资料,直接返回
if (faithfulness.faithful) {
return { answer, iterations: i };
}
// 否则进入下一轮重试(重新检索)
}
// 达到最大重试次数,返回最佳结果
const docs = await this.retriever.invoke(query);
const answer = await this.generator.generate(query, docs);
return { answer, iterations: this.maxRetries };
}
// 根据缺失信息改写原始查询
private async rewriteQuery(original: string, missing?: string): Promise<string> {
if (!missing) return original;
const prompt = `原始查询: ${original}\n缺失的信息: ${missing}\n请改写查询,使其更可能检索到缺失的信息。只输出改写后的查询。`;
const response = await llm.invoke(prompt);
return (response.content as string).trim();
}
}
以查询"如何配置数据库连接池"为例,第一次检索到的 5 个文档如果判定为相关但不充分,系统将 k 扩大到 10 后重新检索,第二次判定充分并生成答案,忠实度检查通过后输出结果,总共迭代 2 次。
注意,Self-RAG 的每次判断都需要额外调用一次 LLM,延迟和成本都会增加。生产环境中建议加上缓存机制,对相同的查询和文档组合复用之前的判断结果。同时,重试次数不宜过多,一般 2~3 次即可,避免陷入无限循环。
更多 Agent 化的自适应决策会在《第15章:Agent RAG》展开,Self-RAG 的重试循环本质上就是一个简化版的 Agent 推理循环。
三、CRAG:修正检索增强生成
1. 核心思想
Self-RAG 的重试逻辑始终局限在内部知识库里打转,但如果内部知识本身就不覆盖某个问题,再怎么重试也无济于事。CRAG(Corrective RAG) Please provide the text you would like me to translate.Please provide the text you would like me to translate.由 Yan 等人在 2024 年提出,核心创新是当内部检索质量不足时,自动切换到外部搜索。
graph TD
A[用户查询] --> B[内部知识库检索]
B --> C{检索质量如何}
C -->|高| D[基于内部知识生成]
C -->|中| E[内部 + 网络搜索<br/>合并后生成]
C -->|低| F[纯网络搜索<br/>生成]
CRAG 把检索质量分为三档:高质量直接用内部知识,中等质量合并内外部知识,低质量完全依赖外部搜索。这个三档判断复用了 Self-RAG 的自反思逻辑,只是把判断结果映射到了三种不同的数据源策略上。
2. 质量评估与数据源切换
评估函数直接复用第二节的 checkRetrieval,避免重复实现相似度判断逻辑。
/**
* 评估内部检索结果的质量档位
* 复用 Self-RAG 的自反思逻辑,将结果映射为三档
*/
async function assessQuality(
query: string,
docs: Document[]
): Promise<"high" | "medium" | "low"> {
// 完全没有检索到文档,直接判定为低质量
if (docs.length === 0) return "low";
const check = await checkRetrieval(query, docs);
// 既相关又充分,判定为高质量
if (check.relevant && check.sufficient) return "high";
// 相关但不充分,判定为中等质量
if (check.relevant) return "medium";
// 不相关,判定为低质量
return "low";
}
根据质量档位选择数据源,是 CRAG 的核心分支逻辑。
class CRAGSystem {
constructor(
private internalRetriever: VectorStoreRetriever, // 内部向量检索器
private generator: RAGGenerator, // 答案生成器
private webSearchAPI: WebSearchAPI // 网络搜索 API
) {}
async answer(query: string): Promise<string> {
// 第一步:从内部知识库检索
const internalDocs = await this.internalRetriever.invoke(query);
// 第二步:评估检索质量
const quality = await assessQuality(query, internalDocs);
// 第三步:根据质量档位选择数据源
let finalDocs: Document[];
if (quality === "high") {
// 高质量:直接用内部知识
finalDocs = internalDocs;
} else if (quality === "medium") {
// 中等质量:内部 + 外部合并
finalDocs = [...internalDocs, ...(await this.webSearch(query))];
} else {
// 低质量:完全依赖外部搜索
finalDocs = await this.webSearch(query);
}
// 第四步:基于最终文档生成答案
return this.generator.generate(query, finalDocs);
}
/**
* 调用网络搜索 API,将结果转换为 Document 格式
* 统一内部文档和外部文档的数据结构,方便后续合并处理
*/
private async webSearch(query: string): Promise<Document[]> {
const results = await this.webSearchAPI.search(query, { num: 5 });
return results.map(r => new Document({
pageContent: r.snippet || r.title, // 优先用摘要,没有则用标题
metadata: { source: r.url, title: r.title, type: "web" },
}));
}
}
三档策略对应三类比较典型场景,比如问"公司的部署流程是什么"这种内部文档能完全覆盖的问题走高质量分支;问"React 18 的新特性"这种内部教程部分覆盖的问题走中等质量分支,合并内外部知识;问"React 19 的最新特性"这种内部知识完全没有的问题走低质量分支,纯外部搜索。
CRAG 的网络搜索 API 调用会产生额外成本,建议对相同查询做缓存。同时,合并内外部文档时要注意去重和排序。如果内部文档和外部搜索结果高度相似,保留质量更高的那个即可,避免 Prompt 上下文被冗余信息占满。
3. Self-RAG 与 CRAG 的关系
两者经常被放在一起比较,但它们解决的是不同层面的问题。
| 特性 | Self-RAG | CRAG |
|---|---|---|
| 搜索范围 | 仅内部知识库 | 内部 + 外部 |
| 自适应行为 | 重新检索、改写查询 | 降级到外部搜索 |
| 适用场景 | 内部知识基本覆盖 | 内外部知识混合 |
| 额外成本 | LLM 调用 1-2 次 | LLM 调用 + 网络搜索 API |
两者并不互斥,可以组合使用:先用 Self-RAG 在内部重试一到两轮,如果仍然判定为不充分,再触发 CRAG 的降级策略走向外部搜索。这样既避免了不必要的外部调用,又保证了兜底能力。
四、HyDE:先猜再搜
前面两节讨论的 Self-RAG 和 CRAG,都是在检索之后做文章,要么反思检索质量,要么切换数据源。HyDE(Hypothetical Document Embeddings,假设性文档嵌入)则把优化点前移到了检索之前。
1. 核心思想
标准 RAG 直接用用户的原始查询去做向量检索,但短查询和长文档在嵌入空间里天然不对称,一个十几字的口语化问题,和一个几百字的专业文档段落,它们的向量往往相距甚远。比如像"这个怎么配"这种短查询信息密度太低,检索器很难在语义空间里把它和具体文档对齐。
HyDE(Hypothetical Document Embeddings) 由 Gao 等人在 2022 年提出,它的核心思路是先让 LLM 生成一个假设性的答案,再用这个假设答案去检索。即使假设答案内容不准确,也可能包含幻觉,它也会天然包含比原始查询丰富得多的专业术语、上下文、句式结构、信息密度,这些都能让向量检索更精准地锚定目标区域。
graph LR
A[用户查询] --> B[LLM 生成假设答案]
B --> C[用假设答案检索]
C --> D[用原问题 + 真文档生成最终答案]
2. 实现方案
生成假设答案时不需要要求 LLM 给出正确答案,只需要合理的猜测,因为检索环节找的是相关文档,不是验证假设答案本身的正确性。LangChain 提供了 HypotheticalDocumentEmbedder可以直接封装这一逻辑,但手动实现更能看清底层机制。
class HyDERetriever {
constructor(
private llm: ChatOpenAI,
private generator: RAGGenerator,
private baseRetriever: VectorStoreRetriever
) {}
async retrieve(query: string): Promise<Document[]> {
const hypothesis = await this.generateHypothesis(query);
return this.baseRetriever.invoke(hypothesis);
}
private async generateHypothesis(query: string): Promise<string> {
const prompt = `请针对以下问题写一个假设性的回答段落。
注意:你可能不知道真实答案,这是用来辅助检索的,写一个合理的猜测即可,
尽量包含相关的术语和上下文,长度控制在 100-200 字。
问题:${query}
假设回答:`;
const response = await this.llm.invoke(prompt);
return (response.content as string).trim();
}
async answer(query: string): Promise<string> {
// 用 HyDE 策略检索真实文档
const docs = await this.retriever(query);
// 基于真实文档生成最终答案
return this.generator.generate(query, docs);
}
}
以查询"新版React有什么新特性"为例,如果直接用这个短查询做向量检索,可能召回一堆 React 16/17 的旧文档,因为嵌入空间里它们距离很近。但 HyDE 先生成的假设文档会包含"Server Components""Actions""use() Hook"等具体术语,用这个假设文档的向量去检索,就能更精准地命中真正讨论 React 19 新特性的文档。
3. 优势、局限与适用场景
HyDE 在短查询和模糊查询上能显著提升召回率,实现成本也不高,只需要额外一次 LLM 调用。但它会增加 1 到 2 秒的延迟,对于查询本身已经足够明确的场景收益有限,甚至可能因为假设答案引入无关术语而拖累检索精度。因此 HyDE 更适合客服场景中大量出现的短查询和模糊查询,不适合延迟敏感或查询已经很精确的场景。
五、RAPTOR:层次化文档索引
1. 核心思想
标准 RAG 把文档切分成大小均匀的 chunk,这个过程丢失了文档的整体结构,导致两类问题:宏观问题难以回答(比如"这份文档主要讲了什么"),以及检索时需要在所有片段中暴力搜索,效率随文档量线性下降。
RAPTOR(Recursive Abstractive Processing for Tree-Organized Retrieval) 由 Sarthi 等人在 2024 年提出,通过聚类和递归摘要构建多层树形索引:先对原始片段做 Embedding 和聚类,再为每个簇生成摘要,摘要本身作为更高一层的"文档"继续递归聚类和摘要,最终形成一棵树。
graph TD
A[原始文档] --> B[切分为片段]
B --> C[Embedding + 聚类]
C --> D[为每个簇生成摘要]
D --> E[递归处理摘要]
E --> F[形成树形结构]
G[查询] --> H[从根节点开始搜索]
H --> I[选择最相关的簇]
I --> J[逐层向下]
J --> K[到达叶子节点]
检索时从根节点开始,先比较查询和顶层摘要的相似度,选出最相关的簇后逐层向下,直到到达叶子节点返回具体片段。这样宏观问题在顶层摘要就能直接命中答案,细节问题则需要一路钻到叶子节点。
2. 实现思路
RAPTOR 的完整实现涉及聚类算法选型和摘要生成两个独立环节,这里先给出树的递归构建逻辑。
interface TreeNode {
type: "leaf" | "internal";
docs?: Document[];
summary?: string;
embedding: number[] | null;
children: TreeNode[];
}
async function buildTree(docs: Document[], depth = 0): Promise<TreeNode> {
if (docs.length <= 1 || depth > 3) {
return { type: "leaf", docs, embedding: null, children: [] };
}
const clusters = clusterDocuments(docs);
const children = await Promise.all(
clusters.map(async cluster => buildTree(cluster, depth + 1))
);
return { type: "internal", embedding: null, children };
}
检索逻辑按相似度排序子节点,优先递归搜索最相关的分支,凑够 k 个结果就提前返回。
async function searchTree(
node: TreeNode,
queryEmbedding: number[],
k: number
): Promise<Document[]> {
if (node.type === "leaf") {
return node.docs!.slice(0, k);
}
const ranked = node.children
.map(child => ({ child, sim: cosineSimilarity(queryEmbedding, child.embedding!) }))
.sort((a, b) => b.sim - a.sim);
const results: Document[] = [];
for (const { child } of ranked) {
results.push(...(await searchTree(child, queryEmbedding, k)));
if (results.length >= k) break;
}
return results.slice(0, k);
}
聚类算法(K-Means、层次聚类等)和摘要生成 Prompt 的具体实现因业务而异,这里不展开。实践中建议优先使用成熟的开源实现(如官方 RAPTOR 仓库或 LlamaIndex 的封装),自行实现聚类和树的维护成本较高。
3. 优势、局限与适用场景
RAPTOR 的优势是能同时支持宏观和微观检索,并保留了文档的层级结构,树形搜索的效率也高于暴力搜索。但代价是实现复杂,索引阶段需要额外的聚类和摘要计算,文档更新时树的维护成本也不低。因此 RAPTOR 适合文档量超过千篇、且文档本身有明显层级结构(如技术手册、法规条文)的场景,小型知识库上引入 RAPTOR 往往得不偿失。
六、Fusion Retrieval:多路召回融合
1. 核心思想
前面四种模式都在优化"检索之后怎么处理",Fusion Retrieval 关注的是检索本身:单一检索策略天然存在盲区。向量检索擅长捕捉语义相似性,但对精确的专有名词、型号、错误代码这类信息不敏感;关键词检索(如 BM25)擅长精确匹配,但对同义改写无能为力。Fusion Retrieval 的做法是并行执行多路检索,再用融合算法合并排序。
graph LR
A[用户查询] --> B[向量检索]
A --> C[BM25 关键词检索]
A --> D[Multi-Query 检索]
B --> E[RRF 融合排序]
C --> E
D --> E
E --> F[最终结果]
这部分内容在《第9章:高级检索策略》已经详细实现过混合检索和 RRF(Reciprocal Rank Fusion) 算法,这里不重复实现细节,只强调它在高级 RAG 版图中的位置:Fusion Retrieval 是在检索源头就做多样化,而 Self-RAG、CRAG 是在检索之后做质量判断和补救,两者是互补关系而非替代关系。
2. 与其他模式的组合
Fusion Retrieval 常作为其他高级模式的检索底座。比如 CRAG 判断内部检索质量时,如果内部检索本身就用了向量 + BM25 的融合策略,质量评估会更准确,因为召回的盲区已经被大幅缩小。同样,Self-RAG 的重试逻辑如果建立在纯向量检索之上,重试很可能还是命中同样的盲区;如果重试时切换到融合检索,命中缺失信息的概率会明显提高。
3. 适用场景
Fusion Retrieval 特别适合专有名词、型号、错误代码密集的技术文档场景,比如 API 文档、运维手册。这类场景中用户查询经常直接包含精确的函数名或报错信息,纯向量检索容易因为语义泛化而错过精确匹配,融合 BM25 之后能显著提升召回率。代价是需要维护两套索引(向量索引和倒排索引),存储和检索延迟都会有一定增加。
七、模式选择与组合指南
1. 五种模式对比
| 模式 | 解决的核心问题 | 额外成本 | 实施难度 | 适用场景 |
|---|---|---|---|---|
| Self-RAG | 检索结果不可信时无法自愈 | LLM 调用 1-2 次 | 中 | 对答案准确性要求严格的场景(法律、医疗) |
| CRAG | 内部知识覆盖不足 | LLM 调用 + 网络搜索 API | 中高 | 内外部知识混合使用的企业场景 |
| HyDE | 查询信息密度低 | LLM 调用 1 次 | 低 | 短查询、模糊查询为主的客服场景 |
| RAPTOR | 文档层级结构丢失 | 索引阶段聚类和摘要 | 高 | 大型文档库,需要宏观+微观检索 |
| Fusion Retrieval | 单一检索策略召回不全 | 多路检索 + 融合 | 中 | 专有名词密集的技术文档 |
2. 渐进式引入路径
不建议一次性引入所有高级模式,每种模式都会带来额外的延迟和调用成本,叠加使用时复杂度会成倍上升。更合理的做法是从《第12章:RAG 系统评估与指标体系》的评估指标出发,按需逐步升级。
先建立标准 RAG 基线(参考《第7章:基础篇实战:文档问答机器人》),如果评估发现查询以短查询和模糊查询为主,优先引入 HyDE,这是成本最低、见效最快的一步。如果 Faithfulness 指标偏低说明模型经常基于不充分的上下文生成答案,引入 Self-RAG 提升答案可靠性。如果内部知识库明显无法覆盖部分问题类型,引入 CRAG 补充外部搜索能力。如果查询中专有名词和精确匹配需求较多,引入 Fusion Retrieval。只有当文档量达到千篇规模且有明显层级结构时,才考虑投入较高成本引入 RAPTOR。每一步升级后都要用评估指标验证效果,避免凭直觉判断。
3. 模式组合示例
五种模式之间没有互斥关系,实践中经常组合使用。以下是一个 HyDE、Self-RAG、CRAG 三种模式串联的示例,体现了从查询增强到质量判断再到降级搜索的完整链路。
// HyDE 生成假设答案用于检索
const hypothesis = await generateHypothesis(query);
const docs = await retriever.invoke(hypothesis);
// Self-RAG 判断检索质量
const check = await checkRetrieval(query, docs);
if (!check.relevant) {
// CRAG 降级到外部搜索
const webDocs = await webSearch(query);
return generator.generate(query, webDocs);
}
return generator.generate(query, docs);
组合使用时需要注意,每增加一层判断逻辑,系统的可观测性要求就越高。建议给每一层加上日志埋点,记录触发了哪个分支、消耗了多少额外调用,这样出问题时才能快速定位是哪一环节的判断出了偏差。
FAQ
Q1:Self-RAG 和 CRAG 有什么区别?
Self-RAG 在 RAG 内部闭环:检索不好就重试,但重试范围始终局限在内部知识库。CRAG 在 RAG 外部开环:内部检索质量不足时直接切换到外部搜索。两者可以组合使用,先用 Self-RAG 在内部重试,如果仍然不足再触发 CRAG 的降级策略。
Q2:HyDE 会不会因为假设答案本身错误而检索到错误内容?
不会,这正是 HyDE 巧妙的地方。检索环节比较的是假设答案和文档库之间的语义相似度,找的是相关文档而不是验证假设答案的正确性。即使假设答案的具体内容是错的,它使用的术语和上下文往往是对的,这些术语才是驱动检索命中的关键。
Q3:Self-RAG 的自反思逻辑会不会每次都判定"不够",陷入死循环?
需要设置最大重试次数,通常 2 到 3 次即可。超过上限就停止检索并明确告知用户当前信息不足以回答,而不是无限重试。同时可以调整评估 Prompt,让判断标准适度宽松,避免模型过度谨慎。
Q4:RAPTOR 相比标准 RAG,检索速度会更快吗?
会。虽然索引阶段因为聚类和摘要而更耗时,但检索阶段的树形搜索能快速定位到相关区域,避免在全量文档中暴力搜索。对于百万级文档规模,RAPTOR 的检索速度提升可以达到一个数量级。
Q5:Fusion Retrieval 和 Self-RAG、CRAG 是竞争关系吗?
不是,它们作用在管道的不同环节。Fusion Retrieval 优化的是检索源头的召回率,Self-RAG 和 CRAG 优化的是检索之后的质量判断和补救。实践中经常把 Fusion Retrieval 作为 Self-RAG、CRAG 的检索底座,这样质量评估的准确性也会随之提升。
练习
练习 1:实现 HyDE 并对比效果
在你的 RAG 项目中实现 HyDE 方法,用 5 个短查询对比 HyDE 和标准检索的 MRR。
验证标准:
- HyDE 在短查询(不超过 5 个词)上的 MRR 至少提升 10%
- 能够展示假设答案和最终检索结果的差异
- 分析哪些查询从 HyDE 中受益最大
练习 2:实现 Self-RAG 的自反思判断
实现本章的自反思 Prompt,在你的评估集上测试自反思逻辑能否正确判断检索结果是否足够。
验证标准:
- 自查判断与人工标注的一致性超过 80%
- 能够识别不相关和不充分的检索结果
- 不会出现过度保守、总是判定不够的情况
练习 3:实现 CRAG 的三级降级策略
实现 CRAG 的高、中、低三档质量评估和对应的数据源切换逻辑,模拟内部知识不足的场景进行测试。
验证标准:
- 能够正确评估检索质量档位
- 低质量场景下能够切换到外部搜索
- 中等质量场景下能够正确合并内外部知识
练习 4:对比高级模式的综合效果
对你在《第12章:RAG 系统评估与指标体系》中构建的评估集,分别用标准 RAG、HyDE、Self-RAG 运行评估,对比各项指标。
验证标准:
- 量化每种模式的 MRR、Faithfulness、Answer Relevancy
- 分析哪种模式最适合你的业务场景
- 给出推荐的模式组合及理由
📚 延伸阅读
- Self-RAG 论文 — Asai et al., "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection"
- CRAG 论文 — Yan et al., "Corrective Retrieval Augmented Generation"
- HyDE 论文 — Gao et al., "Precise Zero-Shot Dense Retrieval without Relevance Labels"
- RAPTOR 论文 — Sarthi et al., "RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval"
- LangChain Advanced RAG — LangChain 的高级 RAG 功能文档,包含更多实现示例