標籤: RAG 架構設計

  • 生產級 RAG 架構實戰與踩坑指南

    生產級 RAG 架構實戰與踩坑指南

    📌 本文重點

    • 生產級 RAG 核心在「檢索精準度」
    • 先設計穩定索引管線,再優化查詢路徑
    • 權限與多租戶必須在索引層處理
    • 把 RAG 當「檢索系統 + LLM」來設計

    在 demo 環境跑得很順的 RAG,一接上真實企業文件就開始答非所問、亂編內容、延遲爆炸。這篇文章要解決的痛點很直接:如何把「玩具級 RAG」變成「可以被客服、業務、內部搜尋真正依賴」的生產系統,而不是靠換更大的 LLM 硬撐。


    重點說明:兩個關鍵心智模型

    1. 檢索精準度 > 模型能力

    多數生產事故不是 LLM 太笨,而是檢索到的內容就錯了:

    • chunk 切太碎:一句關鍵話被切開,LLM 根本看不到完整前後文
    • embedding 品質差:相似度搜尋抓不到真正相關的段落
    • 檢索策略太單純:只用 top-k dense vector,忽略 metadata / keyword / rerank

    💡 關鍵: 先把檢索品質拉高,通常比直接換更大的模型帶來更高的整體效益。

    結論:先優化檢索,再考慮換更大的模型。實務上,花在檢索調整的時間,ROI 通常比換模型高很多。

    2. 系統品質由最弱環節決定

    一個典型 RAG 流水線:

    資料 → chunking → embedding → 向量庫/索引 → 查詢策略 → rerank → LLM 回答

    任何一段出問題,整條鏈就報廢:

    • Chunking 不考慮結構:FAQ 題目和答案被拆開
    • 向量庫設計混亂:不同語言、不同資料源混在同一 index
    • 檢索策略只有單一路徑:某個類型問題天生查不到答案,直接導致幻覺

    把它當成一條 ML data pipeline 來設計:

    先穩定離線索引,再設計可觀測的線上查詢路徑。


    離線索引管線:從混亂文件到可用索引

    1. 資料清洗與格式統一

    企業常見情境:Confluence、PDF 掃描檔、工單、Excel 報表全混在一起。

    目標:轉成「統一的文件 schema」,例如:

    {
      "doc_id": "policy-2024-hr-001",
      "source": "confluence",
      "title": "2024 HR 政策總則",
      "lang": "zh-TW",
      "section_path": ["人資", "請假制度"],
      "content": "純文字內容...",
      "permissions": ["dept-hr", "role-admin"],
      "updated_at": "2024-05-01T10:00:00Z"
    }
    

    重點:

    • 提前把 權限、多租戶標籤、語言 等 metadata 帶上,後面檢索才能 filter
    • 盡量在這一步做結構抽取(標題、段落、表格轉文字)

    2. Chunk 策略:不是「固定 500 tokens 就好」

    實務上可以用「結構優先 + 長度限制」策略:

    MAX_TOKENS = 350
    OVERLAP_TOKENS = 50
    
    # 1. 先依標題 / 小節切
    sections = split_by_headings(doc.content)
    
    # 2. 每個 section 再依 token 長度切成多個 chunk
    chunks = []
    for sec in sections:
        for c in sliding_window_tokenize(
            sec,
            max_tokens=MAX_TOKENS,
            overlap_tokens=OVERLAP_TOKENS,
        ):
            chunks.append({
                "doc_id": doc.doc_id,
                "section_title": sec.title,
                "content": c.text,
                "start_offset": c.start,
                "end_offset": c.end,
            })
    

    好處:

    • 儘量保持語意完整的段落,減少「半句話」的 chunk
    • 使用 overlap 避免重要句子剛好被切斷

    3. Embedding 批次處理與向量庫設計

    建議:

    • 儘量統一使用 同一個 embedding 模型 處理相同語料
    • 做 batch embedding,避免每個 chunk 單獨呼叫 API 導致吞吐量慘烈

    示意程式碼:

    from openai import OpenAI
    from tqdm import batched
    
    client = OpenAI()
    
    EMBED_MODEL = "text-embedding-3-large"
    BATCH_SIZE = 256
    
    vectors = []
    for batch in batched(chunks, BATCH_SIZE):
        texts = [c["content"] for c in batch]
        resp = client.embeddings.create(
            model=EMBED_MODEL,
            input=texts,
        )
        for c, emb in zip(batch, resp.data):
            vectors.append({
                "id": f"{c['doc_id']}::{c['start_offset']}",
                "embedding": emb.embedding,
                "metadata": {
                    "doc_id": c["doc_id"],
                    "section_title": c["section_title"],
                    "permissions": doc.permissions,
                    "lang": doc.lang,
                    "source": doc.source,
                },
            })
    

    向量庫設計要點(不管你用 Pinecone、Weaviate、Qdrant、pgvector):

    • 每個 index 維持單一主要語言或資料型態,避免 embedding 空間太混
    • 必須支援 metadata filter(之後做權限、多租戶隔離)

    線上查詢路徑:多路檢索 + Rerank + Fallback

    1. 多路檢索與 metadata filter

    典型路徑不是一發向量搜尋就結束,而是:

    1. 根據使用者身份加上 權限 filter
    2. 同時走 向量檢索(semantic) 與 關鍵字 / BM25(lexical)
    3. 合併結果後用 reranker 排序

    假設你用的是一個支援 hybrid search 的向量庫:

    query_vector = embed_query(user_query)
    
    filters = {
      "must": [
        {"key": "tenant_id", "match": user.tenant_id},
        {"key": "permissions", "in": user.roles},
      ]
    }
    
    # dense + keyword 路徑
    vec_results = vector_index.search(
        vector=query_vector,
        top_k=30,
        filter=filters,
    )
    
    keyword_results = keyword_index.search(
        text=user_query,
        top_k=30,
        filter=filters,
    )
    
    candidates = dedup(vec_results + keyword_results)
    

    2. 使用 rerank 提升最終精準度

    在 top-30 或 top-50 候選上,用一個更強的 cross-encoder / LLM reranker 排序:

    from my_reranker import cross_encoder_rerank
    
    reranked = cross_encoder_rerank(user_query, candidates)  # 回傳已排序列表
    top_contexts = reranked[:5]
    

    好處:

    • dense 向量比較好抓「同一概念不同字眼」
    • keyword 比較好抓「精準術語」與數字、代碼
    • rerank 用較貴的模型,但只跑在小量候選上,CP 值高

    3. LLM 回答與 Fallback 策略

    最後呼叫 LLM 時,不要直接餵所有 chunk,而是:

    • 限制 context 數量(例如最多 4–8 個 chunk)
    • 明確告訴模型:只能根據提供的資料回答
    SYSTEM_PROMPT = """你是公司內部知識庫助理,只能根據提供的 context 回答。
    如果找不到答案,請明確回答「依目前資料無法確認」。
    """
    
    context_str = "\n\n".join(
        [f"[{i}] {c['content']}" for i, c in enumerate(top_contexts)]
    )
    
    completion = client.chat.completions.create(
        model="gpt-4.1-mini",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"問題:{user_query}\n\n參考資料:\n{context_str}"},
        ],
    )
    

    Fallback 設計建議:

    • 若檢索結果的 相似度分數都低於某門檻,直接回答「查無對應資料」而非亂編
    • 若向量檢索失敗,可 fallback 成只用 keyword search

    常見坑與實戰建議

    1. 混亂企業文件與長上下文幻覺

    • 問題:把整份 PDF 直接丟進 LLM 的 long context,看起來很酷,但成本與幻覺都超高
    • 建議:
    • 把 long context 當成 最後手段,先用精準檢索把範圍縮小
    • 針對常見錯誤問句,建立 失敗樣本集,離線迭代 chunking / 檢索策略

    2. 權限與多租戶

    • 絕對不要在 LLM prompt 內才控制權限
    • 權限應該在 向量庫的 metadata filter 層 完成
    • 多租戶建議:
    • 小規模:同一 index,用 tenant_id 做 filter
    • 大客戶:獨立 index,避免資料量與權限邏輯互相影響

    3. 成本與延遲控制

    可以從幾個槓桿調整:

    • top-k:從 20 慢慢調到 50 看效果,不要一開始就丟 100
    • 使用 gpt-4.1-mini / Llama 小模型當回答模型,搭配精準檢索,通常已足夠
    • 批次 embedding、批次向量查詢,減少 API round-trip

    💡 關鍵: 先優化 top-k、模型選型與批次策略,往往就能在不換主模型的情況下顯著降低成本與延遲。

    4. 離線評估與線上監控

    離線評估:

    • 建立一小包「問題–標準答案–應該被檢索到的 chunk」
    • 指標:Hit@k、MRR、生成答案與標準答案的相似度

    線上監控:

    • 記錄每次 query 的:
    • 檢索到的 doc_id / 相似度分數
    • LLM 回答 + 使用者後續行為(是否重新提問、是否人工改寫)
    • 針對「多次重試」或「被人工標記錯誤」的 query,自動加入離線失敗樣本集

    不同規模專案的 RAG 架構選型建議

    1. 小型專案(單一產品 FAQ / 文件 < 1k 篇)

    • 架構:Single-vector RAG 即可
    • 單一向量庫 index
    • 單一路徑的向量檢索(top-k=10)+ 簡單 rerank 或甚至不 rerank
    • 什麼時候足夠:
    • 問題類型集中、文件格式相對乾淨
    • 沒有複雜權限、多租戶

    2. 中型專案(多產品、多來源文檔,含內部知識庫)

    • 架構:Hybrid Search RAG
    • 向量檢索 + keyword/BM25 檢索
    • Metadata filter 處理基本權限、多語言
    • reranker 排序 top-30
    • 適用情境:
    • 文件來源與格式混雜
    • 問題類型多樣(政策、程式碼、FAQ 混在一起)

    3. 大型企業級專案(多租戶 SaaS、嚴格權限控制)

    • 架構:分層 RAG pipeline
    • 第一層:根據 query 先判斷「要去哪個 domain / product / tenant」
    • 第二層:在該 domain 內做 hybrid search + rerank
    • 針對高價值流程(法務、財務)再加一層 LLM verification / rule-based check
    • 必備:
    • 完整 observability(檢索 log、失敗樣本管理)
    • 嚴格權限控制嵌在 index filter,不依賴 prompt

    小結:先把檢索做好,再談「更聰明的模型」

    如果要一句話總結生產級 RAG:

    把它當檢索系統 + LLM,而不是 LLM + 一點點檢索。

    從離線索引管線、chunk 策略、向量庫設計到線上多路檢索與 rerank,只要能有系統地優化這些「最弱環節」,你的 RAG 往往在不升級模型的情況下,就能從 demo 品質變成可以上線承壓的生產系統。

    🚀 你現在可以做的事

    • 把現有企業文件先整理成統一 document schema,並補上權限與語言等 metadata
    • 實作一條離線 chunking + batch embedding 管線,搭配支援 metadata filter 的向量庫
    • 為常見查詢建立「失敗樣本集」,定期用 Hit@k / MRR 檢驗並調整檢索與 chunk 策略