上一篇把 RAG 的 pipeline 建起來了。這篇聊怎麼讓搜尋更準、回答更好、以及怎麼知道你的 RAG 到底行不行。
先講結論
RAG 品質不好通常只有兩個原因:
- 搜不到對的東西(Retrieval 問題)
- 搜到了但 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 就該說「我不知道」,而不是開始瞎掰。
進階:Hybrid Search
純向量搜尋有個盲點——使用者搜「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_scorealpha=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 框架:
| 指標 | 低分代表 | 怎麼改 |
|---|---|---|
| Faithfulness | LLM 在編造 | 加強 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 架構實務」系列到這邊告一段落:
- RAG 是什麼?為什麼 90% 的 AI 應用都該用它
- RAG 實作:從切文件到存進 Vector DB
- → 你在這裡:搜尋策略與品質調校
接下來可以看 AI 工作流自動化,把 RAG 整合進實際的工作流程。
RAG 系統就像養植物——種下去很快,但要長得好需要持續照顧。