上一篇把 RAG 的 pipeline 建起來了。這篇聊怎麼讓搜尋更準、回答更好、以及怎麼知道你的 RAG 到底行不行。

先講結論

RAG 品質不好通常只有兩個原因:

  1. 搜不到對的東西(Retrieval 問題)
  2. 搜到了但 LLM 沒用好(Generation 問題)

這篇教你怎麼分辨是哪個問題,以及怎麼修。


搜尋策略:不只是 Top-K

基本款:Top-K + 相似度門檻

最簡單的做法——找最相似的 K 個 chunk,低於門檻的丟掉:

def retrieve(query, top_k=5, threshold=0.7):
    query_embedding = get_embedding(query)
    results = vector_search(query_embedding, top_k=top_k)
    return [r for r in results if r["similarity"] >= threshold]

這個 threshold 很重要。太低(0.5)會撈到一堆不相關的;太高(0.9)會漏掉有用的。我通常從 0.7 開始調。

但如果 retrieve 回來是空的呢?那 LLM 就該說「我不知道」,而不是開始瞎掰。

純向量搜尋有個盲點——使用者搜「NT$3,000」這種精確數字時,向量搜尋可能找不到,因為它看的是「語意」不是「字面」。

Hybrid Search 把向量搜尋和傳統關鍵字搜尋(BM25)混在一起:

# 概念上的做法
vector_score = cosine_similarity(query_vector, chunk_vector)
keyword_score = bm25_score(query_text, chunk_text)
 
# 加權混合(alpha 控制比例)
combined = alpha * vector_score + (1 - alpha) * keyword_score

alpha=0.5 是個好起點。如果你的使用者常搜精確詞(產品編號、金額),就把 alpha 調低一點,讓關鍵字搜尋權重高一些。

進階:Re-ranking

先粗篩 Top-20,再用一個專門的模型精排到 Top-5:

from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
 
def retrieve_with_reranking(query, initial_k=20, final_k=5):
    candidates = basic_retrieve(query, top_k=initial_k)
    pairs = [(query, doc["content"]) for doc in candidates]
    scores = reranker.predict(pairs)
    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, _ in ranked[:final_k]]

Re-ranking 比較慢(多了一次模型推論),但品質提升明顯。如果你的 RAG 已經「還行但不夠好」,加 re-ranking 通常是投資報酬率最高的改善。


Prompt 設計:教 LLM 乖乖用資料回答

搜到好的 chunk 只是一半的工作。你還要教 LLM 怎麼用這些 chunk。

基本 Template

def build_prompt(query, chunks):
    system = """你是一個根據知識庫回答問題的助手。規則:
1. 只根據以下參考資料回答
2. 如果資料中沒有答案,說「根據現有資料,我無法回答」
3. 回答時標註來源(文件名稱和頁碼)
4. 使用繁體中文"""
 
    context = "\n\n---\n\n".join([
        f"[資料 {i+1}] 來源:{c['metadata']['source']}\n{c['content']}"
        for i, c in enumerate(chunks)
    ])
 
    return [
        {"role": "system", "content": system},
        {"role": "user", "content": f"參考資料:\n{context}\n\n問題:{query}"},
    ]

幾個重點:

  • 明確說「只用參考資料」——不然 LLM 會混入自己的「知識」
  • 處理「不知道」——這是防幻覺的最後一道防線
  • 要求標註來源——RAG 的核心價值就是可追溯

搜不到東西的時候

def rag_answer(query):
    chunks = retrieve(query, threshold=0.65)
    if not chunks:
        return {
            "answer": "根據現有知識庫,我找不到相關資訊。",
            "confidence": "low",
        }
    # ... 正常流程

千萬不要在搜不到東西的時候還硬要 LLM 回答。那就等於沒有 RAG,回到了原始的幻覺模式。


評估:你不能改善你沒量測的東西

RAG 的評估分兩個維度:

Retrieval 評估(搜得準不準)

準備 50 組 QA pairs(手動標也行),測量:

  • Precision@K:搜到的 K 個結果裡,幾個是真正相關的?
  • Recall:所有相關的資料,你搜到了幾個?
  • MRR:第一個正確結果排在第幾?

如果 Precision 低 → 搜到太多垃圾 → 調高 threshold 或加 re-ranking 如果 Recall 低 → 漏掉重要資料 → 改善 chunking 或用 hybrid search

Generation 評估(答得好不好)

推薦用 RAGAS 框架:

指標低分代表怎麼改
FaithfulnessLLM 在編造加強 prompt 限制
Answer Relevancy回答離題改善 retrieval
Context Precision太多不相關 chunk調整 chunking、top_k
Context Recall漏掉重要資料hybrid search、改善 embedding

最便宜的評估方法: 自己準備 50 個問題,人工看 RAG 的回答。花半天時間,但你會馬上發現最大的問題在哪。別小看這種「低科技」方法——我每次做完都能找到至少 3-4 個明確的改善方向。


最常踩的 5 個坑

根據我自己和看過的專案,這五個坑踩的人最多:

1. 一上來就 over-engineer 簡單的 ChromaDB + Recursive Chunking + OpenAI embedding 可以處理 80% 的場景。不需要第一天就上 Milvus 集群。

2. 忽略 Chunking 花三天選 Vector DB,chunking 用預設的 fixed-size。這本末倒置了。Chunking 才是品質瓶頸。

3. 不做評估 「感覺還行」不是評估。即使只標注 50 個 QA pairs 手動測,也比瞎猜有用。

4. 沒處理「不知道」 如果 RAG 搜不到東西但還是讓 LLM 回答,你就等於沒有 RAG。一定要在 threshold 和 prompt 兩個層面都防守。

5. 沒有 source attribution RAG 最大的優勢就是可以告訴使用者「我根據哪份文件回答的」。如果你沒做這個,RAG 的信任優勢就浪費了。


從簡單開始,逐步迭代

第 1 週:
├── ChromaDB 或 pgvector
├── OpenAI embedding(small)
├── Recursive chunking(800/200)
├── 基本 Top-K retrieval
└── 簡單 prompt template

第 2-3 週(根據評估結果):
├── 加 metadata filtering
├── 試 hybrid search
├── 加 re-ranking
└── 優化 chunk size

之後(上線後):
├── 監控 + logging
├── 使用者回饋機制
└── 定期更新索引

RAG 不是做一次就好的東西。它更像是一個持續調校的系統——但好消息是,每一步的改善都是漸進式的,不需要砍掉重來。


系列回顧

「RAG 架構實務」系列到這邊告一段落:

  1. RAG 是什麼?為什麼 90% 的 AI 應用都該用它
  2. RAG 實作:從切文件到存進 Vector DB
  3. → 你在這裡:搜尋策略與品質調校

接下來可以看 AI 工作流自動化,把 RAG 整合進實際的工作流程。


RAG 系統就像養植物——種下去很快,但要長得好需要持續照顧。