開始自架 RAG(Retrieval Augmented Generation)系統後,遇到最難解的 bug 類型是「答非所問」。LLM 回了一個錯的答案,但你不知道是:
- 檢索有問題?還是 LLM 理解有問題?
- 是 chunk 被錯誤切分?還是 embedding 模型沒抓到重點?
- 是 top-k 設太低?還是 reranker 排序錯?
靠人工 print log、改 threshold、反覆試,一個問題 debug 兩天常見。
評估 RAG 可觀測性方案時,測試了 vectorize-io/hindsight。它把 RAG 的整個檢索鏈路「錄影」起來:每次 query 有哪些 chunk 被撈出、各自的相似度、rerank 前後順序、最終送給 LLM 的完整 prompt。Debug 從「猜測」變成「數據驅動」。
這篇寫 Hindsight 的完整用法:整合、關鍵功能、實戰 debug 案例。
一、Hindsight 是什麼?
RAG 檢索鏈路的 replay & 分析工具。
三大功能:
1. Trace recording:每次 query 的檢索步驟完整記錄
2. Replay:可 re-run 歷史 query,比較不同參數
3. Quality analysis:評估 chunk 相關性、答案準確度
支援的 RAG 框架:
- LangChain
- LlamaIndex
- 自訂 pipeline(透過 SDK 埋點)
二、為什麼 RAG 需要這種工具?
RAG debug 常見問題:
| 問題 | 傳統 debug 方式 | Hindsight |
|---|---|---|
| 答案離題 | 手動 print 所有 chunk | 一個介面看全部 |
| 相似度閾值對不對 | 改 code 重跑 | replay 比較 |
| 新 embedding 模型是否更好 | 寫 A/B 腳本 | 內建 compare |
| chunk 切分策略評估 | 建兩套 index 對比 | 一個 UI 切換 |
三、前置需求
| 項目 | 版本 | 說明 |
|---|---|---|
| Python | 3.10+ | SDK 用 |
| RAG pipeline | 自建或 LangChain | |
| Postgres / SQLite | — | trace 儲存 |
| 瀏覽器 | 現代 | UI 檢視 |
四、安裝
4.1 安裝 SDK
pip install hindsight-rag
4.2 啟動 UI server
hindsight server --port 4800
打開 http://localhost:4800 看到 Hindsight dashboard。
五、整合到既有 RAG pipeline
5.1 LangChain
from hindsight import HindsightTracker
from langchain.chains import RetrievalQA
tracker = HindsightTracker(project="myapp-rag")
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever,
callbacks=[tracker.langchain_callback()],
)
answer = qa_chain.run("How do I reset password?")
5.2 LlamaIndex
from hindsight import HindsightTracker
from llama_index.core.callbacks import CallbackManager
tracker = HindsightTracker(project="myapp-rag")
callback_manager = CallbackManager([tracker.llama_index_callback()])
query_engine = index.as_query_engine(callback_manager=callback_manager)
answer = query_engine.query("How do I reset password?")
5.3 自訂 pipeline
from hindsight import HindsightTracker
tracker = HindsightTracker(project="myapp-rag")
with tracker.trace("user-query-001") as trace:
trace.log_step("retrieval", {
"query": query,
"top_k": 5,
"chunks": [{"id": c.id, "text": c.text, "score": c.score} for c in chunks]
})
trace.log_step("rerank", {
"reranker": "cohere-rerank",
"reordered": [c.id for c in reranked]
})
trace.log_step("llm", {
"model": "gpt-4o",
"prompt": full_prompt,
"answer": answer
})
六、UI 主要畫面
6.1 Trace List
每筆 query 一條記錄:
- Query text
- Retrieved chunks count
- Final answer
- Latency
- Cost
6.2 Trace Detail
點進去看完整鏈路:
Query: "How do I reset password?"
│
├─ Retrieval (vector search)
│ top-5 chunks, highest similarity: 0.89
│ chunks: [#1234, #2156, #3098, #4521, #5678]
│ ↓ (each chunk shows content + score)
│
├─ Rerank (cohere)
│ reordered to: [#2156, #1234, #4521, #3098, #5678]
│ ↓
│
├─ LLM (gpt-4o)
│ prompt: [full text with context]
│ tokens in/out: 2340 / 180
│ latency: 4.2s
│ ↓
│
└─ Answer: "To reset your password, click..."
每個 chunk 都可以展開看完整內容,點 "contribution" 看它對最終答案的影響度。
6.3 Replay
選一條歷史 trace,改參數(如 top_k 從 5 改 10),re-run。Hindsight 會 side-by-side 比較兩次結果。
七、實戰:debug 一個錯答案
7.1 問題
用戶問「怎麼取消訂閱」,系統回答「請前往 Settings → Plans → 續約管理」。方向錯了——我們根本沒有「續約管理」這個 UI。
7.2 用 Hindsight 找原因
開 trace detail 看,發現:
Retrieved chunks:
#3521 (score 0.87): "...訂閱續約管理頁面..." ← ??? 這不是我們的文件
#1209 (score 0.72): "...Settings 頁面的各個區塊..."
#2456 (score 0.68): "...取消訂閱步驟..." ← 這才對
7.3 發現根因
第一個 chunk #3521 來自另一個產品的舊文件,誤匯入了 knowledge base。因為 embedding 相似度高,被排到第一,LLM 以那個為主。
7.4 修正
- 從 index 移除
#3521相關 chunks - 加入 metadata filter:只檢索當前產品的文件
- 用 Hindsight replay 同樣的 query,確認新版 chunk 正確
整個 debug 時間 < 30 分鐘。沒有 Hindsight 這類工具,這題至少半天。
八、Quality metrics:自動化評估
Hindsight 提供幾個自動化指標:
8.1 Faithfulness
答案是否 grounded 在 retrieved chunks 中?還是 LLM 胡謅的?
tracker.evaluate_faithfulness(trace_id)
# {"score": 0.85, "unsupported_claims": ["...", "..."]}
8.2 Relevance
Retrieved chunks 對 query 是否真的相關?
tracker.evaluate_relevance(trace_id)
# {"score": 0.72, "low_relevance_chunks": ["#3521"]}
8.3 Context Precision
Top chunks 的相關性是否高?
tracker.evaluate_context_precision(trace_id, top_k=3)
# {"score": 0.67}
這些 metrics 可以 CI 自動跑,低於 threshold 就 block 部署。
九、A/B 測試:比較兩套設定
# 設定 A: top-k=5, no rerank
trace_a = tracker.replay(original_trace_id, params={"top_k": 5, "rerank": False})
# 設定 B: top-k=10, with rerank
trace_b = tracker.replay(original_trace_id, params={"top_k": 10, "rerank": True})
# 比較
tracker.compare(trace_a, trace_b)
# 輸出兩者的 faithfulness、relevance、latency、cost 對比
這對「新 embedding 模型該不該上線」這類決策特別有用。
十、常見坑點
10.1 Trace 太多儲存爆掉
症狀:跑一週,DB 佔 10 GB。
解法:
tracker = HindsightTracker(project="myapp", retention_days=30)
# 自動清理 30 天前的 trace
10.2 Sensitive data 被記下來
症狀:user query 含個資被完整記錄。
解法:
tracker = HindsightTracker(
project="myapp",
redactors=[
"email", "phone", "credit_card",
r"\b\d{10}\b", # custom regex
],
)
10.3 高流量 service 記錄太慢
症狀:每個 query 多 50ms 延遲。
解法:啟用 async mode,async 寫 DB:
tracker = HindsightTracker(project="myapp", async_writes=True)
10.4 Evaluation metrics 不準
症狀:faithfulness 分數跟主觀感受對不上。
解法:用 LLM-as-a-judge 客製化 evaluation prompt:
tracker.set_evaluation_prompt(
"faithfulness",
custom_prompt="你是嚴格的事實查核員。以下是 answer 跟 chunks..."
)
十一、RAG 開發流程建議
導入 Hindsight 後的典型流程:
- 開發:寫 RAG pipeline,埋入 Hindsight tracking
- 內部測試:用真實 query 跑,檢查 trace
- Shadow mode:上線但不回答用戶,只記 trace
- Metric-gated rollout:Faithfulness / Relevance > 0.8 才打開
- 生產監控:每日看 trace 分佈,抓 outliers
- 持續迭代:用 replay 比較新舊設定
十二、結語
RAG 生態從 2023 年 POC 階段進入 2025 年的 production 階段,可觀測性不足是最大瓶頸。過去靠直覺調參的時代已經過去,Hindsight 這類工具把 RAG 系統變成「可 debug、可 A/B、可 evaluation」的正規工程系統。
建議做 RAG 產品的團隊:
- 從第一天就接入 trace 工具(不要等 bug 才接)
- 把 faithfulness / relevance 當成 SLI
- 把 eval suite 當 CI gate
下一篇會寫「RAG 系統的 CI/CD 最佳實踐」——結合 Hindsight + evaluation dataset + automated regression testing。


發佈留言