地端 Agentic RAG:跟一般 RAG 差在哪、斷網怎麼上線

一句話心法
naive 與 agentic RAG 的唯一架構差異,是「檢索被一條寫死的直線呼叫,還是被一顆會迭代的控制器呼叫」。chunk / embed / 向量庫 / API 兩邊一模一樣。MCP 是插座,不是大腦。

重點摘要(TL;DR)

  • 唯一架構差異:naive 是一條直線(查一次→生成);agentic 多一顆會反覆決策的控制器,把檢索當工具反覆呼叫。其他元件兩邊相同。
  • MCP 是插座不是大腦:包成 MCP 只是讓 agent「叫得到」;要不要 agentic,看呼叫它的腦有沒有迭代。沒有 MCP 也能 agentic。
  • 那顆腦放哪是設計選擇:thin-tool(消費端自帶生成)vs fat-service(平台內部自帶生成+決策 loop)。
  • 斷網逼向 fat-service:進場斷網沒有外部強 agent 當腦,只能把本地 LLM(Ollama)焊進服務內部當控制器 ——「外面訓、裡面跑」。
  • 只准 443:Ollama 就是普通 HTTP 服務,用既有 Nginx 反向代理從 443 轉到內部 11434,沒特殊做法。唯一必修是把 proxy_read_timeout 拉大,否則慢模型的長答案會被 60 秒 timeout 砍成 504。
  • 別過度:約 80% 單跳查手冊 naive 就夠;只有約 20% 多跳排障才值得開 agentic loop。
🧭 本文導覽

把一份地端 RAG(向量檢索 + 生成)升級成 agentic RAG,到底要不要打掉重練?答案:是加法,不是重寫。本文以某萬人跨國製造集團的 air-gapped 廠區知識庫為情境,拆清楚 naive 與 agentic 唯一的架構差異、MCP 為何是「插座」不是「大腦」、那顆生成加決策的 LLM 該放哪,以及在「公司只准走 443、進場斷網」下怎麼把地端 Ollama 接起來。

一、naive RAG vs agentic RAG:唯一的架構差異

先給結論:兩者差別只有一處 —— 誰在驅動檢索。naive 是程式寫死的直線;agentic 是一顆 LLM 控制器在決策。

元件 naive agentic 差異
chunk + embed 完全一樣
向量庫 ✅(可能多庫/多源) 幾乎一樣
檢索(top-k) 單發 可被呼叫多次 同一個函式
生成 LLM(G) 一次 一次(在 loop 尾) 同一顆模型
控制器 / loop ❌ 沒有(直線) ✅ 有(決定→迭代) ⭐ 唯一的架構差異
API / MCP 可有可無 可有可無 正交,與 naive/agentic 無關
  • naive:問題 → embed → 向量庫 top-k → 塞 prompt → LLM 生成(帶出處)→ 答。走一次,不回頭。
  • agentic:上面那套檢索當成一個工具,上面多一顆 LLM 控制器反覆決定「要不要再查、查什麼、夠了沒」,最後才生成。
  • 升級成本是加法:naive → agentic 只是外面包一個決策函式 + 迴圈,embed / retrieve 一行不動。不是重寫。

判準:單跳問題(「A 規格 vs B 規格怎麼分」)一次檢索就答完 → naive。多跳問題(「這個異常 → 可能原因 → 對應處置」要翻兩三本手冊)一次檢索答不全 → 才需要 agent。

二、MCP 是「插座」,不是「大腦」

普通 REST API,LLM agent 不會自己知道怎麼呼叫。MCP 把你的檢索「自我描述」給 LLM(有個工具、參數是什麼、做什麼),agent 才叫得動 —— 這是插座

但插座不強制 agentic:同一個 MCP tool,agent 可以只按一次(等於 naive),也可以反覆按(agentic)。是按的那顆腦有沒有跑迭代決定 agentic,不是 MCP。反例:你可以有完全不用 MCP 的 agentic RAG(loop 就是一段程式);也可以有 MCP 包好但只被叫一次的 naive 用法。

三、那顆腦放哪:thin-tool vs fat-service

thin-tool fat-service
平台提供 R(檢索)+ 可選 A(格式化 context) R + A + G + 決策 loop 全包
生成 / 控制器在哪 消費端自帶(外部 agent) 平台內部(本地 Ollama)
呼叫端要多強 要是個強 agent 可以是笨前端 / 一行 curl
適合 有網、消費端本來就強 斷網、消費端很笨
代價 平台輕快;智力外包 平台變重、每次呼叫可能多輪;智力負載壓平台

fat-service 的殺手級性質:決策腦焊死在平台內部,不管誰呼叫、怎麼呼叫,答案水準一致 → 品質可保證、斷網自足、呼叫端極簡。

四、斷網 → 只能 fat-service

  • thin-tool 的大腦是外部 agent(在外網)→ 進場斷網就沒腦,agentic 直接死。
  • fat-service 的大腦(本地 LLM)包在服務內部 → 整包搬進場,斷網照樣自走 loop。這就是「外面訓、裡面跑」的裡面那顆自走的腦。

五、算力分層 + air-gap 拓撲約束

Ollama 本來就是 HTTP 服務(11434 埠),放哪台跟服務層無關,客戶端把 OLLAMA_HOST 指過去即可,零改碼:

[服務層:輕、CPU]                        [推論層:重、GPU]
 · agent loop(決策)         ──內網 HTTP──►  Ollama(本地大模型 + embedding 模型)
 · 向量庫 + 檢索
 · MCP 端點
  • 一台 GPU 可餵多個瘦服務節點 → 對「集中一台 GPU、各處放瘦服務」友善。
  • ⚠️ air-gap 拓撲:服務機 + GPU 機都必須在同一個隔離網段內;GPU 機不可放外面或雲(那就破 air-gap)。各廠若各自隔離 → 每個隔離域一台 GPU,不可跨域共用。
  • ⚠️ 分層不增加算力:拆機只讓服務不跟推論搶 CPU;GPU 的吞吐天花板不變,多節點打同一台仍在 GPU 排隊。

六、只准 443:Nginx 反向代理(唯一必修 = timeout)

公司只准走 443、既有服務都用 Nginx 從 443 轉下來 —— Ollama 同一套 pattern,沒有特殊轉拋。對外 443 → 內部 127.0.0.1:11434:

location /ollama/ {
    proxy_pass http://127.0.0.1:11434/;   # 尾斜線:剝掉 /ollama/ 前綴再轉

    proxy_http_version 1.1;
    proxy_set_header Host $host;

    # ⚠️ 必修:大模型慢 + agentic 多輪,長答案會超過預設 60s → 504。拉大。
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;

    # 之後若改 streaming(stream:true)才需要:
    proxy_buffering off;

    # Ollama 預設零認證 —— 最低限度鎖一個 token
    if ($http_x_ollama_token != "<SECRET>") { return 403; }
}

三個坑:

後果
proxy_read_timeout 預設 60s 大模型長答案跑超過 60s → Nginx 切斷回 504 拉到 600s(必修)
proxy_buffering 開著 改 streaming 後 token 被憋住不即時吐 串流時 off
Ollama 零認證 掛 443 後任何連得到的人能白嫖 / 打爆 GPU Nginx 層加認證

七、agentic loop 骨架(約 30 行,套現有檢索,核心不動)

def decide(q, scratchpad):
    got = "\n".join(f"- {c['title']}" for c in scratchpad) or "(還沒查任何東西)"
    prompt = (f"問題:{q}\n目前已查到:\n{got}\n\n"
              "決定下一步。資訊不夠→ 回 search + query;已足夠→ 回 answer。只回 JSON。")
    d = _post("/api/chat", {"model": GEN_MODEL, "stream": False, "format": "json",
                            "messages": [{"role": "user", "content": prompt}]})
    try:
        return json.loads(d["message"]["content"])
    except Exception:
        return {"action": "answer"}          # 護欄:解析壞 → 退化成 naive 單發

def agent_answer(q, max_steps=4):            # 護欄:硬上限,防無限迴圈
    chunks = load_index()
    scratchpad, seen = [], set()
    for _ in range(max_steps):
        step = decide(q, scratchpad)
        if step.get("action") != "search":
            break
        for c in retrieve(step["query"], chunks, k=3):   # 原本的檢索,沒改
            key = (c["source"], c["title"])
            if key not in seen:
                seen.add(key); scratchpad.append(c)
    return generate_final(q, scratchpad)     # 幾乎就是原本的生成

三護欄:max_steps 硬上限、decide 的 JSON try/except、壞掉退化成 naive。別為此拉重框架(LangGraph / LlamaIndex):斷網場手刻這 30 行剛好,可控、可離線、每行能解釋。

八、結語:先做簡單版,別在承重牆上炫技

「agentic」不是更聰明的檢索,是一顆會決定「要不要多查、查哪本、查夠沒」的外殼。它跟你的向量庫、embedding、API 都無關 —— 那些兩邊一模一樣。

所以先做 naive 完全正確,別為了潮上 agentic。等真出現「一次檢索答不出來」的多跳問題,再把那顆會迭代的腦加上去 —— 加法不是重寫。

而斷網場的關鍵,是那顆腦不能借外面的,得焊進自己服務裡:外面訓、裡面跑。只准 443 也不特別 —— Ollama 就是個 HTTP 服務,Nginx 照舊轉,只記得把 timeout 拉大。

延伸閱讀

留言

在〈地端 Agentic RAG:跟一般 RAG 差在哪、斷網怎麼上線〉中有 1 則留言

  1. […] 採購 Agent 實戰:逼 Agent 真去查價、地端 Agentic RAG:斷網怎麼上線、真實手冊 RAG 一日實戰:15 […]

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *