標籤: 向量資料庫

  • Agent Loop RAG:超越傳統 RAG 的下一步

    Agent Loop RAG:超越傳統 RAG 的下一步

    📌 本文重點

    • 多輪 Agent Loop RAG 可讓 QA 準確率大幅提升
    • 關鍵是「決策 +多輪檢索」而非一味加 reranker
    • 實作重點在 decision head、記憶管理與 citation 驗證

    Google FRAMES 的多跳 QA 實驗顯示:最佳傳統 RAG pipeline 78.9%,Agent Loop 式 RAG 92.7%,幾乎等同「直接給模型正確文件」。對工程團隊來說,這代表一件很直接的事:

    你不一定需要更大的模型或更複雜的 reranker,而是需要讓模型有“多輪檢索與決策能力”的 RAG 架構。

    💡 關鍵: 在相同文件與 embedding 條件下,Agent Loop 式 RAG 能比傳統 RAG 多拿約 14 個百分點的準確率,效益遠勝只換大模型或小 reranker。

    下面從工程視角拆解:Agent Loop 式 RAG 到底多了什麼、怎麼實作一個最小可行版本、以及部署時要小心哪些坑。


    重點說明:Agent Loop 式 RAG 的關鍵差異

    1. 一次性 Top‑k vs. 多輪自我檢索

    傳統 RAG:
    1. 使用者 query
    2. 向量庫 top_k 檢索
    3. 把 chunks 拼進 context
    4. 一次性回答

    Agent Loop RAG(FRAMES 的 Agent Loop 類型):
    1. 使用者 query
    2. 模型判斷:需要檢索嗎?→ 呼叫 檢索工具
    3. 讀結果後:
    – 若資訊不足:改寫 / 分解 query 再查一次
    – 若資訊足夠:生成答案
    4. 持續迭代,直到滿足「可以回答」的停止條件

    對多跳問題(典型企業知識庫 QA)來說:
    – 傳統 RAG 常卡在「第一跳檢索就打偏」
    – Agent Loop 允許模型根據前一輪讀到的內容,修正檢索方向

    這也是為什麼同一組 embedding/文件,Agent Loop 在 FRAMES 能拉高約 14 個百分點的原因。


    2. Agent 如何決定「要不要再查」?

    實務上會做成一個小的 decision head,而不是完全靠 prompt。常見做法:

    • 用一個 工具選擇 schema,讓模型每輪輸出:
    • action:"search" | "answer"
    • reason:決策理由(方便 debug)
    • query:若 action 是 search,要查的內容

    範例(以 OpenAI / OpenRouter 類 API 為例):

    // tool schema(簡化版)
    {
      "type": "function",
      "function": {
        "name": "agent_decide",
        "description": "決定下一步是再次檢索還是回答",
        "parameters": {
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "enum": ["search", "answer"]
            },
            "reason": {"type": "string"},
            "query": {
              "type": "string",
              "description": "如果需要檢索,這是更新後的查詢語句"
            }
          },
          "required": ["action", "reason"]
        }
      }
    }
    

    在 loop 中實作邏輯:

    • 若 action == "search":
    • 呼叫你的 檢索 API(向量庫、混合搜尋皆可)
    • 把檢索結果 append 到對話狀態
    • 若 action == "answer":
    • 要求模型在嚴格引用現有 context 的前提下生成最終答案

    這種方式比「用 system prompt 告訴模型:資訊不夠就再查」穩定得多,因為 decision 被結構化,容易觀測與評估。


    3. 為何小 reranker 反而拖垮效果?

    FRAMES 實驗的結果很不直覺:
    – 小 reranker:讓最佳 pipeline 準確率 掉了 9 個百分點
    – 大 reranker:只有輕微提升

    💡 關鍵: 弱 reranker 容易錯殺原本已在 top_k 的關鍵 chunk,在多跳 QA 中反而降低整體答對率。

    工程上的原因通常是:
    1. 弱 reranker = 引入噪音排序
    – 原本 top_k 裡其實已有足夠訊息
    – 小模型 rerank 反而把關鍵 chunk 往下排
    2. 多跳場景不適合一次性排序整包文件
    – FRAMES 類題目常需要「先找到中間 entity,再查下個文件」
    – 一次性把所有文件塞進 context,再怎麼 rerank 都很難

    對多跳問題來說,檢索策略(多輪) > 排序策略(rerank)。實務結論:
    – 先把心力放在 Agent Loop + query 改寫/分解
    – reranker 真要上,從 大一點的模型 + 明確場景評估 開始


    實作範例:最小可行 Agent Loop RAG

    以下是一個可以直接改成你專案版本的「最小可行」架構:

    1. 介面與狀態設計

    核心組件:
    – 檢索 API:search_docs(query, top_k) -> [Doc]
    – 對話狀態:messages + memory
    – 工具 schema:agent_decide + search_tool

    # 假設已有向量庫 search_docs
    
    def search_docs(query: str, top_k: int = 5):
        # return list of {"id", "title", "content"}
        ...
    
    # 對話記憶(簡化)
    class AgentMemory:
        def __init__(self):
            self.history = []      # user / assistant turns
            self.retrieved = []    # 已讀過的 docs meta
    
        def add_retrieval(self, query, docs):
            self.retrieved.append({"query": query, "docs": docs})
    

    2. 工具定義(決策 + 檢索)

    TOOLS = [
        {
            "type": "function",
            "function": {
                "name": "search_tool",
                "description": "用關鍵字搜尋文件庫",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "query": {"type": "string"},
                        "top_k": {"type": "integer", "default": 5}
                    },
                    "required": ["query"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "agent_decide",
                "description": "決定接下來是檢索還是回答",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "action": {"type": "string", "enum": ["search", "answer"]},
                        "reason": {"type": "string"},
                        "query": {"type": "string"}
                    },
                    "required": ["action", "reason"]
                }
            }
        }
    ]
    

    3. Agent Loop Pseudo-code

    MAX_STEPS = 4
    
    def agent_loop(user_query: str, llm_client) -> str:
        memory = AgentMemory()
        messages = [
            {"role": "system", "content": "你是一個嚴格依據檢索文件回答的助理。"},
            {"role": "user", "content": user_query},
        ]
    
        for step in range(MAX_STEPS):
            # 1) 請模型用 agent_decide
            decision = call_llm_decide(messages, llm_client)
    
            if decision["action"] == "search":
                q = decision.get("query") or user_query
                docs = search_docs(q, top_k=5)
    
                # 避免重複查同批文件(一個常見坑)
                if is_duplicate_retrieval(memory, q, docs):
                    # 強制模型換策略:更新 prompt 或降低溫度
                    messages.append({"role": "assistant", "content": "目前檢索結果重複,請改寫查詢或嘗試總結。"})
                    continue
    
                memory.add_retrieval(q, docs)
                # 把摘要版 docs 塞回 messages
                context_txt = summarize_docs_for_context(docs)
                messages.append({
                    "role": "assistant",
                    "content": f"[檢索結果]\n{context_txt}"
                })
    
            elif decision["action"] == "answer":
                # 2) 最終回答:強調不得虛構引用
                messages.append({
                    "role": "system",
                    "content": "只允許引用上述[檢索結果]中出現的資訊,若沒有就回答無法判斷。"
                })
                return call_llm_answer(messages, llm_client)
    
        # 超過 MAX_STEPS 仍未收斂
        return "目前檢索仍不足以可靠回答這個問題。"
    

    這樣的框架可以直接套到既有向量庫專案,只需要:
    – 把原本「一次性檢索 + 回答」拆成 decision → 檢索 → decision → 回答 loop
    – 把 檢索結果縮摘要(避免 context 爆掉)


    建議與注意事項:部署時的實務考量

    1. 延遲與成本:多輪檢索怎麼控

    Agent Loop 必然:
    – 更多 LLM call(decision + answer)
    – 更多檢索 call

    控制策略:
    – 設定 MAX_STEPS(通常 3–5 輪就夠,多了收益遞減)
    – decision 用較小模型、最後回答用大模型(類似 routing)
    – 對「簡單問題」走捷徑:
    – 先讓模型判斷 need_search: bool
    – 為 False 時走 direct answer 或 cached FAQ


    2. 限制 hallucination 與 citation 造假

    FRAMES 實驗也發現,即使明講「只能根據文件」,模型仍會:
    – 憑記憶補完內容,硬塞 citation 看起來很合理

    實務防禦:

    1. 結構化 citation:
    2. 要求回答時返回:{"answer": ..., "citations": [doc_id, ...]}
    3. 像「citation receipt」一樣,保存:哪個句子對應哪個 chunk

    4. 離線自動驗證:

    5. 對高風險場景(法務、醫療),可加一個 verification pass:

      • 把 answer + citations 再丟給 LLM,問:
      • 這些句子是否都能在 citied chunks 中找到明確證據?
      • 若否,標記為需人工審查
    6. 明確拒答路徑:

    7. 在 prompt 中允許、甚至鼓勵模型回答「依據現有檢索結果無法判斷」,比亂猜好

    3. Loop 不收斂、重複查同一批文件、context 爆掉

    這是 Agent Loop RAG 的三大工程坑:

    1. loop 不收斂:
    2. 加 MAX_STEPS 上限
    3. decision prompt 中加入:「若多次檢索仍無新訊息,請選擇 answer 並說明無法回答」

    4. 重複查同一批文件:

    5. 在 AgentMemory 中存檢索 query + top 文檔 id
    6. 若新一輪檢索與前一輪 query 相似度高且 top ids 近似,視為重複,強制換策略

    7. context 爆掉:

    8. 對每次檢索結果先做 chunk‑level 摘要 再拼進 prompt
    9. 對話歷史採 滑動視窗,只保留「最近幾輪檢索摘要 + 關鍵決策理由」

    4. 從既有 RAG 平滑遷移到 Agent Loop

    推薦遷移路徑:

    1. Phase 1:保留現有 RAG,只加一層 decision head
    2. 先讓模型判斷 need_extra_search(布林值)
    3. 若是:再跑一次檢索 + answer

    4. Phase 2:引入多輪 query 改寫

    5. 在 decision 中加入 refined_query 欄位
    6. 觀察實際 query 改寫對召回率的提升

    7. Phase 3:完全 Agent Loop

    8. 將檢索、決策、回答視為三個工具,以 loop 驅動
    9. 為每一輪紀錄 log,方便做 自動打標 / 事後評估

    觀測上,建議:
    – 對一批固定測試集(可自建,也可用 FRAMES 類題目)
    – 比較:
    – 傳統 RAG pipeline vs Agent Loop RAG 的答對率 / citation 正確率
    – 並記錄平均回合數與成本


    總結:
    – 多跳與複雜 QA 場景下,Agent Loop 式 RAG 的收益實際可觀(以 FRAMES 為證)
    – 與其疊更多「hybrid search + 小 reranker」,不如先讓模型具備:
    – 多輪檢索決策能力
    – 對話狀態 / 檢索記憶管理
    – 可觀測、可驗證的 citation 流程

    如果你已有一個傳統 RAG QA 服務,本文的最小 Agent Loop 範例幾乎可以直接改成你自己的 API 呼叫,把 loop 跑起來,會比換更大模型更划算。

    🚀 你現在可以做的事

    • 在現有專案中,先加上一層 agent_decide decision head,觀察多一次檢索帶來的答對率變化
    • 用自家資料集或 FRAMES 類題目,對比「傳統 RAG vs Agent Loop RAG」的準確率與平均步數
    • 為現有回答格式加入結構化 citations 欄位,並設計一個簡單的離線驗證流程
  • 生產級 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 策略
  • 企業級 RAG 實戰架構與評估全攻略

    企業級 RAG 實戰架構與評估全攻略

    📌 本文重點

    • 企業級 RAG 關鍵在檢索架構與權限設計
    • 混合檢索 + 重排序是實務標配
    • metadata/ACL 必須在檢索前就生效
    • 評估指標需同時涵蓋正確性與延遲

    在企業環境裡,RAG 的真正價值是:讓 LLM 能安全地用內部最新知識、可控地減少幻覺、並可以用工程方法迭代優化。本文從工程視角拆解:向量資料庫選型、檢索策略、chunking 與 metadata 設計、多租戶與權限控制、線上/離線評估指標,以及常見坑與解法。


    重點說明

    1. 檢索策略:不只向量,BM25 + 重排序才是實務標配

    企業知識庫多來源、多格式,單純向量檢索很容易 miss 關鍵字或出現語義偏移。建議:

    1. 混合檢索(Hybrid Search):
    2. 先用 BM25 或全文搜尋(Postgres tsvector、Elasticsearch、OpenSearch)做初篩
    3. 再用向量相似度做語義排序,避免只靠 embedding 導致關鍵領域術語被忽略

    4. 重排序(Rerank):

    5. 對 top-50 結果使用 cross-encoder / reranker 模型重新排序(如 bge-reranker-large)
    6. 好處:在不放大向量庫負載的情況下,顯著提升 answer correctness

    💡 關鍵: 透過「先 BM25 初篩、再向量排序、最後 rerank」三段式檢索,可以在不犧牲效能的前提下,大幅提升答案正確率與穩定性。

    實務上你可以:

    • 用 pgvector 存 embedding + Postgres 原生全文檢索
    • 或用 Milvus 做向量檢索,搭配獨立搜尋服務(Elastic / OpenSearch)做 BM25

    2. Chunking 與 Metadata:為檢索設計,而不是為分段而分段

    錯誤的 chunking 會直接降低 context recall,企業常見問題是「段太小、沒有結構」。建議:

    1. 混合策略 chunking:
    2. 先以語義斷點(標題、小節)切大塊,再用字數/token 限制微調
    3. 典型配置:512–1024 tokens + 128–256 tokens overlap

    4. metadata schema 是檢索與權限的核心:至少包含:

    5. tenant_id: 多租戶隔離
    6. doc_id, section_id: 追蹤來源與回溯
    7. source_system: Slack / GDrive / Confluence / Jira…
    8. visibility_tags / acl: 權限控制(角色、群組、文件 owner)

    好處:

    • chunk 不只是「段落」,而是帶有權限、業務上下文的最小檢索單位
    • 評估失敗案例時可以精準定位是哪個 chunk / doc 出問題

    3. 多租戶與權限:在「檢索前」把不該看的東西砍掉

    語義向量檢索會天然繞過傳統 RBAC 的關鍵字邊界,必須在檢索前加上身分綁定的 filter:

    • Identity-bound pre-retrieval filters:查詢向量庫前,先用 tenant_id + ACL 建立 filter
    • 所有檢索 API 都要支援條件:WHERE tenant_id = $tenant AND acl @> $user_roles

    關鍵結論:

    • 權限控制不能只做在「生成後」,因為一旦檢索到不該看的 context,就算你遮罩輸出,也已經有資料洩漏風險
    • 向量庫層一定要有 硬隔離策略(租戶切庫 / 切 collection / 至少切 partition)

    💡 關鍵: 權限過濾一定要在向量檢索「之前」就生效,否則只在輸出層遮罩,其實已經完成資料洩漏。


    4. 評估與監控:不只看 BLEU,更要看「能不能在生產上 debug」

    建議至少三類指標:

    1. Answer Correctness(生成品質)
    2. LLM 自評(使用 judge model)、人工標註小樣本、或用 rule-based 準確率

    3. Context Recall(檢索品質)

    4. 離線 benchmark:準備一組「問題 + 標準文件」pair,測量 top-k 是否包含正確文件

    5. Latency(服務體驗)

    6. 分段量測:檢索延遲、重排序延遲、LLM 生成延遲

    好處:

    • 可以清楚區分是檢索出錯、權限漏控,還是 LLM 幻覺
    • 為迭代(換 embedding 模型、調 chunking、調 rerank)提供可量化目標

    實作範例:Node + pgvector + 開源 embedding 的簡單企業知識庫

    下面用一個簡化的架構示範:

    • 向量庫:Postgres + pgvector
    • Embedding 模型:BAAI/bge-base-en(你可以換成中文模型)
    • 後端:Node.js (TypeScript)

    1. 資料庫 schema 設計

    -- pgvector 安裝後,建立知識庫表
    CREATE TABLE kb_chunks (
      id             BIGSERIAL PRIMARY KEY,
      tenant_id      TEXT NOT NULL,
      doc_id         TEXT NOT NULL,
      section_id     TEXT,
      source_system  TEXT NOT NULL,
      content        TEXT NOT NULL,
      embedding      VECTOR(768) NOT NULL,
      visibility_tags TEXT[], -- e.g. ['legal', 'finance']
      acl_roles      TEXT[],  -- e.g. ['legal_team', 'admin']
      created_at     TIMESTAMP DEFAULT NOW()
    );
    
    CREATE INDEX idx_kb_chunks_tenant ON kb_chunks (tenant_id);
    CREATE INDEX idx_kb_chunks_acl_roles ON kb_chunks USING GIN (acl_roles);
    CREATE INDEX idx_kb_chunks_visibility_tags ON kb_chunks USING GIN (visibility_tags);
    CREATE INDEX idx_kb_chunks_embedding ON kb_chunks USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
    

    重點:

    • 用 ivfflat + lists 調優檢索速度
    • 用 acl_roles / visibility_tags 作為 pre-retrieval filter 的基礎

    2. Chunking 與寫入管線(Python 伺服端工具)

    from transformers import AutoTokenizer, AutoModel
    import psycopg2
    
    MODEL_NAME = "BAAI/bge-base-en"
    MAX_TOKENS = 800
    OVERLAP = 200
    
    # 省略連線與模型初始化
    
    def chunk_document(text: str, tenant_id: str, doc_id: str, source_system: str, acl_roles: list[str]):
        tokens = tokenizer.encode(text)
        chunks = []
        start = 0
        while start < len(tokens):
            end = min(start + MAX_TOKENS, len(tokens))
            chunk_tokens = tokens[start:end]
            chunk_text = tokenizer.decode(chunk_tokens)
            chunks.append(chunk_text)
            start += MAX_TOKENS - OVERLAP
        return chunks
    
    def embed(texts: list[str]):
        # 簡化:batch embedding
        inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
        with torch.no_grad():
            outputs = model(**inputs)
        embeddings = outputs.last_hidden_state[:, 0, :].cpu().numpy()  # CLS pooling 或改用 mean pooling
        return embeddings
    
    # 寫入 pgvector 的 SQL 略
    

    好處:

    • chunking 有明確 token 參數,可調試
    • metadata 與 ACL 一起寫入,避免「後面再補權限」的錯誤做法

    3. Node RAG Pipeline:帶權限的混合檢索 + 重排序

    import { Pool } from 'pg';
    import { getEmbedding } from './embeddingClient'; // 呼叫 Python 或直接用 Node 模型
    
    const pool = new Pool({ /* db config */ });
    
    interface UserContext {
      tenantId: string;
      roles: string[];
    }
    
    export async function ragAnswer(query: string, user: UserContext) {
      const embedding = await getEmbedding(query); // 返回 Float32Array 長度 768
    
      // 1. 帶 ACL 的向量檢索
      const vectorSql = `
        SELECT id, content, doc_id, section_id, source_system,
               1 - (embedding <=> $1::vector) AS score
        FROM kb_chunks
        WHERE tenant_id = $2
          AND acl_roles && $3::text[]
        ORDER BY embedding <-> $1::vector
        LIMIT 50;
      `;
    
      const vectorRes = await pool.query(vectorSql, [embedding, user.tenantId, user.roles]);
    
      // 2. 這裡可以加 BM25 / tsquery 做 keyword 初篩(略)
    
      // 3. 用 reranker 模型重排序(虛擬碼)
      const reranked = await rerank(query, vectorRes.rows.map(r => r.content));
      const topContexts = reranked.slice(0, 5).map(r => r.content);
    
      // 4. 組 prompt 給 LLM
      const prompt = `你是公司內部助理,回答必須只根據提供的內容。
    
    [檢索到的內容]
    ${topContexts.join('\n---\n')}
    
    [問題]
    ${query}
    
    請根據上面內容回答,若資料不足請明確說「目前知識庫沒有相關資訊」。`;
    
      const answer = await callLLM(prompt); // 例如 OpenAI / 本地 LLM
    
      return {
        answer,
        contexts: topContexts,
        debug: {
          retrievedCount: vectorRes.rowCount,
          tenantId: user.tenantId,
        },
      };
    }
    

    關鍵 API / 參數:

    • embedding <-> $1::vector:pgvector 距離運算,建議用 L2 或 cosine
    • acl_roles && $3::text[]:在檢索階段就做 ACL filter(pre-retrieval)
    • LLM prompt 強制「無資料要明說」,降低幻覺

    4. 簡單線上評估與監控

    可以加一個中介層紀錄:

    await logMetrics({
      tenantId: user.tenantId,
      query,
      latencyMs,
      retrievedCount: vectorRes.rowCount,
      model: 'bge-base-en',
      llmModel: 'gpt-4o',
    });
    

    後續離線跑:

    • 對一批標註問題跑 RAG,請 judge LLM 給出 correctness score(1–5)
    • 比較不同 embedding / chunking / rerank 策略的分數與延遲,做 A/B 測試

    建議與注意事項

    1. 幻覺依然存在:RAG 不是「關幻覺開關」

    • 即使檢索正確,LLM 仍可能「補細節」或「推理過頭」
    • 解法:
    • 明確在 prompt 中說:「不在 context 裡的資訊一律不要編造」
    • 在輸出層加 rule-based 檢查(如法律/財務答案一定要附來源文件 ID)

    2. 檢索結果不穩定:embedding / chunking / rerank 三者缺一不可

    • 換 embedding 模型時,一定要重新做離線 benchmark,不要只看 demo 感覺
    • chunking 調整需同時看:
    • context recall(有沒有檢索到正確文件)
    • latency(chunk 變多會拖慢向量檢索)

    💡 關鍵: 每次調整 embedding 或 chunking,都要用標註資料評估「召回率 + 延遲」,而不是只憑主觀 demo 感受做決策。


    3. 權限洩漏:不要相信「應用層自己會控」

    • RBAC 必須深入到向量庫 query 層
    • 尤其是把 Slack / GDrive / Jira 接在一起作企業 search 時:
    • 預設策略是「拒絕」,只對明確授權內容建索引
    • 建議對敏感資料(legal / M&A 文件)做 單獨 collection / database 隔離

    4. 迭代路線:從 PoC 到生產級

    1. PoC 階段:單租戶、簡單 chunking、只向量檢索
    2. Beta 階段:加 metadata schema、權限 filter、簡單監控
    3. 生產階段:
    4. Hybrid search(BM25 + 向量)
    5. reranker 模型
    6. 線上指標 + 離線 benchmark(answer correctness / context recall / latency)
    7. 權限審計與合規(log 每次檢索的 doc_id 與 user_id)

    核心結論:企業級 RAG 的關鍵不在「模型選哪個」,而在於:檢索架構、權限設計與可評估性是否工程化落地。只要這三件事做穩,模型與向量庫都可以迭代替換,而整個系統仍保持穩定、可回溯、可持續優化。


    🚀 你現在可以做的事

    • 在現有 Postgres 專案中安裝 pgvector,建立含 tenant_id 與 acl_roles 的 kb_chunks 表
    • 準備一批「問題 + 標準答案文件」pair,離線跑一次 context recall/latency benchmark
    • 將現有 RAG 應用的權限邏輯下沉到向量庫查詢層,加入 tenant_id + ACL 的 pre-retrieval filter
  • 為 AI Agent 建長期記憶:Rust 實戰

    為 AI Agent 建長期記憶:Rust 實戰

    📌 本文重點

    • 單純丟向量庫不足以支撐可運維的長期記憶
    • 將 Events / Sessions / Facts 結構化並分層檢索
    • 以 Rust 記憶服務提供 vendor-agnostic 的統一記憶層

    AI Agent 開到一定規模後,「把聊天記錄丟進向量資料庫」很快就不夠用:

    • 記憶膨脹導致成本失控、查詢變慢
    • 多個 Agent、不同 LLM 共用資料時互相污染
    • 想換模型或供應商時,歷史記憶幾乎不能重用

    這篇從記憶層設計問題拆解起,用 Rust 開源專案 akitaonrails/ai-memory 當主線,示範如何把「長期記憶」升級成可運維的基礎設施,而不是一堆臨時向量。


    重點說明:把「記憶」變成明確的基礎設施

    💡 關鍵: 把記憶從「單一向量庫」拆成 Events / Sessions / Facts,可以同時兼顧語義檢索與結構化查詢,讓長期記憶真正可維護、可重用。

    1. 資料結構:從 chat log 到 events / sessions / facts

    粗糙作法:

    • 每輪對話做 embedding → 丟進向量庫 → 用 semantic search 回撈

    問題:

    • 事件順序感消失:只剩相似度,沒有「先後」與「上下文」
    • 無法表達持久事實:像使用者偏好、系統狀態,只是散落在多個對話片段

    較好的設計是拆成三類:

    • Events:原始互動紀錄
    • 例:user_message, tool_call, agent_decision
    • 保留時間戳與來源,適合做 audit 和重播
    • Sessions:一次任務或一段對話的邊界
    • 例:session_id、agent_id、status=completed
    • 讓你知道「這次訂房流程」完整發生了什麼
    • Facts:可重用、可更新的持久知識
    • 例:使用者偏好、系統配置、業務規則
    • 需要版本與狀態(active, deprecated)

    這樣一來,semantic search 用在 Events/Facts 的內容檢索,structured query 用在 Sessions/Facts 的條件篩選,組出可靠的上下文再餵給 LLM,而不是全靠相似度。


    2. 檢索策略與 vendor-agnostic 介面

    實際上你會需要兩層 API:

    • 語義檢索層:
    • 例如:search_events(query, top_k)、search_facts(query, filters)
    • 背後可接不同 embedding provider(OpenAI, local model 等),但對上層 Agent 暴露的是穩定的介面

    • 結構化查詢層:

    • 例如:get_session(session_id)、list_facts(owner_id, kind="preference")
    • 通常走資料庫索引(Postgres, SQLite),不牽涉向量搜尋

    akitaonrails/ai-memory 正是要提供這種「供應商無關的記憶層」,讓你可以:

    • 今天用 OpenAI,明天換到本地模型
    • 多個 Agent framework(LangChain, LlamaIndex, 自家的 SDK)共用同一套記憶服務

    3. Rust 記憶服務 + RAG / 工具調用整合

    長期記憶一旦變成基礎設施,就有三個技術要求:

    • 效能:高頻讀寫、多 Agent 並行
    • 安全:記憶裡必然有 PII 和敏感業務資訊
    • 跨語言可接:Python、Node、Go、甚至 CLI agent 都要用

    Rust 在這裡的角色:

    • 提供一個高效、型別安全的記憶核心(ai-memory)
    • 對外以 FFI / HTTP / IPC 三種方式暴露 API
    • Agent 框架只需要呼叫類似 store_event / query_memory 的介面,就能把記憶接進 RAG pipeline 或工具調用流程

    實作範例:用 ai-memory 建一個共用長期記憶服務

    以下以虛構的 ai-memory 介面示意,重點放在設計思路而不是精確函式名稱。

    1)定義記憶 schema,接上現有 Agent

    先在 Rust 端定義核心結構:

    // memory_schema.rs
    
    #[derive(Debug, Clone)]
    pub enum EventKind {
        UserMessage,
        AgentReply,
        ToolCall,
        ToolResult,
    }
    
    #[derive(Debug, Clone)]
    pub struct Event {
        pub id: String,
        pub session_id: String,
        pub agent_id: String,
        pub kind: EventKind,
        pub content: String,
        pub metadata: serde_json::Value,
        pub created_at: chrono::DateTime<chrono::Utc>,
    }
    
    #[derive(Debug, Clone)]
    pub struct Fact {
        pub id: String,
        pub owner_id: String,    // user_id 或 system
        pub kind: String,        // preference, policy, profile
        pub content: String,
        pub embedding: Option<Vec<f32>>,  // for semantic search
        pub version: i32,
        pub status: String,      // active, deprecated
    }
    

    對 Python Agent 來說,只需要一個薄封裝,把每輪互動寫入記憶:

    # agent_memory.py
    
    class MemoryClient:
        def __init__(self, base_url: str):
            self.base_url = base_url
    
        def store_event(self, session_id, agent_id, kind, content, metadata=None):
            payload = {
                "session_id": session_id,
                "agent_id": agent_id,
                "kind": kind,
                "content": content,
                "metadata": metadata or {},
            }
            requests.post(f"{self.base_url}/events", json=payload)
    
        def search_facts(self, query, owner_id=None, kind=None, top_k=5):
            params = {
                "query": query,
                "owner_id": owner_id,
                "kind": kind,
                "top_k": top_k,
            }
            resp = requests.get(f"{self.base_url}/facts/search", params=params)
            return resp.json()["results"]
    

    Agent loop 中的使用方式:

    memory = MemoryClient(base_url="http://localhost:8080")
    
    # 每輪對話寫入 event
    memory.store_event(
        session_id=session_id,
        agent_id="support-bot-v2",
        kind="UserMessage",
        content=user_input,
    )
    
    # 在回應前查詢長期偏好
    facts = memory.search_facts(
        query="使用者的語言偏好與通知設定",
        owner_id=user_id,
        kind="preference",
        top_k=3,
    )
    
    context = format_facts_for_prompt(facts)
    response = llm.chat(prompt=build_prompt(user_input, context))
    

    這樣你的 Agent 邏輯完全不需要知道底層用的是哪家向量資料庫,也不綁在單一 LLM provider。


    2)Rust 記憶服務:FFI / HTTP / IPC 三種接法

    假設 ai-memory 提供核心 crate ai_memory_core,我們可以用三種方式包出去。

    HTTP 服務(最通用)

    優點:任何語言都能接;缺點:有網路 overhead。

    // http_server.rs
    
    use ai_memory_core::{MemoryStore, SearchQuery};
    use axum::{routing::get, routing::post, Json, Router};
    
    async fn create_event(Json(payload): Json<CreateEventRequest>) -> Json<EventResponse> {
        let mut store = MemoryStore::global();
        let event = store.store_event(payload.into())?;
        Json(EventResponse::from(event))
    }
    
    async fn search_facts(Json(query): Json<SearchFactsRequest>) -> Json<SearchFactsResponse> {
        let store = MemoryStore::global();
        let results = store.search_facts(SearchQuery::from(query))?;
        Json(SearchFactsResponse { results })
    }
    
    pub fn app() -> Router {
        Router::new()
            .route("/events", post(create_event))
            .route("/facts/search", get(search_facts))
    }
    

    命令列 Agent 或後端服務只要跑一個共用 memory server,就可以用 HTTP 存取。

    FFI(嵌入到 Python / Node 進程)

    優點:效能好、延遲低;缺點:需維護 binding。適用高頻工具調用型 Agent。

    // lib.rs (Rust)
    
    #[no_mangle]
    pub extern "C" fn store_event_ffi(json_payload: *const c_char) -> *const c_char {
        // 解析 JSON,呼叫 MemoryStore,回傳 JSON 字串
    }
    

    Python 側使用 ctypes 或 pyo3 包一層,暴露同樣的 store_event / search_facts 介面,對應前面的 MemoryClient。

    IPC(同機不同進程,高安全場景)

    可以用 Unix socket + protobuf 或 Cap’n Proto:

    • 優點:比 HTTP 更輕量,適合同機多服務共用記憶
    • 缺點:部署稍複雜,需額外 tooling

    設計上只要確保三種接法都共用同一套核心 API(MemoryStore),就能在不同專案中任意選擇實作方式,而不改動 Agent 邏輯。


    3)版本升級、資料遷移、多模型共用記憶庫

    長期記憶真正困難在於 「持續演化」。

    Schema 版本管理

    在 Fact 結構上掛版本欄位:

    pub struct Fact {
        pub id: String,
        pub owner_id: String,
        pub kind: String,
        pub content: String,
        pub version: i32,       // schema/version
        pub status: String,
        pub embedding: Option<Vec<f32>>,  
    }
    

    當你需要新增欄位或改變結構時:

    • 新寫入用 version = 2
    • 舊資料由 migration job 緩慢升級
    • 查詢 API 接受 min_version / max_version 作為過渡策略

    多模型共用同一記憶庫

    不同 LLM 對同一條記錄的解讀可能不同,所以要在 metadata 明確標注來源模型:

    pub struct Event {
        pub model_name: Option<String>,   // gpt-4o, llama-3-70b 等
        pub agent_id: String,
        // ...
    }
    

    策略上:

    • Facts 儘量由工具或人類決策產生,減少「模型幻覺」寫入持久記憶
    • 檢索時可加 filter:model_name in ["gpt-4o", "internal-rule-engine"],避免用某些品質較差模型產生的事件來推論

    建議與注意事項:把坑提前填好

    💡 關鍵: 控制 embedding 範圍、處理 PII、標記 embedding 模型,是讓長期記憶在成本、隱私與準確度間取得平衡的三個關鍵。

    1. 記憶膨脹與成本控制

    問題:

    • 所有對話都 embedding → 向量庫數量爆炸,成本跟查詢延遲一起上升

    建議:

    • 只對重要 Events / Facts 做 embedding,例如:完成一個任務時產生 summary fact
    • 針對長期 session,定期做 conversation summarization,保留摘要而不是 raw log
    • 對向量庫設計 TTL 或冷/熱層級:冷資料只保留摘要向量

    2. 隱私與合規(PII / 敏感資料)

    問題:

    • 長期記憶通常含姓名、電話、訂單資訊等 PII

    建議:

    • 設計 PII-aware schema:把 PII 拆出獨立欄位,便於加密與 masking
    • 在寫入前跑簡單的 PII 檢測 rule:
    • 例:電話號碼、email pattern 直接用工具抽出 → 存入安全欄位
    • 對查詢 API 加上角色權限(例如 owner_id+role=internal_support)

    3. 語義檢索漂移與模型更新

    問題:

    • 換 embedding 模型後,舊向量的語義分佈不同,semantic search 精度變差

    簡單防線:

    • 在向量旁存 embedding_model 欄位
    • 新模型上線後:
    • 新增資料用新 embedding
    • 舊資料分批 re-embed,或只重算活躍 Facts
    • 用 offline evaluation:定義一組標準 query + expected hits,監控 semantic search 成效

    4. 不同模型對同一紀錄的解讀不一致

    問題:

    • Model A 把某次對話解讀為「使用者喜歡簡訊通知」,Model B 覺得是「偏好 email」

    建議:

    • 把「推論」與「事實」分開存:
    • Fact:明確的、可驗證的偏好(使用者自行設定)
    • Inference:模型的猜測,需經多次交叉驗證或人工確認才升級為 Fact
    • 在 prompt 中區分:
    • 「已確認偏好」 vs. 「推測偏好」

    結語:把長期記憶升級成基礎設施

    對成熟的 AI 專案來說,長期記憶不再是「多塞幾個向量」的 hack,而是需要設計、版本與治理的系統。利用像 akitaonrails/ai-memory 這種以 Rust 實作的開源方案,你可以:

    • 為所有 Agent 建立一個 供應商無關、可演化的記憶層
    • 清楚區分 Events / Sessions / Facts,同時用語義與結構化查詢做精準檢索
    • 以 HTTP / FFI / IPC 等方式,在不同語言與框架中重用同一記憶庫

    這些設計一開始多花一點功夫,換到的是更穩定的成本、更易維護的架構,以及在多 Agent、多模型環境裡,真正能「記住」使用者與業務脈絡的系統。

    🚀 你現在可以做的事

    • 到 GitHub 搜尋並閱讀 akitaonrails/ai-memory 專案原始碼與文件
    • 在現有 Agent 專案中先導入 Events / Sessions / Facts 基本 schema,重構記憶寫入流程
    • 實作一個簡單的 HTTP memory server,讓至少兩個不同語言或框架的 Agent 共用同一記憶層
  • RAGFlow 實戰:從 PDF 到多代理助理

    RAGFlow 實戰:從 PDF 到多代理助理

    📌 本文重點

    • RAGFlow 把各種文件變成可查詢的智慧助理
    • 支援 RAG + 多 Agent 工作流,一站式整合
    • 易於部署與接入現有系統,適合團隊內部知識應用
    • 可從簡單問答慢慢擴展到會執行任務的 AI 助理

    RAGFlow 解決的問題很直接:把「一大堆文件、網頁」變成「會自己查資料、自己動手執行的 AI 助理」。

    專案連結:https://github.com/infiniflow/ragflow


    核心功能:RAG + Agent 一站搞定

    1. 資料接入與向量化:丟文件就能問

    RAGFlow 的第一層就是把各種原始資料變成「可被 LLM 搜尋理解」的向量索引。

    你可以實際做的事:

    • 支援來源大致可分:
    • 文件:PDF、Word、Markdown、純文字
    • 網頁:URL 抓取、站內內容同步
    • 結構化內容:CSV、JSON(做報表型問答很實用)
    • 常見流程:
    • 在 RAGFlow 後台建立一個 Knowledge Base
    • 上傳公司工程文檔 / 產品手冊 / SOP PDF
    • 設定切分規則(例如:依段落、標題分 chunk)
    • 選擇 Embedding 模型(內建或外部,像是 Hugging Face 上的中文向量模型)
    • 建立索引(系統會自動向量化並存入向量庫)

    這一步做完,你就等於有了一個「會理解語義」的知識庫,而不是只能關鍵字搜尋的檔案櫃。

    💡 關鍵: 透過向量化與語義搜尋,文件從「只能關鍵字查」升級成「能用自然語言問問題」的智慧知識庫。

    2. 檢索層設計:不只是「找幾段文字」

    RAGFlow 把檢索層做得比較「工程化」,可以細調:

    • 索引與向量庫選擇:
    • 內建向量引擎
    • 或接外部向量庫(如 Milvus、PgVector 等)
    • 檢索策略可調:
    • Top-K(一次取幾段)
    • 相似度門檻(不夠像就不要給模型亂猜)
    • 多輪查詢(先廣泛找,再縮小範圍)

    你可以做的調整動作:

    • 對「工程文檔」類知識庫:
    • chunk 設大一點(保留上下文),例如 800–1000 字
    • Top-K 調高(例如 8–10),避免漏掉關鍵步驟
    • 對「QA 知識庫」類資料:
    • chunk 設小一點(每題一段)
    • Top-K 只取 3–5,讓回答更集中

    這樣調完,LLM 拿到的上下文就更乾淨,回答會明顯少犯「自己亂編」的錯。

    💡 關鍵: 調整 chunk 大小與 Top-K 是降低幻覺、提升回答準確率的核心手段。

    3. 多代理協作:有人查、有人想、有人執行

    RAGFlow 的亮點是多 Agent 工作流。不是只給你一個「聊天機器人」,而是可以把任務拆給不同角色:

    常見的 Agent 角色設計:

    • Retriever Agent:只負責根據問題去知識庫檢索
    • Summarizer Agent:把檢索結果整理成可閱讀摘要
    • Planner Agent:拆解任務步驟(例如:先查產品規格,再產出比較表,最後寫結論)
    • Executor Agent:真的去調 API、寫入工單、更新系統

    你可以在 RAGFlow 裡做的事情:

    1. 建立一個 Workflow,指定多個 Agent 節點
    2. 設計流程圖:
    3. 使用者提問 → 檢索 Agent → 摘要 Agent → 回答 Agent
    4. 對客服場景:回答 Agent 判斷是否需建立工單 → 如果需要,丟給 Executor Agent 調用工單系統 API

    這種拆分的好處是:每個 Agent 的 System Prompt 可以很精準,調整也比較容易觀察哪一段出問題。

    💡 關鍵: 多 Agent 拆分角色,讓「查資料、思考、執行」三件事各自優化,比單一聊天機器人穩定得多。


    適合誰用:三個典型場景

    1. 團隊知識庫問答:工程文檔 / 產品手冊 / SOP

    適合情境:

    • 軟體團隊:想讓新人直接問「部署步驟」「API 使用範例」
    • 硬體 / SaaS 產品:希望售前售後都能秒查產品規格、授權規則

    可實際做的:

    • 建立一個「Internal KB」
    • 匯入:
    • Git repo README、Wiki 轉成 PDF
    • 內部 SOP、操作手冊、Onboarding 文件
    • 建立一個 QA Agent:只允許根據知識庫回答,不會自己瞎編超出範圍的內容

    2. 客服與支援 Bot:先查知識庫,再動手開工單

    適合情境:

    • 官網線上客服、App 內支援中心
    • B2B 工單系統前台

    可以設計的流程示例:

    1. User 問:帳號、付費、故障
    2. RAG Agent:
    3. 先查 FAQ / 幫助中心 / 條款文件
    4. 回覆解法
    5. 評估是否要升級:
    6. 若使用者多次追問或提到「不能用」「錯誤碼」,交給 Executor Agent
    7. Executor Agent 呼叫工單 API,建立 Ticket,回傳工單編號

    這樣你得到的不是一個「只能回 FAQ 的機器人」,而是能接著做事情的支援助理。

    3. 數據 / 報告助理:多來源文件 → 週報 / 簡報框架

    適合情境:

    • PM、營運:每週要整理多份報表、數據截圖、訪談記錄
    • 顧問 / 分析師:客戶給你一堆 PDF 報告,要快速產出簡報大綱

    可操作的工作流範例:

    1. 上傳:
    2. Google Analytics / Amplitude 匯出報表(CSV)
    3. 各部門週報(PDF/Docx)
    4. 客戶訪談逐字稿
    5. Workflow 設計:
    6. 檢索 Agent:依照「本週」「產品 A」「轉換率」等關鍵詞找資料
    7. 摘要 Agent:對各來源做重點整理
    8. 報告 Agent:產出「本週成效總結 + KPI 變化原因 + 下週建議行動」框架

    你可以在此基礎上,讓 Executor Agent 直接生成 Markdown / PPT 大綱,甚至丟到自家文檔系統。


    怎麼開始:從部署到接入自家系統

    1. 用 Docker 一鍵部署 RAGFlow

    先準備一台機器(本機或雲端均可):

    • 建議配置:
    • CPU 4 核以上
    • RAM 8GB 以上
    • 有 GPU 更好(但非必須)

    以 Docker Compose 為例(以官方 README 為準):

    # 取得專案
    git clone https://github.com/infiniflow/ragflow.git
    cd ragflow
    
    # 啟動(實際指令以官方文件為準)
    docker compose up -d
    

    啟動後:

    • 打開瀏覽器訪問類似 http://localhost:port 的管理介面(依官方說明)
    • 建立管理帳號

    官方專案與更新請看:https://github.com/infiniflow/ragflow

    2. 接一個免費 / 便宜 LLM

    RAGFlow 可以接:

    • 本地部署開源模型(透過 Ollama / vLLM 等)
    • 或雲端 API(OpenAI、阿里通義、字節、百度等)

    如果你想先用開源模型:

    • 選擇方向示例:
    • Qwen 系列(中文友好)
    • Gemma / Gemma 2(Google 開源,英文較強)
    • Nemotron / Nemotron Lightning(適合雲端部署,高效推論)

    實作步驟:

    1. 在 RAGFlow 的 LLM 設定頁,新增模型提供者
    2. 如果用雲端 API:填入 API Key、模型名稱(如 qwen-turbo)
    3. 如果接本地:填寫本地推論服務的 URL(例如你用 vLLM 啟的 endpoint)

    測試方式:在介面裡直接試打一段文字,看能否正常回應。

    3. 走一個 End-to-End 範例:PDF → Agent Workflow

    假設你要做一個「公司內部 SOP 助理」。

    1. 建立知識庫
    2. 在後台新增 Knowledge Base:company-sop
    3. 上傳:人資流程 SOP、報銷流程、IT 報修流程(PDF/Word)
    4. 設定:

      • chunk 大小 500–800 字
      • 選一個中文向量模型(如 bge-large-zh 類)
      • 建立索引
    5. 建立簡單 Agent 工作流

    6. 建立一個 Workflow:sop-assistant
    7. 節點配置:
      • 節點 1:User Input
      • 節點 2:Retrieval Agent
      • 指定使用 company-sop 知識庫
      • 節點 3:Answer Agent
      • System Prompt 例:
        > 你是公司內部 SOP 助理,只能根據提供的 SOP 文件回答。若文件中沒有相關內容,請明確說「文件中沒有這部分說明」並建議聯絡負責部門。
    8. 連線:User Input → Retrieval Agent → Answer Agent → 輸出

    9. 測試互動

    10. 在 Workflow 測試頁輸入:
      • 「新人報到第一天要做哪些流程?」
      • 「海外差旅報銷要準備什麼文件?」
    11. 檢查:Answer Agent 回覆中是否有明確指出 SOP 條款、步驟,必要時微調 chunk / Top-K。

    4. 用 REST API 接入自家網站 / 工具

    完成 Workflow 之後,你可以透過 HTTP 對它發問,嵌入到產品裡。

    典型 API 互動(偽例,請依官方文件實際調整路徑):

    POST http://your-ragflow-host/api/workflows/sop-assistant/run
    Content-Type: application/json
    
    {
      "input": {
        "query": "我要怎麼申請筆電維修?"
      }
    }
    

    後端會回一個 JSON,其中包含:

    • 模型回答內容
    • 可能還有使用到的文件片段(方便你在前端顯示「引用來源」)

    實際接入建議:

    • 官網 / 內部系統前端:做一個 Chat UI,後端把使用者輸入轉發到 RAGFlow API
    • 內部 Bot(如 Slack、Teams):寫個小 Bot,把訊息丟給 Workflow API,再把回答貼回頻道

    補充:RAGFlow、向量庫與模型框架的關係

    要把整套系統看清楚,可以把角色分成三層:

    名稱 核心功能 免費方案 適合誰
    RAGFlow (https://github.com/infiniflow/ragflow) RAG + 多 Agent 工作流、資料接入、檢索與 LLM 編排 開源自架 想做完整 RAG 應用、需要工作流與 API 的團隊
    向量資料庫(如 Milvus、Postgres + pgvector) 儲存與檢索向量,支援語義搜尋 多數有開源版 有大量文檔 / 多模態資料,要穩定、高效向量檢索的團隊
    Transformers (https://github.com/huggingface/transformers) 提供各類 LLM / embedding 模型與推論框架 開源 需要自行訓練 / 微調模型、客製化 NLP 任務的工程師

    可以簡單理解為:

    • Transformers 負責「模型」
    • 向量庫負責「記憶」
    • RAGFlow 把兩者接在一起,幫你做「會查資料又會動手的 AI 助理」

    想快速上手 RAGFlow,最實用的路徑就是:一台機器 + Docker、一個便宜或免費的 LLM API、一疊 PDF,先做出一個真的能回答問題的內部助理,再慢慢加上更多 Agent 和外部 API,讓它從「會回答」進化到「會幫你做事」。

    🚀 你現在可以做的事

    • 到 GitHub 下載 RAGFlow 專案並用 docker compose up -d 在測試機器上啟動
    • 準備一疊內部 SOP / 產品手冊 PDF,上傳到新建的 Knowledge Base 做第一版助理
    • 在 RAGFlow 裡配置一個簡單 Workflow,然後用自家網站或 Slack Bot 透過 REST API 接入試跑
  • 把技術書變成 Claude 助教

    把技術書變成 Claude 助教

    📌 本文重點

    • book-to-skill 把技術 PDF 變成 Claude 專屬助教
    • 透過向量搜尋,讓 Claude 有「依據地翻書」
    • 最適合工程師、考證照與公司內規查詢
    • 先選一本常用技術書技能化,再擴展到更多書

    一句話先講白:book-to-skill 能把一本厚厚的技術 PDF,變成你在 Claude 裡隨叫隨到的專屬助教,查手冊、問觀念、看範例都靠它。

    專案連結:https://github.com/virgiliojr94/book-to-skill


    核心功能:怎麼把「書」變成「技能」?

    book-to-skill 目標很單純:讓 Claude 能像熟讀那本書的助教一樣,回答你問題。背後大致分三步:

    💡 關鍵: book-to-skill 的核心價值是讓 Claude「有書可依」,回答都能對應到原書具體章節,而不是憑空生成


    1. 從 PDF 抽取結構化內容

    行動重點:先準備「可選字」的 PDF(不是純掃描圖片)。

    book-to-skill 會做的事:

    • 解析目錄、章節標題,切成「小段知識塊」(chunks)
    • 保留章節層級(例如:第 3 章 / 3.2 / 3.2.1),讓 Claude 知道上下文
    • 盡量區分:
    • 正文說明
    • 程式碼區塊
    • 小節標註(例如「Tip」「Warning」)

    這一步的效果:你問一個問題時,Claude 能直接定位該書第幾章、第幾節的內容來回答,而不是憑空亂講。


    2. 建立向量資料庫,讓 Claude 精準「翻書」

    行動重點:在執行流程前,要有一組向量資料庫(一般內建用本地或雲端向量引擎)。

    在 book-to-skill 裡,它會:

    • 把每個「知識塊」丟給嵌入模型(embedding)轉成向量
    • 存進向量資料庫
    • 之後你問問題時,用語義搜尋找出最相關的幾段內容

    簡單理解:你問問題 → 書中相關段落被找出 → Claude 再根據這些段落回答。這樣 Claude 的回答就「有根據」,不再只是通用知識。


    3. 生成 Claude 專用 skill:問答 / 解說 / 摘要

    book-to-skill 的最後一步,是幫你產生一個可在 Claude 中使用的「技能」(skill),裡面已經寫好:

    • 這個 skill 的用途:
    • 針對書內內容回答問題
    • 幫你做重點整理
    • 把書裡的概念轉成實作範例
    • 如何引用書中內容:
    • 優先用向量資料庫找到的段落
    • 如果找不到,就誠實說「這本書沒提」,不要亂掰
    • 回答格式:
    • 可以附上「出自第幾章、第幾節」
    • 可以整理成步驟、表格或程式碼範例

    行動重點:

    • 你完成轉換後,會得到一個可以在 Claude Code / Skills 面板中載入的 skill
    • 之後在任何對話裡打開這個 skill,就像多了一位「專門只看過這本書」的助教

    💡 關鍵: skill 的規則會強制 Claude「找不到就說沒提」,大幅降低亂掰與幻覺風險


    適合誰用:三種典型場景

    book-to-skill 最實用的地方是,把「一本大家都該看的技術書」,變成「團隊共享的 AI 助教」。以下三個最常見的用法:


    1. 工程師寫程式時查框架手冊

    場景:

    • 例如 Spring Boot 官方指南、Django 文件、Kubernetes 手冊
    • 你在寫程式,突然忘了某個 annotation 或 config 怎麼寫

    用法:

    • 把官方 PDF 或整理好的手冊丟進 book-to-skill
    • 在 Claude 裡問:
      -「依照這本書,@Transactional 在這種情境要注意什麼?」
      -「照書上的做法改寫下面這段程式碼。」

    效果:比直接問 Claude 靠譜,因為它會基於那本書的版本說明,不會混入網路上別的框架版本的寫法。


    2. 考證照:題目解析 + 重點複習

    場景:

    • AWS / GCP / Cisco / 資安證照
    • 有一本官方考試指南 PDF

    用法:

    • 先把官方指南變成 skill
    • 丟一題題目進 Claude,指示:
      -「用這本書的內容解析這題,並指出相關章節。」
      -「請依照書的架構,整理出本章考點清單。」

    效果:

    • 不只是出答案,而是帶你回到書裡的原文解釋
    • 讀題 → 回書本 → 再回題目,形成一個穩定的複習 loop

    3. 團隊共用標準文件:把 PDF 內規變成「制度客服」

    場景:

    • 公司有一本厚厚的「開發標準」「資安政策」「客服流程手冊」 PDF
    • 新人加入時,最常問:「這個情境要照哪條規範?」

    用法:

    • 把這份內規 PDF 轉成 skill
    • 在 Claude 裡問:
      -「依照公司內規,如果客戶資料要保留超過 1 年,需要哪些批准?」
      -「這段 SOP 是否符合本書的流程條件?」

    效果:

    • 變成一個24 小時不會累的規範客服
    • 即使文件沒人想翻,也能透過 Claude 查到正確條目

    💡 關鍵: 把「沒人願意翻的大部頭 PDF」轉成可問答的制度客服,是提升新手上手速度的低成本方法


    怎麼開始:一步一步把第一本書「技能化」

    這裡以最常見的情境:在本機跑 book-to-skill,然後在 Claude 中使用。流程大致三步:


    步驟 a:準備一份技術 PDF

    行動清單:

    1. 選一本你真的會常查的書,例如:
    2. 框架官方手冊
    3. 證照官方考試指南
    4. 公司內部規範 PDF
    5. 儘量選:
    6. 可選取文字的 PDF(非掃描圖片)
    7. 章節結構清晰,有目錄

    常見坑:

    • 純掃描 PDF:book-to-skill 需要 OCR 才能讀;如果原專案沒有整合 OCR,你要先用別的工具(如 OCR 軟體)轉成可選字的 PDF 再丟進來。
    • PDF 內嵌字型怪:解析時可能出現亂碼,建議先開啟確認文字是否正常。

    步驟 b:在本機或雲端部署 book-to-skill

    行動清單(以本機為例):

    1. 安裝必備環境:
    2. Python 3.x
    3. pip / uv / conda 任選一種套件管理
    4. Clone 專案:
    git clone https://github.com/virgiliojr94/book-to-skill.git
    cd book-to-skill
    
    1. 安裝依賴:
    pip install -r requirements.txt
    # 或依照 repo 說明使用對應工具
    
    1. 設定 API Key:

    2. 到 Anthropic 建立 API key

    3. 新增 .env(或依 repo 說明)填入:
    ANTHROPIC_API_KEY=你的_API_key
    

    常見坑:

    • 忘記設定地區 / 帳號權限,導致模型呼叫被拒
    • API key 放進 git repo:請務必把 .env 加入 .gitignore

    步驟 c:跑完一次轉換流程

    大致流程(名稱依實際 repo 為準):

    1. 執行轉換命令:
    python book_to_skill.py \
      --pdf path/to/your_book.pdf \
      --output ./skills/your_book_skill
    
    1. 轉換過程會:
    2. 解析 PDF → 切成章節 chunks
    3. 建立向量資料庫
    4. 生成對應的 Claude skill 定義檔(通常是 JSON / YAML)

    5. 完成後,你會得到:

    6. 一個可在 Claude 中註冊的 skill 定義
    7. 相關的向量資料檔案

    程式碼與公式處理注意:

    • 程式碼區塊:
    • 有些 PDF 把 code 斷在奇怪地方;轉換後可以抽查幾段,看縮排是否錯誤
    • 公式:
    • 以純文字形式存進向量庫,適合解釋概念,但不適合做精確 LaTeX 排版

    步驟 d:在 Claude 中載入並實際操作

    以「Claude Code + Skills」為例,操作大致如下(依當前產品 UI 為準):

    1. 打開 Claude Code → 找到 Skills / Plugins 管理頁面
    2. 選擇「匯入技能 / 自訂 skill」
    3. 指定 book-to-skill 生成的 skill 定義檔
    4. 啟用後,你可以:
    5. 在任何對話中切換到這個 skill
    6. 用自然語言指示:
      -「接下來所有解釋,都請只根據這本書。」
      -「請用書中的說明,幫我把這段程式碼改成推薦寫法。」

    常見使用習慣建議:


    book-to-skill 與一般 Claude 插件的差異

    如果你已經在看 Claude 插件市場,可能會有這個疑惑:「這算 plugin?skill?connector?」

    根據 Towards AI 的實測文章(The Only 11 Claude Plugins You Need in 2026),生態圈的命名確實有點亂。

    用一張表整理 book-to-skill 在這個世界觀裡的位置:

    名稱 核心功能 免費方案 適合誰
    book-to-skill 把 PDF 技術書轉成 Claude skill + 向量搜尋 開源,自架需 API 想把特定技術書變成助教的工程師 / 團隊
    一般 Claude 插件(plugin) 擴充 Claude 能力,如連結 Git、DB、工具 多數有免費層級 想接外部服務、做自動化工作流的使用者
    CLAUDE.md(設定檔) 定義專案規則、上下文、風格 內建免費 希望 Claude 像熟悉專案的隊友的開發者

    你可以這樣理解:

    • plugin / connector:讓 Claude 接到更多外部系統
    • book-to-skill:讓 Claude 多一個「專門熟讀某本書」的大腦
    • CLAUDE.md:告訴這個大腦「你在這個專案裡要怎麼說話、怎麼做事」

    收尾:先把「一本你最常翻的書」技能化

    最後給一個最簡單的行動建議:

    1. 選 一本你這一年一定會反覆查的技術書(而不是你覺得「應該讀」但其實不會打開的那種)。
    2. 用上面的步驟跑完一次 book-to-skill 流程。
    3. 在接下來一週寫程式或讀書時,逼自己所有相關問題先問 Claude 助教,看它給不給力;遇到錯誤答案,順手標記對應章節修正 prompt。

    等你把第一本書「技能化」成功,第二本、第三本就只是在重複同一套流程。真正的差別是:

    你不再只是「收藏 PDF」,而是把它們變成每天會主動被你用到的活教材。


    🚀 你現在可以做的事

    • 先在 GitHub 打開 book-to-skill 專案,確認環境需求與使用說明
    • 從你的電腦挑出一份「這一年一定會常查」的技術 PDF,檢查是否可選字且章節清晰
    • 在本機依照文中步驟跑完一次轉換,並在 Claude 中匯入 skill,開始用這本「技能化」的書來解決實際問題
  • 本地 LLM+RAG 架構:從 NASA CMO-DA 學部署

    本地 LLM+RAG 架構:從 NASA CMO-DA 學部署

    📌 本文重點

    • 斷網環境可用本地 LLM+RAG 解決醫療輔助
    • llama.cpp 搭配容器化可在受限硬體穩定推理
    • 使用 OSS 向量庫打造可控、可回滾的 RAG pipeline
    • 模型與知識庫需分版本管理確保合規與安全

    在太空任務這種完全斷網、硬體受限且高合規的環境,NASA 的 CMO-DA 醫療助手用一套可攜、可封裝的本地 LLM+RAG 架構,解決了三個常見痛點:

    1. 不依賴雲端:模型推理與檔案檢索都在本地完成,沒有外部服務依賴。
    2. 可控資源與效能:透過 llama.cpp + 容器化(RamaLama 類工具),在 CPU/GPU 都受限的情況下仍能穩定跑推理。
    3. 安全與更新邊界清楚:醫療場景下做到知識庫可更新但可 rollback,模型版本可控且遵守合規要求。

    💡 關鍵: 在完全斷網且高合規場景中,能本地推理並可回滾的 LLM+RAG 架構是可行且可維護的方案

    下面用 NASA CMO-DA 的思路,拆成一個可直接套用的本地 LLM+RAG 模板。


    重點說明

    1. 為何選擇 llama.cpp + 容器化工具做本地推理

    在 CMO-DA 這類場景,llama.cpp 有幾個關鍵優勢:

    • 硬體覆蓋廣:支援 x86、ARM、多種 GPU(CUDA、Metal、OpenCL 等),適合未知/多樣化載具(太空艙、工廠、船艦)。
    • GGUF 模型格式:支援量化(q4_0, q5_K 等),可以在有限記憶體上跑中大型模型。
    • 單一 binary,易封裝:搭配像 RamaLama 這種工具,把模型 + 推理程式封裝成容器映像,做到「模型即容器」。

    實際好處:

    • 對你來說,部署路徑變成:git pull → cmake → 放入 GGUF 模型 → 打包成 container,不需要大堆框架整合。
    • DeepSeek V4 已合併進 llama.cpp 主幹,你可以直接在本地跑 DeepSeek V4 GGUF,在推理品質與效能上有更好的選擇。

    容器化工具(以 RamaLama 類工具為例)則負責:

    • 自動 GPU 偵測與穿透(在 Kubernetes / OpenShift 中把 GPU 資源暴露給容器)。
    • 統一啟動參數與模型路徑,讓模型像應用程式一樣可複製、可移動。

    💡 關鍵: 透過 GGUF 量化與「模型即容器」封裝,能在受限硬體上穩定部署中大型本地模型

    2. 斷網環境下的 RAG pipeline 設計

    本地 RAG 的核心是:不要依賴雲端向量服務,一切用自架的 OSS 向量庫。

    常見 OSS 選擇:

    • Qdrant:Rust 實作,支援 HNSW、瀏覽器/邊緣友好,有好用 REST / gRPC API。
    • Milvus / Weaviate:功能更完整,適合資料量非常大但資源較充裕的內網環境。

    醫療場景(也是典型高合規場景)的設計要點:

    1. Chunking 策略
    2. 以醫療指引、SOP 文件為單位,再切成 512–1024 tokens chunk,重點是維持語境完整。
    3. 加上 overlap(例如 128 tokens),避免答案跨 chunk 斷裂。
    4. 針對表格或 checklist,考慮以「row / section」為粒度,而不是固定 tokens。

    5. Embedding 模型本地化

    6. 選一個能放進 GGUF 或支援 CPU/GPU 的 embedding 模型,例如 bge-large 的量化版本或 MiniLM 類模型。
    7. 避免用雲端 embedding API,保證完全離線。

    8. 延遲與資源限制

    9. 查詢流程:Embedding → Vector search → Top-k 文件 → LLM 回答,每一步都要評估延遲。
    10. 太空艙/工廠場景中通常只有 1–2 張 GPU,LLM 推理延遲是主瓶頸,可以:
      • 調低 max_tokens 回答長度。
      • 預先計算並緩存常見問答(FAQ)以降低實時查詢負擔。

    💡 關鍵: 在離線場景中,512–1024 tokens chunk 加 128 overlap 能平衡上下文完整性與檢索效率

    3. GPU 自動偵測與穿透:Kubernetes / OpenShift 配置

    在 CMO-DA 中,他們透過類似 RamaLama 的工具,在 OpenShift 上做到:

    • 自動偵測節點是否有 GPU(透過標籤或 device plugin)。
    • 在 pod 內把 GPU 暴露給 llama.cpp。

    對你來說,可以簡化成 Kubernetes YAML 設計:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: local-llm
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: local-llm
      template:
        metadata:
          labels:
            app: local-llm
        spec:
          containers:
            - name: llama-cpp
              image: your-registry/llama-cpp-gguf:latest
              args:
                - "--model=/models/med-llm.gguf"
                - "--ctx-size=4096"
                - "--n-gpu-layers=35"  # **關鍵參數**:LLM 分配到 GPU 的層數
              resources:
                limits:
                  nvidia.com/gpu: 1      # Kubernetes GPU 資源宣告
              volumeMounts:
                - name: models
                  mountPath: /models
          volumes:
            - name: models
              persistentVolumeClaim:
                claimName: llm-model-pvc
          nodeSelector:
            nvidia.com/gpu.present: "true"  # **GPU 自動偵測:只排程到有 GPU 的節點
    

    在 OpenShift 上同樣依賴 GPU Operator / device plugin,容器內不需要特殊程式碼,llama.cpp 會透過 CUDA / ROCm 自動偵測 GPU。


    實作範例:Minimal 本地 LLM+RAG PoC

    下面是一個可離線部署的小型 RAG 助理範例,組合:llama.cpp + DeepSeek V4 GGUF + Qdrant。

    1. 準備模型與 llama.cpp

    # 取得最新 llama.cpp(含 DeepSeek V4 支援)
    git clone https://github.com/ggml-org/llama.cpp.git
    cd llama.cpp
    git pull
    cmake -B build
    cmake --build build -j
    
    # 假設你已下載 DeepSeek V4 GGUF 模型到 ./models/deepseek-v4-med.q4_0.gguf
    

    2. 啟動本地 Qdrant 向量庫

    docker run -d --name qdrant \
      -p 6333:6333 \
      -v qdrant_data:/qdrant/storage \
      qdrant/qdrant
    

    3. 建立 RAG Pipeline(Python 範例)

    import requests
    from sentence_transformers import SentenceTransformer
    
    # **Embedding 模型**:可替換為你量化後的本地模型
    embed_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
    
    QDRANT_URL = "http://localhost:6333"
    COLLECTION = "med_docs"
    
    def create_collection():
        body = {
            "vectors": {
                "size": 384,
                "distance": "Cosine"
            }
        }
        requests.put(f"{QDRANT_URL}/collections/{COLLECTION}", json=body).raise_for_status()
    
    def index_documents(docs):
        vectors = embed_model.encode([d["text"] for d in docs])
        points = [{
            "id": i,
            "vector": vectors[i].tolist(),
            "payload": docs[i]
        } for i in range(len(docs))]
        requests.put(f"{QDRANT_URL}/collections/{COLLECTION}/points", json={"points": points}).raise_for_status()
    
    def rag_search(query, top_k=3):
        vec = embed_model.encode([query])[0].tolist()
        body = {
            "vector": vec,
            "limit": top_k
        }
        res = requests.post(f"{QDRANT_URL}/collections/{COLLECTION}/points/search", json=body).json()
        return [r["payload"]["text"] for r in res]
    
    # 初始化
    create_collection()
    index_documents([
      {"text": "若出現輕微頭痛且無其他症狀,建議先休息並補充水分。"},
      {"text": "若有持續胸痛或呼吸困難,應立即啟動緊急醫療流程。"}
    ])
    
    print(rag_search("胸口痛怎麼辦?"))
    

    4. 將檢索結果交給 llama.cpp

    在本地,你可以用簡單的 HTTP wrapper 把 context 拼接給 llama.cpp 的 --prompt:

    ./build/bin/llama-cli \
      --model ./models/deepseek-v4-med.q4_0.gguf \
      --ctx-size 4096 \
      --n-gpu-layers 35 \
      --prompt "根據以下醫療指引回答問題:
    [指引1]
    若有持續胸痛或呼吸困難,應立即啟動緊急醫療流程。
    
    問題:胸口痛怎麼辦?"
    

    在正式專案中,你會把 Qdrant 的 top-k 結果串接到 prompt,組成完整 RAG 回答流程。


    建議與注意事項

    1. 醫療場景的安全邊界與 offline 更新策略

    醫療、工業安全等場景,關鍵是 模型與知識庫的版本分離:

    • 模型(LLM)版本:透過容器映像管理,例如在 tag 中寫明版本:med-llm:v1.2.0。
    • 知識庫(向量庫 + 原始文件)版本:在 Qdrant collection 命名中加入版本:med_docs_v2024_06。

    更新策略建議:

    1. 離線同步:
    2. 透過 USB / 專用線路將新模型(GGUF)與新向量庫 dump 帶到現場。
    3. 先在 staging 節點載入,跑自動化驗收測試(醫療 QA 集)。

    4. Rollback 機制:

    5. 保留上一版模型映像與向量庫快照,決策錯誤時可以在幾分鐘內回退。
    6. 配置開關:只允許在非緊急任務時切換版本,避免任務中途變更行為。

    2. 常見坑與最佳實踐

    1. 坑:只量化模型但忘記量化記憶體 footprint
    2. 即使用 q4_0,context 太大(例如 32k)時仍可能爆 RAM。
    3. 建議:先用 --ctx-size=4096 做壓力測試,再逐步拉高。

    4. 坑:Chunk 太小導致回答失去上下文

    5. 斷網場景下沒有「多輪 call retriever 補充」的餘裕,chunk 要適度放大。
    6. 建議:醫療/工廠 SOP 至少維持 512–1024 tokens + 128 overlap。

    7. 坑:GPU 穿透配置錯誤導致 fallback 到 CPU

    8. 沒有設 resources.limits.nvidia.com/gpu 或節點沒正確標記,pod 會跑在無 GPU 節點上。
    9. 建議:在 CI/CD 中加入簡單檢查:呼叫 nvidia-smi 或 llama.cpp GPU info API,確認有 GPU 再部署。

    10. 最佳實踐:在高合規場景用「白名單指令 + RAG」模式

    11. 不允許模型「自由想像」,回答必須引用 RAG 檢索到的片段。
    12. prompt 設計中加入:「只能根據提供的文件回答,若無相關資訊請回答『資料不足』」,降低幻覺風險。

    總結來說,NASA CMO-DA 的做法給了我們一個清晰模板:llama.cpp + 容器化 + OSS 向量庫 + 可控版本策略。只要把這套思路搬到你的工廠、船艦、礦場或企業內網,就能在斷網或高合規環境下建立一個可維護、可升級又安全的本地 LLM+RAG 助理。

    🚀 你現在可以做的事

    • 在 GitHub 搜尋並 git clone 官方 llama.cpp 專案,編譯並測試本地推理
    • 使用 Docker 拉起 Qdrant,依照文中 Python 範例建立一個最小 RAG PoC
    • 寫一份 Kubernetes Deployment YAML,把你的 GGUF 模型封裝成「模型即容器」,並在測試環境驗證 GPU 穿透与延遲表现