超越朴素 RAG
基础检索的局限性。
超越朴素 RAG 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。
朴素 RAG 的工作方式
朴素 RAG 是基线方案:对文档分块、生成嵌入、存储向量、对查询生成嵌入、按余弦相似度检索前 k 个结果、将这些分块填入提示词,然后生成回答。它是一个很好的起点,但在大规模场景下会以可预见的方式失败。
理解这些失败模式,是掌握本课程所介绍的高级技术(重排序、压缩、查询重写)的前提。
def naive_rag(query, k=5):
q = embed(query)
chunks = vector_store.search(q, k) # top-k by cosine
context = '\n\n'.join(c.text for c in chunks)
return llm('Context:\n' + context + '\n\nQ: ' + query)检索召回率与精确率
朴素的前 k 个结果检索优化的是原始向量相似度,它将相关性与表面语义接近程度混为一谈。您会面临这样的矛盾:较小的 k 可能遗漏答案(召回率低);较大的 k 则会用干扰内容淹没上下文(精确率低)。
驱动检索的嵌入相似度只是对真实相关性的粗略代理,也是多个下游问题的根源。
# The core dilemma
# small k -> may miss the gold chunk (recall problem)
# large k -> distractors crowd context (precision + cost problem)
# Advanced RAG decouples 'retrieve many' from 'use few'嵌入不匹配问题
查询和文档通常处于不同的语言表达层次:一个是简短的问题,另一个是冗长的陈述性段落。双编码器嵌入可能会将相关答案放置在远离问题的位置,因为两者的措辞不同(即词汇不匹配问题)。
这促成了查询重写和 HyDE 等技术:它们会在检索前调整查询,使其匹配文档空间。
# Query: 'how do I revoke a token?'
# Doc: 'Token invalidation is performed via the /sessions endpoint.'
# Lexically and semantically distant -> bi-encoder may miss it
sim = cos(embed('how do I revoke a token?'),
embed('Token invalidation via /sessions')) # may be low中间内容丢失
即使检索到了正确的分块,将大量分块全部填入上下文也会触发中间内容丢失效应:模型对长上下文中间位置的内容关注不足。一个排在 10 个结果中的第 3 位、被埋在中间的正确分块,实际上可能会被忽略。
这也是采用重排序(将最佳分块放到模型最关注的位置)和压缩(缩小上下文,避免任何内容被埋没)的原因。
# Retrieval rank != attention rank
# Place the highest-relevance chunk at the START or END,
# never stranded in the middle of a large concatenation.对干扰内容的敏感性
LLM 对无关上下文十分敏感。加入看似合理但实际错误的分块,可能会使答案偏离正确方向,即使正确分块也同时存在。检索到更多上下文并不一定会持续带来更好的结果。
这正是精确率重要的原因:紧凑、经过重排序和压缩的上下文,往往胜过大量关联性松散的分块。
# Empirically: appending a single highly-similar but WRONG chunk
# can flip a previously-correct answer. RAG quality depends on
# keeping distractors OUT, not just getting the gold chunk IN.分块病变
固定大小分块会在句子中间切断思想,将论点与证据分开,并剥离结构上下文(属于哪个章节、哪个文档)。某个分块单独阅读时可能连贯,但脱离周围内容后却可能毫无用处,甚至产生误导。
高级处理流程会使用结构感知分块、重叠、父文档扩展和元数据来保留语义。
def structure_aware_chunks(doc, max_tokens=400, overlap=50):
sections = split_by_headings(doc) # respect document structure
chunks = []
for sec in sections:
for c in sliding_window(sec.text, max_tokens, overlap):
chunks.append(Chunk(c, meta={'section': sec.title}))
return chunks仅语义检索的缺口
纯稠密检索无法满足精确匹配需求:标识符、错误代码、罕见专有名词和接口名称都属于这种情况。而这些恰恰是用户期望获得字面精确结果的地方。混合检索融合稠密(语义)信号和稀疏(BM25/关键词)信号,从而兼顾两者。
倒数排名融合是一种简单而稳健的方式,可以合并两个排序列表,而无需调节权重。
def rrf(dense_ranks, sparse_ranks, k0=60):
scores = {}
for ranks in (dense_ranks, sparse_ranks):
for rank, doc_id in enumerate(ranks):
scores[doc_id] = scores.get(doc_id, 0) + 1.0 / (k0 + rank)
return sorted(scores, key=scores.get, reverse=True)过时且无法验证的上下文
朴素 RAG 没有新鲜度或来源可追溯性的概念。它可能检索到过时文档,也没有内置方式将论断归因于来源,从而削弱可信度,并使幻觉难以检测。
高级系统会附加元数据(时间戳、来源、版本),根据这些元数据进行过滤,并要求生成器引用分块标识符,以便验证答案。
def filtered_retrieve(q, after_date):
cands = vector_store.search(embed(q), k=50)
fresh = [c for c in cands if c.meta['date'] >= after_date]
return fresh # then re-rank; generator must cite c.id没有反馈,就没有适应
朴素 RAG 会盲目检索:它无法判断检索何时失败,无法决定何时不需要检索,也无法迭代。高级模式会加入相关性检查、条件检索和多步骤(代理式)检索;当结果看起来较弱时,系统会重新表述查询。
这样,处理流程就从单次前向传递变成了带有自我评估的循环。
def adaptive_rag(q):
chunks = retrieve(q)
if relevance_score(q, chunks) < 0.4:
q2 = rewrite_query(q) # reformulate and retry
chunks = retrieve(q2)
if relevance_score(q, chunks) < 0.4:
return 'I could not find this in the sources.'
return generate(q, chunks)高级 RAG 技术栈
将这些失败模式综合起来,高级处理流程包含多个层次:带元数据的结构感知分块;具有高召回率的混合检索;用于提升精确率的交叉编码器重排序器;用于适配上下文长度并突出重点的上下文压缩;用于修复不匹配的查询重写 / HyDE;以及带引用的相关性门控。
接下来的课程将逐层构建这些能力。贯穿始终的原则是:广泛检索,然后积极过滤和精炼。
def advanced_rag(q):
cands = hybrid_retrieve(rewrite_query(q), k=50) # high recall
top = rerank(q, cands)[:8] # precision
ctx = compress(q, top) # focus + fit
return generate_with_citations(q, ctx) # verifiable先测量,再优化
在添加更多机制之前,请先诊断实际存在的失败类型。分别测量检索召回率@k(是否至少检索到了标准分块)和答案准确率(生成器是否使用了该分块)。召回率问题和精确率问题需要不同的修复方法。
请对这两个环节都进行监测;如果真正的问题在于分块或查询不匹配,就不要贸然加装重排序器。
def diagnose(eval_set):
return {
'recall@5': recall_at_k(eval_set, k=5), # retrieval health
'recall@50': recall_at_k(eval_set, k=50), # ceiling with rerank
'answer_acc': answer_accuracy(eval_set), # generation health
}快速检查
诊断一种 RAG 失败模式。
回顾
要点:
- 朴素 RAG(分块、生成嵌入、取前 k 个结果、填入上下文、生成)是一个强大的基线,但存在可预见的失败模式。
- 双编码器相似度只是粗略的相关性代理;查询与文档的表达层次不匹配会损害召回率。
- 更多上下文并不更好:随着 k 增大,干扰内容敏感性和中间内容丢失会降低答案质量。
- 分块病变、仅语义检索的缺口、内容过时以及缺乏反馈,都会限制朴素 RAG。
- 高级 RAG 会先广泛检索再进行过滤:采用混合检索、重排序、压缩、查询重写和相关性门控。在优化之前,请分别测量召回率和答案准确率。
常见问题解答
「超越朴素 RAG」课时是免费的吗?
是的 — 「超越朴素 RAG」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。
「超越朴素 RAG」这节课中我会学到什么?
基础检索的局限性。 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Prompt Engineering 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「超越朴素 RAG」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Prompt Engineering 课中编写并运行代码吗?
能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 超越朴素 RAG
- 对检索到的文本块重新排序
- 上下文压缩
- 查询改写与 HyDE