返回指南
架構·2026年5月25日·更新於 2026年9月23日·閱讀約 14 分鐘

RAG 實作指南——使用 Claude 與 Gemini 建立可正式上線的檢索增強生成

建立真正可運作、可正式上線的 RAG 系統——不只是 100 行示範程式。涵蓋分塊策略、embedding 模型選擇、檢索排序、提示詞結構、幻覺控制,以及即使每天 10,000 次查詢也能將月成本維持在三位數的方法。

最後審核於 。

大多數 RAG 示範在正式環境中都會失敗。它們在 10 題示範簡報上運作正常,但使用者一提出新問題就崩潰。本指南介紹能夠實際維持的架構與模式:尊重語意邊界的分塊、同時捕捉模糊與字面查詢的混合檢索、防止幻覺的提示結構,以及在擴展時維持帳單平穩的成本控管。

五個重要決策

  1. 分塊策略——比模型更能影響檢索品質
  2. 嵌入模型——決定召回率上限
  3. 檢索策略——僅使用向量並不足夠
  4. 提示結構——決定模型是否產生幻覺
  5. 產生模型 + 快取——決定單位經濟效益

1. 分塊——樸實勝過聰明

不要想得太複雜。使用每個分塊 1000-1500 個字元、重疊 100-200 個字元的遞迴字元分割器。它會優先嘗試在段落邊界,其次在句子,再其次在單字處切分。在不同領域都很穩定:

chunk.py
# Recursive character splitter with overlap — the boring choice that wins
from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1200,
    chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_text(document)
# 1200 chars ≈ 300-400 tokens — fits 5+ chunks in a Sonnet context window
# with room for system prompt + question + answer

看起來更聰明但很少有幫助的方法:具備句子感知能力的模型、支援 Markdown 的分割器、搭配嵌入的滑動視窗。它們並不差——只是投入大量工作後略好一些。把這些投入用在檢索上。

2. 嵌入模型——text-embedding-3-large 是預設選擇

Kunavo 不提供嵌入服務——此步驟請直接呼叫嵌入供應商。 /v1/embeddings 是已實作的線路格式,但其後沒有啟用任何模型,因此對它的請求會失敗;GET /v1/models 始終是判斷哪些項目可呼叫的權威來源。實務上,這不會讓 RAG 管線增加任何費用,只有多一把金鑰:嵌入呼叫和產生呼叫本來就是獨立請求,因此可針對 OpenAI、Voyage 或 Cohere 執行嵌入,再以 Kunavo 產生內容。

text-embedding-3-large 在多數正式環境使用情境中,兼具多語言能力與準確度的平衡,價格依 OpenAI 自行公布的費率計算——請在其定價頁面查看,而不是在這裡查看,因為我們不轉售該服務,也不應在此引用其價格。變更分塊策略或模型本身時,請重新產生嵌入;內容更新時不必重新產生(只要加入新的分塊)。

較小嵌入適用的例外情況:純英文短文字檢索(FAQ、產品卡片)。對這些內容,text-embedding-3-small 的價格為 large 的四分之一,品質損失可忽略。對多語言或技術內容,請繼續使用 large。

3. 檢索——純向量不如混合檢索

向量相似度很擅長模糊的語意比對(「如何取消」→「訂閱取消政策」)。但它非常不擅長處理字面詞彙(「SKU-A92837」或「訂單 #4729」)。解決方法是混合檢索:平行執行 BM25 關鍵字搜尋,然後使用 reciprocal rank fusion:

retrieve.py
# Hybrid retrieval: vector similarity + BM25 keyword scoring
# Pure vector misses literal keywords (product SKUs, IDs, dates)
def retrieve(question: str, k: int = 5) -> list[dict]:
    q_embed = embed([question])[0]
    vector_hits = vector_db.similarity_search(q_embed, k=k * 2)
    keyword_hits = bm25_search(question, k=k * 2)

    # Reciprocal rank fusion — simple, robust
    scores: dict[str, float] = {}
    for rank, hit in enumerate(vector_hits):
        scores[hit["id"]] = scores.get(hit["id"], 0) + 1 / (rank + 60)
    for rank, hit in enumerate(keyword_hits):
        scores[hit["id"]] = scores.get(hit["id"], 0) + 1 / (rank + 60)

    top_ids = sorted(scores, key=scores.get, reverse=True)[:k]
    return [chunk_by_id[i] for i in top_ids]

在選擇品質尚可的嵌入模型之後,這是最大的單一品質提升。在保留的 100 題評估集上追蹤 recall@5——如果加入 BM25 後從 70% 提高到 90%,就等於移除了三分之一的「我不知道」失敗情況。

4. 提示結構——防止幻覺的三種模式

answer.py
# Final RAG prompt structure — three sections, citations enforced
SYSTEM_PROMPT = """You answer based exclusively on the supplied Context.
- Cite the [doc_id] for each factual claim.
- If the context doesn't answer the question, say "I don't have that
  information" — do not extrapolate or use general knowledge.
- Be concise. No throat-clearing."""

def answer(question: str) -> dict:
    chunks = retrieve(question, k=5)
    context = "\n\n---\n\n".join(
        f"[doc:{c['id']}] {c['text']}" for c in chunks
    )
    resp = client.chat.completions.create(
        model="claude-sonnet-4-6",
        messages=[
            {"role": "system", "content": [{
                "type": "text",
                "text": SYSTEM_PROMPT,
                "cache_control": {"type": "ephemeral"},
            }]},
            {"role": "user", "content": f"# Context\n{context}\n\n# Question\n{question}"},
        ],
        max_tokens=600,
    )
    return {
        "text": resp.choices[0].message.content,
        "sources": [c["id"] for c in chunks],
    }

系統提示中有三項不可妥協的要求:

  • 引用來源——要求模型在每項聲明附上 [doc:42]。如果引用的 ID 不存在,就表示你捕捉到了一次幻覺
  • 明確拒答——「如果內容沒有回答,請說我沒有相關資訊。」沒有這項要求時,模型會使用世界知識補完回答
  • 簡潔輸出——簡短回答與準確回答相關。最多 600 個 token 是很好的正式環境預設值

5. 產生模型 + 快取——成本層

Claude Sonnet 4.6 是預設選擇。如果成本受限,Claude Haiku 4.5 是便宜的備援選項。始終將系統提示包在 cache_control 中——系統提示每次呼叫都相同,因此快取可將這部分的成本降至輸入費率的 10%。

正式環境規模下的實際每次查詢成本(5K 內容、500 個輸出、啟用快取):

模型每次查詢成本每天 10,000 次查詢時相較於 Sonnet 的品質
claude-sonnet-4-6~$0.007~$70/天基準
claude-haiku-4-5~$0.001~$10/天~85%,便宜 4 倍

每天 10,000 次查詢 → Sonnet $70/天,Haiku $10/天。高風險使用情境(面向客戶、法律、醫療)選擇 Sonnet;內部/大量處理選擇 Haiku。

正式環境中會出現的問題(以及如何捕捉)

  • 分布漂移:訓練語料不再符合真實使用者問題。每週抽樣 100 次查詢,手動檢查 recall@5
  • 過時的嵌入:來源文件已更新,但嵌入未重新整理。每週追蹤索引大小與來源大小
  • 虛假引用:模型捏造看似真實的文件 ID。在呈現前,針對實際檢索到的 ID 清單驗證每個 [doc:N]
  • 延遲陡升:向量資料庫在超過一百萬個向量後效能暴跌。使用 HNSW 索引;如果是多租戶,請按租戶分區

4 週正式環境路線圖

  1. 第 1 週:使用 100 份文件、10 個測試問題建立原型,進行手動評估
  2. 第 2 週:擴展至完整語料庫,建立混合檢索,撰寫 100 題評估集
  3. 第 3 週:根據評估反覆調整分塊 + 系統提示,發布給內部使用者
  4. 第 4 週:監控、成本預算、搭配引用來源 UI 的公開發布

常見問題

正式環境的 RAG 系統應使用多大的分塊?

每個分塊使用 1,000–1,500 個字元,重疊 100–200 個字元;使用遞迴字元分割器,優先在段落邊界,其次在句子,再其次在單字處切分。具備句子感知能力的模型、支援 Markdown 的分割器和滑動視窗,在投入高得多的工作量後會略好一些——但這些投入用在檢索上,回報更高。

RAG 管線應使用哪個嵌入模型?

text-embedding-3-large 是多語言和技術內容的預設選擇;對於 FAQ 和產品卡片等純英文短文字檢索,text-embedding-3-small 的價格約為前者的四分之一,而品質損失可忽略。請注意,Kunavo 不提供嵌入服務:/v1/embeddings 是已實作的資料傳輸格式,但其後沒有啟用任何模型,因此嵌入步驟會直接呼叫 OpenAI、Voyage 或 Cohere,而產生步驟則呼叫 Kunavo。這不會讓管線增加任何費用,只有多一把金鑰,因為無論如何兩者都是獨立請求。

僅使用向量搜尋足以完成 RAG 檢索嗎?

不夠。向量相似度很適合處理模糊的語意比對,但無法處理 SKU 或訂單編號等字面詞彙。平行執行 BM25 關鍵字搜尋,再使用 reciprocal rank fusion 合併結果,通常能將 recall@5 從約 70% 提高到約 90%——這是在選擇品質尚可的嵌入模型之後,最大的單一品質提升。

如何阻止 RAG 系統產生幻覺?

在系統提示中加入三種模式:要求每個事實性聲明都附上 [doc:N] 引用;指示模型在內容沒有回答時說明它沒有相關資訊;並讓回答保持簡短——最多 600 個輸出 token 是很好的正式環境預設值。接著,在呈現前驗證每個引用的 id 是否存在於檢索結果集合中;不存在的 id 就是已被捕捉到的幻覺。

正式環境中的 RAG 查詢成本是多少?

在 5K 上下文、500 個輸出 token 且啟用提示快取的情況下:Claude Sonnet 4.6 每次查詢約 $0.007,Claude Haiku 4.5 約 $0.001(便宜 4 倍,品質約為 85%)。每天 10,000 次查詢時,Sonnet 約為 $70/天,Haiku 約為 $10/天。