DAY 10 / PHASE 1 · ENGINEERING

RAG 实战工程

Chunking · Hybrid Search · Reranker · Query Transformation

2026-05-26 · BigCat

RAG 不是「embedding + 余弦相似度」那么简单。Demo 跑得通,生产里 recall 卡在 60%、reranker 没钱、HyDE 引入幻觉——这一期把 4 个最高 ROI 的工程层一次说穿。

// WHY THIS MATTERS

2024 年开始,大家发现一个尴尬现象:长 context(200K-2M tokens)出来了,但 RAG 没死,反而成了生产标配——因为 long-context 贵、慢、且中部信息会被忽略(lost-in-the-middle)。但很多人手里的 RAG 仍是 2023 年的水平:固定 512 token chunk、纯 dense embedding、top-k=5、直接拼 prompt。这个 stack 在 demo 里 recall 80%,在生产 corpus 上 50% 出头。差距全在 4 个工程层:chunking 策略 → hybrid retrieval → reranker → query transformation。每层都有 30-40% 的 recall 提升空间,组合起来差出整整一个 generation。Anthropic 2024 年 Contextual Retrieval 报告里,把这套组合做满后 retrieval failure rate 从 5.7% 降到 0.4%——13 倍提升。这一期不讲怎么搭 demo,讲 demo → 生产之间那段没人讲、但决定 RAG 是否值得用的工程。

生产级 RAG 4 层工程栈(vs naive baseline) USER QUERY │ ▼ ┌─────────────────────────────────────────────────────────┐ │ ① Query Transformation (HyDE / multi-query / step-back) │ │ 1 query → 3-5 query variants │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ ② Hybrid Retrieval │ │ ┌──BM25 (top 50)──┐ ┌──Dense (top 50)──┐ │ │ └────────┬────────┘ └────────┬─────────┘ │ │ └──── RRF / fusion ────┘ │ │ │ │ │ top 50 candidates │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ ③ Cross-encoder Reranker (top 50 → top 5-10) │ │ Cohere / BGE / GPT-4-mini-as-judge │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ ④ Context Assembly (chunking-aware) │ │ contextualized chunks + window context + dedup │ └─────────────────────────────────────────────────────────┘ │ ▼ LLM GENERATE naive baseline 缺 ①③④,只有 ② 的 dense 半边 → 这是 80% 团队的现状
// 01

Chunking:固定 512 是初学者陷阱,结构化才是生产配置

论断:把文档每 512 token 切一刀,是 RAG 入门教程的「Hello World」——和生产能用之间隔了 5 个等级。语义边界 + 父子结构 + contextual prefix 三件套,比单纯调 chunk size 提升 recall 25-40%。

背景与原理

固定大小 chunk 的根本问题:语义边界与字符边界不重合。一个 512 token 的切口可能把「为什么」放在 chunk A、「因为...」放在 chunk B——embedding 各自看不到对方,retrieval 永远召不全。这不是 chunk size 调参能解决的,是 chunking 策略问题。

四代 chunking 演进路径:(1)固定大小——naive,已知失败;(2)Recursive character splitter(LangChain 经典)——按段落/句子/词层级回退,是入门到能用的最低门槛;(3)Structure-aware——按 markdown header / 代码 AST / PDF 章节切,保留语义单元;(4)Parent-child / hierarchical——小 chunk 用来 retrieve、大 chunk(父段落或整章)用来给 LLM,避免「召回精确但上下文残缺」。

2024 年 Anthropic 提出的 Contextual Retrieval 是第五代:每个 chunk 在 embed 前先让 Claude 加一段「这 chunk 在原文中的角色」的 contextual prefix。同样的 RAG pipeline,单加这一步 failure rate 降 35%。原理:embedding 模型对孤立 chunk 编码会丢失「这是关于什么的」信号——人工加上 50-100 token 的上下文标签,embedding 立即对齐到正确语义子空间。配合 prompt caching,给 1M token 的 corpus 加 contextual prefix 的成本只有 $1.02——这是 2024 年 RAG 工程最高 ROI 的单一改动。

Chunking 策略对比(同一 100K-token 技术文档) 策略 recall@10 边界 bug 实现复杂度 ────────────────────────────────────────────────────────────── 固定 512 token 52% ~30% ★ Recursive (段落→句→词) 68% ~10% ★★ Structure-aware (header/AST) 74% ~5% ★★★ Parent-child (small→big) 78% ~3% ★★★ + Contextual prefix (Anthropic) 86% ~2% ★★★★ → 升一档 recall 6-10 个点;最后一档是 prompt caching 后才工程可行的

实战示例

Structure-aware + parent-child + contextual prefix 三件套(最小可工作版):

from langchain.text_splitter import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
import anthropic

# —— 1. Structure-aware: 先按 markdown header 切大块(=父 chunk) ——
header_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[("#", "h1"), ("##", "h2"), ("###", "h3")]
)
parent_chunks = header_splitter.split_text(doc)  # ~1500 token / 块

# —— 2. Parent-child: 再把父 chunk 切成 retrieval 用的小 chunk ——
child_splitter = RecursiveCharacterTextSplitter(
    chunk_size=400, chunk_overlap=50,
    separators=["\n\n", "\n", ". ", " "]
)

# —— 3. Contextual prefix: 用 Claude 给每个 child 加上下文标签 ——
# 整篇文档放 cache_control=ephemeral,1M token 只付 1 次输入费
client = anthropic.Anthropic()

def contextualize(full_doc, chunk):
    msg = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=120,
        messages=[{"role":"user", "content":[
          {"type":"text",
           "text": f"<document>{full_doc}</document>",
           "cache_control":{"type":"ephemeral"}},  # ← 关键
          {"type":"text",
           "text": f"<chunk>{chunk}</chunk>\nGive a 1-2 sentence "
                   "context placing this chunk in the doc. Just the context."}
        ]}
    )
    return msg.content[0].text

# 索引时:embed(contextual_prefix + child_chunk)
# 生成时:retrieve child → 取回对应 parent 给 LLM 做更全上下文
indexed = []
for parent in parent_chunks:
    for child in child_splitter.split_text(parent.page_content):
        prefix = contextualize(doc, child)
        indexed.append({
          "embed_text": prefix + "\n" + child,  # 用来 embed/BM25
          "parent_text": parent.page_content,    # 给 LLM 用
          "metadata": parent.metadata
        })
失败模式:(1)chunk 大小 = 主流嵌入模型的 context window 上限(768/8192)——撑满反而稀释 embedding 信号;最佳 256-512 token;(2)contextual prefix 不走 prompt cache——成本暴涨 30 倍,工程不可行;必须 ephemeral cache;(3)code/PDF 用纯文本 chunking——代码的语义单元是函数/类、PDF 是页/段,用 AST/Unstructured 库别用 text splitter;(4)parent chunk 太大塞爆 context——parent 也要有上限(2-4K token),不能一篇文章整篇丢进去。
进阶资源 · Anthropic Introducing Contextual Retrieval, anthropic.com/news/contextual-retrieval · LangChain Recursive splitter & Parent Document Retriever, python.langchain.com/.../parent_document_retriever
// 02

Hybrid Search:BM25 不是「老古董」,是 dense 永远追不上的另一半

论断:纯 dense embedding 在 paraphrase 和语义模糊查询上赢,但在专有名词、错别字、ID、罕见技术词上稳输 BM25。生产 RAG 必须 hybrid——RRF(Reciprocal Rank Fusion)合并 BM25 + dense 比单边强 15-25%,且实现只需 10 行代码。

背景与原理

2020 年以后大家都说「dense embedding 颠覆了 BM25」——这是 academic benchmark 的错觉。真实场景里:用户问 "GPT-4o vs Claude 4.7",dense embedding 会召回所有「大模型对比」的文章;BM25 直接召回带这两个 token 的文章。"connection refused on port 5432"、"erro_user_42"、"RFC 9457"——这些都是 BM25 完胜 dense 的场景。原因:embedding 把 token 投影到稠密语义空间,稀有词的精确匹配信号被有损压缩;BM25 保留了 token-level 精确度。

Anthropic Contextual Retrieval 报告的 ablation 数据极其干净:在他们的 corpus 上单独 dense retrieval 失败率 5.7%,加 BM25(Contextual BM25 + Contextual Embeddings)降到 2.9%,近乎减半。Microsoft 的 Azure AI Search、Pinecone、Weaviate、Elasticsearch 8.x 全部内置 hybrid 不是巧合——这是 2024 年生产 RAG 的事实标准

合并算法首选 RRF(Cormack et al. SIGIR 2009):不需要 normalize 两边的分数,只用 rank。score(d) = Σ 1/(k + rank_i(d)),k 通常取 60。比加权和稳健得多——加权和需要你不停调权重,RRF 对两边分数尺度免疫。Elastic 默认 k=60,Anthropic 报告用的也是 60。

Dense vs BM25:什么时候谁赢 Query 类型 Dense BM25 Hybrid(RRF) ───────────────────────────────────────────────────────────── 自然语言问句 ★★★ ★★ ★★★ Paraphrase / 同义替换 ★★★ ★ ★★★ 跨语言(中→英) ★★★ ☆ ★★★ 专有名词 (品牌/API/SKU) ★ ★★★ ★★★ ID / Error code / 文件名 ☆ ★★★ ★★★ 罕见技术术语 (RFC / CVE) ★★ ★★★ ★★★ 含错别字的查询 ★★ ☆ ★★ 单字段精确匹配 (邮箱/版本号) ☆ ★★★ ★★★ → 任何真实生产 corpus 都同时有这些 query 类型,所以必须 hybrid

实战示例

10 行 RRF 合并 dense + BM25(用 bm25s 库 + 任意 embedding store):

import bm25s
from collections import defaultdict

# —— 索引阶段 ——
corpus_texts = [item["embed_text"] for item in indexed]
bm25 = bm25s.BM25()
bm25.index(bm25s.tokenize(corpus_texts, stopwords="en"))
# dense_index = your_vector_store.upsert(...)

# —— 查询阶段 ——
def hybrid_retrieve(query, top_k=50, k=60):
    # 两边各取 top_k
    bm25_results = bm25.retrieve(bm25s.tokenize([query]), k=top_k)
    dense_results = dense_index.query(embed(query), top_k=top_k)

    # —— RRF 合并 ——
    scores = defaultdict(float)
    for rank, doc_id in enumerate(bm25_results.ids[0]):
        scores[doc_id] += 1.0 / (k + rank + 1)
    for rank, hit in enumerate(dense_results.matches):
        scores[hit.id] += 1.0 / (k + rank + 1)

    fused = sorted(scores.items(), key=lambda x: -x[1])
    return [doc_id for doc_id, _ in fused[:top_k]]

# 关键:BM25 和 dense 各召回 50 条,RRF 合并保留 top 50
# 不要在这里截到 top 5——把过滤工作留给下一步的 reranker
失败模式:(1)两边分数线性加权——0.5·dense + 0.5·bm25看起来合理,但 dense 是 [0,1] cosine、BM25 是 [0, +∞],权重需要 corpus-specific 调;RRF 用 rank 免疫这问题;(2)BM25 不做 stemming / 中文分词——召回率立刻塌一半;中文必须 jieba/Lucene-CJK,英文用 Porter;(3)只取 hybrid top 5 给 LLM——浪费 hybrid 的召回优势;hybrid 的输出应当是 reranker 的输入(top 50),不是终点;(4)以为加 hybrid 就够了——hybrid 解决 retrieval recall,但 top-1 精度还是要 reranker。
进阶资源 · Cormack et al. Reciprocal Rank Fusion (SIGIR 2009), uwaterloo · RRF paper · Lù BM25S: Orders of magnitude faster lexical search, arxiv.org/abs/2407.03618
// 03

Reranker:用 cross-encoder 把 top-50 重排 top-5,是 RAG 性价比最高的一层

论断:embedding 模型是 bi-encoder,问与答各自独立编码——便宜快但精度有上限。Reranker 是 cross-encoder,把 query 和 candidate 拼到一起过一次模型——慢 100 倍但精度高 20-40%。在 RAG 里只用 reranker 给 top 50 → top 5 重排,cost 增量小、recall@5 提升巨大,是少见的工程帕累托甜点。

背景与原理

Bi-encoder vs cross-encoder 是两类不同模型的本质差异。Bi-encoder(OpenAI text-embedding-3、Cohere embed、BGE):query 和 doc 各自变成 vector,离线索引、毫秒级查询,但模型在编码时不知道对方存在,错过细微相关性。Cross-encoder(Cohere Rerank、BGE-reranker-v2-m3、ms-marco-MiniLM):query 和 doc 拼成 [CLS] query [SEP] doc,整条过一次 BERT-like 模型直接出相关性分——精度逼近 LLM-as-judge 但只用 80M-300M 参数。

Cross-encoder 的缺点是计算量与 candidate 数成线性——把 BM25/dense 召回的 top 50-100 重排是可承受的(50ms-300ms),但如果用它做 first-stage retrieval(10万文档),那是上百秒级别。所以正确架构永远是 two-stage:bi-encoder/BM25 召回大候选集(top 50-200)→ cross-encoder reranker 精排到 top 5-10。

真实 ROI 数据。BEIR benchmark:dense baseline NDCG@10 = 0.42,加 ms-marco-MiniLM-L-6-v2 reranker = 0.54(+28%)。Anthropic Contextual Retrieval 报告:在已有 hybrid + contextual 之上再加 Cohere Rerank,failure rate 从 2.9% 进一步降到 1.9%(再砍 35%)。Cohere 自家 BEIR 复现里 rerank-v3 加到 hybrid 上 NDCG +12-18 个点。代价:每查询 +50-200ms、每千次查询约 $1(Cohere 价位)或自托管 BGE 0 现金成本。这是 RAG 里 ROI 最高的单一组件,没有之一。

Bi-encoder vs Cross-encoder:架构 + 性能 trade-off ┌──────────── Bi-encoder(embedding)────────────┐ │ query ─→ [encoder] ─→ vec_q │ │ doc ─→ [encoder] ─→ vec_d (离线,可索引) │ │ score = cos(vec_q, vec_d) │ │ 速度:sub-ms / 查询 精度:baseline │ └────────────────────────────────────────────────┘ ┌─────────── Cross-encoder(reranker)───────────┐ │ [CLS] query [SEP] doc ─→ [encoder] ─→ score │ │ query × doc 必须一起过,无法离线索引 │ │ 速度:50-200ms / pair 精度:+15~30 NDCG │ └────────────────────────────────────────────────┘ Two-stage 是唯一可行架构: Stage 1 bi-encoder/BM25 → top 50-200 (高 recall, 低 precision) Stage 2 cross-encoder → top 5-10 (高 precision) Cost: stage 2 只跑 50-200 次 → 总延迟 < 500ms

实战示例

用 BGE-reranker(开源、自托管)+ fallback 到 Cohere(managed)的稳定生产配置:

from FlagEmbedding import FlagReranker
import cohere

# —— 默认:自托管 BGE-reranker-v2-m3(多语种、3 亿参数、可在 8GB 卡上跑)——
#   开源、零 cash cost、p95 ~80ms(A10)
reranker_local = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=True)

# —— Fallback:Cohere Rerank-v3(managed、稳定、不需要 GPU)——
co = cohere.Client(API_KEY)

def rerank(query, candidates, top_n=8):
    # candidates 是 hybrid 召回的 top 50(list of {id, text})
    try:
        pairs = [(query, c["text"]) for c in candidates]
        scores = reranker_local.compute_score(pairs, normalize=True)
    except Exception:
        result = co.rerank(
            model="rerank-v3.5",
            query=query,
            documents=[c["text"] for c in candidates],
            top_n=top_n
        )
        return [candidates[r.index] for r in result.results]

    ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])
    return [c for c, _ in ranked[:top_n]]

# —— 关键:reranker 之后的 top_n 才是真正给 LLM 的 chunks ——
# 不要给 LLM 50 个 chunk 让它「自己挑」——浪费 token + lost-in-the-middle
top_chunks = rerank(query, hybrid_candidates, top_n=8)
失败模式:(1)跳过 stage 1,直接用 reranker 排全 corpus——计算量爆炸;reranker 是精排不是召回;(2)top_n 设太小(=1)——丢失多样性,遇到「问题需 2-3 个文档拼」就崩;top_n 8-10 是稳态;(3)reranker 选错语言——bge-reranker-base 只支持英文,中文必须 bge-reranker-v2-m3;(4)reranker 没做 batch——本来 80ms/pair,batch=16 后 5ms/pair;不 batch 是工程事故;(5)用 LLM-as-judge 当 reranker——能 work 但贵 50-100 倍,除非已经在用同一个 LLM 否则不划算。
进阶资源 · Reimers & Gurevych Sentence-BERT (bi-encoder vs cross-encoder), arxiv.org/abs/1908.10084 · BAAI bge-reranker-v2-m3 model card, huggingface.co/BAAI/bge-reranker-v2-m3 · Cohere Rerank docs, docs.cohere.com/docs/rerank-overview
// 04

Query Transformation:用户的 query 才是 RAG 最弱的一环

论断:RAG 工程师把 90% 时间花在 chunking / retrieval / reranker 上,但真正限制 recall 的是 query 本身——简短、含混、缺背景。HyDE、multi-query、step-back 三种 transformation,单独任一个都能提 10-15% recall,组合用 25-35%。

背景与原理

核心矛盾:corpus 里的 chunks 是「答案样态」(陈述句、长上下文),用户 query 是「问题样态」(短、含代词、上下文缺失)。embedding 空间里两者距离天然就远。这就是为什么 demo 里能用「正确措辞」查到答案、用户实际查的话总差一点——不是模型不行,是 query 没在答案的语义邻域里

三种主流 query transformation:

三个 transformation 不互斥。Anthropic 2024 工程经验:HyDE + multi-query 在语义模糊查询上叠加效果最好;step-back 对需要先理解原则再代入的问答(数学、法律、医学)增益最大。但成本也叠加——每查询多 1-2 次 LLM 调用,延迟 +500ms-1s。所以策略上 query transformation 应当按查询类型路由:短而具体的 ID 类查询不需要;自然语言长查询全开。

实战示例

HyDE + multi-query 组合,按查询类型路由:

import anthropic, re
client = anthropic.Anthropic()

# —— 1. HyDE: 生成假答案做 embedding ——
def hyde(query):
    msg = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=200,
        messages=[{"role":"user", "content":
          f"Write a concise, factual 3-4 sentence answer to this question. "
          f"If you don't know, write what such an answer would likely contain.\n\n{query}"}]
    )
    return msg.content[0].text  # 假答案,用它的 embedding 而非 query 的

# —— 2. Multi-query: 改写成 3 个变体 ——
def multi_query(query):
    msg = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=300,
        messages=[{"role":"user", "content":
          f"Rewrite the following query as 3 distinct search queries. "
          f"Each on its own line, no numbering, no quotes.\n\nQuery: {query}"}]
    )
    return [q.strip() for q in msg.content[0].text.strip().split("\n") if q.strip()]

# —— 3. 路由:根据 query 形态选 transformation ——
def route_and_retrieve(query):
    # 短且含 ID/version/error code → 不做 transformation,直接 BM25 主导
    if len(query.split()) < 5 or re.search(r'\b[A-Z]{2,}[-_]?\d+\b|v\d+\.\d+', query):
        return hybrid_retrieve(query, top_k=50)

    # 自然语言长查询 → HyDE + multi-query 全开
    queries = [query] + multi_query(query)
    candidates = defaultdict(float)
    for i, q in enumerate(queries):
        # 用 HyDE 假答案做 dense;BM25 还是用原 query(关键词不应该被改写)
        hyp = hyde(q)
        for rank, doc_id in enumerate(hybrid_retrieve_split(
              dense_query=hyp, bm25_query=q, top_k=50)):
            candidates[doc_id] += 1.0 / (60 + rank + 1)  # RRF
    return sorted(candidates, key=candidates.get, reverse=True)[:50]
失败模式:(1)HyDE 假答案被当成事实直接给 LLM——HyDE 输出只用于 embedding,绝不能进 final prompt;(2)multi-query 改写发散——LLM 改成 5 个跑题变体,retrieve 全是噪音;要在 prompt 里钉死「保持原意图,仅改 phrasing」;(3)所有 query 一律全开——ID 类查询用 HyDE 反而伤 BM25 召回;必须路由;(4)忽视 query transformation 的延迟——HyDE +1s、multi-query +1s、reranker +200ms 累加超过用户耐心阈值;要 streaming 同步显示「正在分析问题…」给体感时间。
进阶资源 · Gao et al. Precise Zero-Shot Dense Retrieval without Relevance Labels (HyDE), arxiv.org/abs/2212.10496 · Zheng et al. Take a Step Back: Evoking Reasoning via Abstraction (ICLR 2024), arxiv.org/abs/2310.06117 · LangChain Multi Query Retriever, python.langchain.com/.../MultiQueryRetriever

// 综合实战 · 把你的 demo 级 RAG 在一个周末升到生产级

假设你已经有一个能跑的 RAG(fixed-chunk + dense + top-5 直接 prompt)。下面是从 50% recall 升到 85%+ 的路径,按 ROI 排序:

  1. 第 1 步 · 加 BM25 hybrid(2 小时,+15-20% recall):装 bm25s,索引一遍 corpus,retrieval 阶段双路召回 + RRF 合并 (k=60)。这是 ROI 最高、改动最小的一步。
  2. 第 2 步 · 加 reranker(3 小时,+15-25% recall@5):装 FlagEmbedding + BGE-reranker-v2-m3,hybrid 召回 top 50 → reranker 排到 top 8 → 给 LLM。如果没 GPU,用 Cohere Rerank API。
  3. 第 3 步 · 升级 chunking(半天,+10% recall + 减少边界 bug):换 RecursiveCharacterTextSplitter,按 markdown header 切父 chunk,再切子 chunk;存 parent_id;retrieve 子 chunk、return parent 给 LLM。
  4. 第 4 步 · Contextual prefix(半天,+8-15%):用 Claude Haiku + prompt cache 给每个 chunk 加 50-100 token 的上下文标签。1M token corpus 约 $1。
  5. 第 5 步 · Query transformation(半天,按查询类型路由):自然语言 query 走 HyDE + multi-query,ID/error code 类直跑 BM25。加路由器,不要一刀切。

完成上述 5 步,你手上的 RAG 在同一份 corpus、同一份 eval set 上 recall@5 通常从 50% 跳到 85%+。不需要换 embedding 模型、不需要 fine-tune、不需要更换 vector DB——这 5 步是 RAG 工程化的最大公约数,做完才有资格谈「我的 RAG 不够好,要不要换 GraphRAG / ColBERT / fine-tune embedding」。先把这层基本功打满。

// ENGLISH GLOSSARY

RAG (Retrieval-Augmented Generation)
检索增强生成;用外部知识库内容作为 LLM 输入的范式。
Chunking
把长文档切成可索引/检索单元的策略;生产级 RAG 的第一道关。
Recursive Splitter
按段落→句→词层级回退的文本切分,LangChain 入门标配。
Parent-Child Retrieval
检索用小 chunk、上下文用大 chunk 的层级架构;解决「精确召回但语境残缺」。
Contextual Retrieval
Anthropic 2024 提出的方法:embed 前给每 chunk 加 LLM 生成的上下文标签。
BM25
1994 年的稀疏 ranking 函数;至今在 ID/罕见词/精确匹配上击败 dense embedding。
Bi-encoder
独立编码 query 与 doc 的双塔模型;快、可离线索引、精度有限。
Cross-encoder
把 query 和 doc 拼起来一起编码的模型;用作 reranker,慢但精度高。
Reranker
对 first-stage 召回候选进行精排的模型;通常是 cross-encoder。
RRF (Reciprocal Rank Fusion)
多路检索结果合并算法,仅用 rank 不用 score,免疫不同分数尺度。
HyDE (Hypothetical Document Embeddings)
让 LLM 先生成假答案、用假答案 embedding 做检索的 query transformation。
Multi-query Retrieval
把一个 query 改写成多个变体并行检索后融合,覆盖 paraphrase 盲区。
Step-back Prompting
生成更抽象/上位的问题并检索原则性答案的方法(Google DeepMind 2023)。
NDCG / Recall@K
检索效果常用指标;NDCG 考虑排序质量,Recall@K 只看 top-K 内有没有命中。

// 深入思考

2M token long-context 时代,RAG 还有存在意义吗?还是只是过渡技术?
RAG 没死,且在可预见未来不会死,原因不是技术决定的,是经济和物理决定的。三条线:(1)成本——每次塞 2M token 给 Claude/GPT 的输入费比 top-10 retrieve 高 20-100 倍,绝大多数 query 不需要全 corpus;(2)延迟——2M token TTFT 在 5-20s 量级,RAG 是 200ms-1s;(3)lost-in-the-middle——Liu et al. 2023 至今未根治,2M context 中段信息回忆率 50% 左右。Long context 真正的角色是RAG 的下一层缓冲:reranker 后给 LLM 不再是 5 个 chunk 而是 20-50 个,long context 容忍更多 retrieval 噪音。所以 RAG + long context 是协同关系,而不是替代关系。GraphRAG / agentic retrieval 也是同一线索的演化——retrieval 不会消失,只会从 flat search 变成 structured search。
Contextual Retrieval 让 Claude 给每个 chunk 加上下文,本质上是用 LLM 推理算力换 retrieval 召回——这种「索引时贵、查询时便宜」的模式还有什么其他场景?
这是一个被低估的范式拐点:索引时算力 ≪ 查询时算力。一次索引、亿次查询,所以索引阶段塞再多 LLM 推理都摊薄。延伸场景:(1)Index-time summarization——每个 chunk 写一个 LLM 生成的「这段在讲什么」摘要,索引摘要而非原文;(2)Index-time entity/relation extraction——GraphRAG 的核心思路;(3)Index-time question generation——给每个 chunk 生成 5 个可能的问题,索引问题(query-doc 对齐到同一空间,比 HyDE 更彻底);(4)Index-time embedding fine-tune signal——LLM 生成 hard negatives 喂给 contrastive learning。共同点:用 prompt cache 把单次索引 cost 摊到 token 级,量级上从「不可行」变成「< $10/百万文档」。未来 5 年的 retrieval 系统重心会越来越向「索引时智能化」转移。
BM25 的稳定优势点(ID、错别字、罕见词)会不会被新一代 embedding 模型(比如 ColBERT v2、Matryoshka embeddings、SPLADE)消除?
部分消除,但完全消除概率小。ColBERT 这类 late-interaction 模型确实在 token-level 精度上比 dense embedding 强一档——它对每个 token 都保留 embedding 而非压缩到单 vector,所以稀有词的精确匹配信号在某种程度上被保留。SPLADE 直接在 sparse 空间里学,更像「learned BM25」。但这些方法都有自己的 trade-off:ColBERT 索引膨胀 10-30 倍、查询计算量比 dense 高 5-10 倍;SPLADE 训练成本高、长尾词依旧弱。BM25 的真正护城河不是精度,是 zero-shot、不需训练、跨语言、可解释、可调试——这些工程属性是 neural method 永远难复制的。所以 hybrid 还会是默认配置很多年,但 hybrid 的「另一半」可能从 BM25 进化到 SPLADE,从 dense 进化到 ColBERT。架构变,原则不变:永远 hybrid。
Reranker 的精度逼近 LLM-as-judge 但便宜得多——为什么我们还没看到「reranker as a service」成为类似 embedding API 的基础设施?
正在发生但尚未饱和。Cohere Rerank 是第一个把 reranker 当独立 SaaS 卖的;Voyage AI 跟进;Jina 跟进。但市场远小于 embedding API,原因有三:(1)认知门槛——很多 RAG 开发者不知道 reranker 存在;2024 年这点正在快速改善;(2)开源替代极强——BGE-reranker 自托管几乎零成本,挤压 SaaS 利润空间;embedding 这边 OpenAI/Cohere 还有品牌溢价但 reranker 没有;(3)体量限制——reranker 只跑 top-50,调用量天然比 embedding 少一个数量级。但中长期看 reranker SaaS 一定会扩张,因为多模态 reranker(query + image candidate)会变成 native multimodal RAG 的关键组件,那不是自托管能轻松搞定的。预测:2026 年 reranker 会变成主流 LLM API 厂商的标配,和 embedding endpoint 并列。
RAG 工程的高门槛技能(hybrid / rerank / HyDE / contextual)会被 framework / agent 抽象掉吗?还是会一直是「专家手艺」?
会被抽象,但抽象的位置不在 framework 而在 agent。LangChain / LlamaIndex 这类 framework 提供 building block 但不替你做决策——你仍要选 chunk 策略、定 reranker top_n、写路由逻辑。真正会革命的是 agentic retrieval:LLM 自己看到 query、自己决定要不要 transformation、自己评估 retrieval 质量、不满意自己再查。Anthropic 的 Claude 4.6+ 已经在朝这方向走(agentic tool use within RAG),未来 retrieval 会从「pipeline 工程」变成「prompt 工程 + tool 设计」——专家手艺仍在,但专家工作的层级上移:从调 reranker 参数变成设计 retrieval agent 的失败检测和回退策略。所以这一期的内容不会立刻过时,但 3 年后这些知识的使用方式会变——你不再手写 hybrid 代码,而是给 agent 写 prompt 让它在合适时机用 hybrid。底层原理不变,工程界面上移一层。

// 延伸阅读