Skip to content

CRAG:檢索失敗時,自動放寬條件重試

2026年3月12日 1 分鐘
TL;DR 過濾條件太嚴格導致零結果?CRAG 自動放寬過濾條件重試,比讓 LLM 用通用知識瞎猜好多了。
目錄
  1. 放寬策略
  2. 實作細節
  3. 與 Agentic RAG 的差異
  4. Adaptive Retrieval 光譜:從 CRAG 到 Agentic RAG 之間
  5. 為什麼是放寬而不是擴大範圍
  6. 整體來說
  7. 更新紀錄
  8. 參考資料

🌏 English version

RAG 系統的一個靜默失敗模式:過濾條件過嚴,沒有候選文件通過,但 pipeline 繼續跑,LLM 只能用通用知識回答

使用者問「龍洞有沒有 5.14 的路線」,系統正確提取了 crag_id = longtunggrade_numeric ≥ 140,但龍洞根本沒有這個難度的路線,搜尋零結果。如果系統直接把空 context 送給 LLM,有兩種糟糕的結果:

  1. LLM 誠實說「沒有相關資料」→ 正確但體驗差(其實應該說龍洞沒有 5.14)
  2. LLM 用通用知識幻覺出一個回答 → 不準確

CRAG(Corrective RAG)的解法:檢測到零結果時,自動放寬過濾條件重試搜尋

先說清楚一件事,免得誤導:這裡做的是 CRAG 的精神,不是論文的原始演算法。Yan 等人 2024 年那篇 CRAG 用一個輕量的 retrieval evaluator 給檢索結果打信心分數,依分數落在 Correct / Incorrect / Ambiguous 哪一區觸發不同動作;判為 Incorrect 時改走大規模網頁搜尋,另外再用 decompose-then-recompose 把文件拆成知識片段、丟掉不相關的部分後重組。本文的實作沒有 evaluator、也沒有 web search,只保留「檢索失敗時要有修正動作,而不是把空 context 丟給 LLM」這個核心想法,把修正動作換成對結構化 filter 做放寬。後面〈為什麼是放寬而不是擴大範圍〉會說明為什麼在這個場景捨棄 web search。

放寬策略

過濾條件有輕重之分。位置過濾(岩場、地區)通常是使用者的核心需求,不能隨便移除;但難度過濾、類型過濾有時是副條件,放寬它們更合理。

放寬的順序:

原始過濾:{ crag_id: 'longtung', grade_numeric: { gte: 140 }, route_type: 'sport' }
    ↓ 零結果
Step 1:移除 grade_numeric 過濾
    { crag_id: 'longtung', route_type: 'sport' }
    ↓ 仍然零結果
Step 2:移除 route_type 過濾
    { crag_id: 'longtung' }
    ↓ 有結果 → 繼續

位置過濾(crag_idarea_idregion)保留到最後,因為使用者問「龍洞」就是要龍洞的資訊,不能因為零結果就去找其他岩場的資料。

實作細節

async function hybridSearchWithCRAG(ctx: PipelineContext): Promise<SearchResult[]> {
  let filter = buildFilter(ctx);
  let results = await hybridSearch(ctx.queryVector, filter);

  // 零結果且有可放寬的條件
  if (results.length === 0 && ctx.cragRetryCount < 1) {
    ctx.cragRetryCount++;

    // 移除難度過濾,保留位置
    const relaxedFilter = removeGradeFilter(filter);
    results = await hybridSearch(ctx.queryVector, relaxedFilter);

    // 記錄到 trace
    ctx.trace.retrieval.crag_triggered = true;
    ctx.trace.retrieval.relaxed_filter = relaxedFilter;
  }

  return results;
}

cragRetryCount < 1 確保最多重試一次——所以上面那張放寬順序圖是概念上的優先序,這份實作實際只跑到第一階(移除難度過濾)。要做到多階放寬,就把上限提高、按順序逐階套用;但每放寬一階,回來的文件離原本的問題就更遠一點。不設上限的話,理論上可以一直放寬到無過濾,帶回完全不相關的結果,反而更糟。

與 Agentic RAG 的差異

CRAG 是規則型的修正,在 pipeline 內自動執行,不需要 LLM 決策。Agentic RAG 是 LLM 主動評估是否要重新檢索並改寫查詢。兩者的定位不同:

CRAGAgentic RAG
觸發條件零結果LLM 評估 context 不足
決策主體規則LLM
適用場景過濾太嚴需要多跳推理
延遲成本低(多一次搜尋)高(多次 LLM 呼叫)

CRAG 解決的是「根本沒東西」的問題,Agentic RAG 解決的是「有東西但不夠好」的問題。

Adaptive Retrieval 光譜:從 CRAG 到 Agentic RAG 之間

上面的比較表把 CRAG 和 Agentic RAG 擺成兩極,但實際上兩者之間存在一系列 adaptive retrieval 策略,各自在「自主性」和「成本」之間取不同的平衡點:

方法誰決定要不要檢索觸發時機額外成本
CRAG規則(零結果)搜尋後一次搜尋
FLARELLM 信心分數生成途中按低信心 token 數而定
Self-RAG特殊 reflection tokens生成途中推論時微增(reflection token 開銷)
Adaptive-RAG分類器查詢進來時一次分類 + 對應路徑
Agentic RAGLLM agent loop搜尋後 / 生成後多輪 LLM 呼叫

FLARE(Forward-Looking Active REtrieval)在生成回答的過程中,偵測到下一句的預測信心偏低時,主動用低信心的 token 組成新查詢去檢索,再把結果插回生成流程。觸發時機比 CRAG 更細緻——不是等整個搜尋回來零結果,而是生成到一半就即時補檢索。

Self-RAG 更進一步:在訓練階段就教模型產生四種 reflection token([Retrieve][IsRel][IsSup][IsUse]),讓模型在推論時自己判斷「現在需不需要去查資料」、「查回來的東西有沒有用」。不需要外部的 agent loop 或分類器,retrieval 決策內化到模型本身。詳見 Self-RAG:用 Reflection Token 讓模型自己決定要不要檢索

Adaptive-RAG 的做法是在查詢進來的第一步就用一個輕量分類器判斷複雜度,把查詢路由到三條路徑:不需要檢索(LLM 直接回答)、單次檢索(標準 RAG)、多跳檢索(iterative retrieval)。比起 CRAG 的被動修正或 Agentic RAG 的全程代理,Adaptive-RAG 的分類器成本最低,但需要訓練資料來校準路由。

這些方法不互斥。CRAG 適合當最底層的安全網(零結果修正),Adaptive-RAG 或 FLARE 處理「有結果但品質未知」的灰色地帶,Agentic RAG 留給真正需要多步推理的複雜查詢。一個成熟的系統可能同時疊加多層。

為什麼是放寬而不是擴大範圍

另一個思路是「沒結果就去外部知識庫搜尋(如 Wikipedia)」。CRAG 原始論文也有這個設計(Web Search fallback)。但在攀岩社群的場景,使用者問的問題通常是關於特定岩場和路線,外部搜尋引入的通用攀岩知識反而可能誤導,不如誠實說「這個岩場沒有這個難度的路線」,輔以相近的資訊。

放寬過濾比引入外部知識在語義上更一致,結果也更可控。

整體來說

CRAG 是 RAG pipeline 的安全網,成本低(多一次搜尋),卻能防止系統在邊緣情況下靜默失敗。搭配 LLM-as-Judge 的 Groundedness 評分,即使放寬後取到的文件相關性較低,Judge 也會降低 groundedness 分數,讓系統加上適當的免責聲明。防禦是多層的,CRAG 是第一層。


更新紀錄

  • 2026-09-03:補充 Adaptive Retrieval 光譜段落(Self-RAG、Adaptive-RAG、FLARE),填補 CRAG↔Agentic RAG 之間的策略空白;新增五篇參考文獻
  • 2026-08-19:對照官方文件逐篇查證翻新,移除易腐內容,並收進「RAG 技法大全」系列

參考資料