iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
AI Engineering

RE: 從 4,343 筆職缺到 AI Engineer:MLOps × GenAI Engineering 雙主軸實戰系列 第 22 篇

Day 22:Agent(二) — MCP、多代理與可靠性護欄

  • 分享至 

  • xImage
  •  

昨天手刻了 ReAct 迴圈,也給了兩個護欄(迭代上限、成本上限)。今天處理讓它能真正上線的三件事:工具怎麼接得可維護、多代理什麼時候值得、怎麼確保它不會闖禍。

今天要解決的問題

問題一:整合成本的乘法。 你有 3 個 AI 應用(客服機器人、內部助理、程式助理),要接 8 個內部系統(訂單、庫存、工單、CRM……)。傳統做法是每個應用各自實作一次整合——3 × 8 = 24 份要維護的整合程式碼,而且每份的參數格式、錯誤處理都不一樣。

問題二:代理闖禍。 一個能寄信的代理,在某次執行中把內部成本表寄給了外部廠商。事後查日誌,只看到「呼叫了 send_email」,不知道為什麼它決定這樣做。

這兩個問題,一個關於架構,一個關於責任。

📊 職缺訊號

Agent 相關任務出現在 47.6% 的生成式 AI 職缺中。但 JD 的層次差異很大:初階寫「串接 LLM 與內部 API」,資深寫「設計 agent 的權限邊界與可觀測性」。今天的內容就是那條分界線。


一、現象:MCP 把 N×M 變成 N+M

Model Context Protocol(MCP)是 2024 年底提出的開放協定,解決的正是問題一:

image

圖 22-1:MCP 如何降低整合成本(示意架構)。每個系統只需實作一次 MCP Server,任何支援 MCP 的應用都能使用。

MCP 定義了三種可以被暴露的東西:

概念 是什麼 例子
Tools 模型可以呼叫的動作 lookup_order、create_ticket
Resources 模型可以讀取的資料 檔案內容、資料庫查詢結果
Prompts 預先寫好的提示範本 「分析這張工單的緊急程度」

對台灣企業的實際意義:如果你們有一套用了十幾年的 ERP,實作一次 MCP Server 之後,未來所有 AI 應用都能用同一個介面接它——而不是每個新專案都重寫一次整合。

"""一個最小的 MCP Server(訂單查詢)。"""
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("order-system")

@mcp.tool()
def lookup_order(order_id: str) -> dict:
    """查詢訂單的出貨狀態與物流編號。

    當使用者詢問訂單進度、何時送達、物流狀態時使用。
    """
    order = erp.get_order(order_id)          # 接你既有的系統
    if not order:
        return {"error": "查無此訂單"}
    return {"status": order.status, "tracking": order.tracking_no}

if __name__ == "__main__":
    mcp.run()

⚠️ MCP 不會自動解決權限問題

MCP Server 的工具依然要自己做權限檢查。實務上要在協定之外,建立呼叫者身分傳遞的機制(例如以 token 帶入使用者身分),否則你只是把一個沒有權限控制的 API 換了個介面暴露出去——而且暴露給更多應用。

二、原理:多代理的真實失敗模式

多代理系統(一個代理拆任務、多個代理各司其職)聽起來很美,但實務上有四種常見的失敗:

失敗模式 症狀 根因
傳話遊戲 資訊經過三個代理後失真,最終答案與原始需求無關 每次傳遞都是一次有損壓縮
成本爆炸 一次請求花了 40 次 LLM 呼叫 代理之間反覆確認、互相委派
責任真空 出錯了不知道是哪個代理的問題 沒有端到端的 trace
無限委派 A 叫 B 做、B 覺得該 A 做,來回踢皮球 沒有明確的終止條件

實務建議:多代理的門檻應該設得比你想的更高。

只有當「不同子任務需要明顯不同的工具集或系統提示,且放在同一個代理裡會互相干擾」時,才值得拆成多代理。

「因為架構圖比較好看」不是理由。

三、動手:五道可靠性護欄

Agent 可靠性工程全景

圖 22-2:Agent 可靠性工程的五道護欄(示意架構)——迭代上限、成本上限、沙箱、審批關卡與 trace 在系統中的位置。

昨天實作了前兩道,今天補完後三道。

護欄三:動作分級與人工審批

"""guard.py —— 依「可逆性」與「影響範圍」把動作分級。"""
from enum import Enum

class RiskLevel(Enum):
    READ = 1        # 唯讀:查詢、搜尋 → 直接執行
    WRITE_OWN = 2   # 寫入自己的資料:建立草稿、加註記 → 直接執行但記錄
    WRITE_SHARED = 3  # 寫入共享資料:更新工單、改狀態 → 需確認
    EXTERNAL = 4    # 對外:寄信、呼叫外部 API → 必須人工審批
    DESTRUCTIVE = 5 # 不可逆:刪除、退款、下單 → 必須人工審批 + 二次確認

TOOL_RISK = {
    "search_kb": RiskLevel.READ,
    "lookup_order": RiskLevel.READ,
    "add_note": RiskLevel.WRITE_OWN,
    "update_ticket": RiskLevel.WRITE_SHARED,
    "send_email": RiskLevel.EXTERNAL,        # ← 開頭那個事故的元凶
    "issue_refund": RiskLevel.DESTRUCTIVE,
}

async def execute_with_guard(tool_name, args, ctx):
    risk = TOOL_RISK.get(tool_name, RiskLevel.DESTRUCTIVE)   # 未知工具視為最高風險

    if risk.value >= RiskLevel.EXTERNAL.value:
        # 不直接執行,而是回傳「待審批」,讓 UI 呈現給人確認
        approval_id = await create_approval_request(
            user=ctx["user"], tool=tool_name, args=args,
            reason=ctx.get("current_reasoning", ""))   # 附上模型的理由讓人判斷
        return {"status": "pending_approval", "approval_id": approval_id,
                "message": "此動作需要您確認後才會執行"}

    result = await execute_tool(tool_name, args, ctx)
    await audit_log(user=ctx["user"], tool=tool_name, args=args,
                    risk=risk.name, result_ok="error" not in result)
    return result

TOOL_RISK.get(tool_name, RiskLevel.DESTRUCTIVE) 這一行是刻意的:未知的工具預設為最高風險。 安全設計的通則是「預設拒絕」,不是「預設允許」。

護欄四:沙箱

如果你的代理會執行程式碼或處理不可信的檔案,執行環境必須隔離:容器化執行、禁止對外網路、限制檔案系統存取、設定 CPU 與記憶體上限、執行逾時。這是 Day 12 容器知識的直接應用——又一個雙主軸交會的地方。

護欄五:可觀測性(trace)

"""observability.py —— 沒有 trace,代理就是黑盒子。"""
async def run_agent_traced(question, ctx):
    trace_id = str(uuid.uuid4())
    span = {"trace_id": trace_id, "user": ctx["user"].id,
            "question": question, "steps": [], "started_at": now()}
    try:
        for i in range(MAX_ITER):
            resp = await call_llm(...)
            span["steps"].append({
                "i": i,
                "reasoning": resp.choices[0].message.content,  # ← 為什麼這樣決定
                "tool_calls": [{"name": c.function.name,
                                "args": json.loads(c.function.arguments)}
                               for c in (resp.choices[0].message.tool_calls or [])],
                "tokens": resp.usage.total_tokens,
                "latency_ms": elapsed(),
            })
            ...
    finally:
        span["ended_at"] = now()
        span["total_tokens"] = sum(s["tokens"] for s in span["steps"])
        await save_trace(span)      # 送到 Langfuse / OpenTelemetry / 自建資料表
    return result

reasoning 這個欄位是關鍵。 開頭那個「寄錯信」的事故,如果有記錄模型的推理過程,你就會看到它讀到了 PDF 裡的某段文字然後「決定」要照做——這正是 Day 25 要談的間接提示注入。沒有這個欄位,你只會看到一行「呼叫了 send_email」,永遠查不出原因。

四、取捨:框架選型與上線檢核

框架 適合 注意
原生 SDK 手刻 單一代理、流程可控 昨天那 30 行就是全部;除錯最容易
LangGraph 需要明確的狀態機與分支 學習曲線;但狀態管理確實比自己寫穩
AutoGen/CrewAI 多代理協作的快速原型 成本與行為較難預測,上生產前務必壓成本上限
雲端 Agent 服務 想少維運 可觀測性與權限模型要先確認是否夠用

上線前的檢核清單(缺任何一項都不該上生產):

□ 迭代次數上限(建議 5–10)
□ 單次任務成本上限(超過就中止並降級)
□ 每個工具都有明確的風險分級
□ 所有 EXTERNAL 與 DESTRUCTIVE 動作有人工審批關卡
□ 工具權限以「呼叫者身分」限制,不依賴提示
□ 完整 trace(含 reasoning、工具參數、token、延遲)
□ 失敗時有降級路徑(轉人工,而不是回一個錯誤)
□ 執行不可信內容時有沙箱隔離
□ 評測集包含「代理該拒絕的請求」(Day 24–25)

4.1 給主軸一讀者:代理系統帶來的維運新問題

如果你走 MLOps 路線,代理系統會給你三個傳統 ML 服務沒有的維運挑戰:

其一:成本不可預測。 傳統推論服務每次請求的成本幾乎固定,代理不是——同一個請求可能花 3 次或 15 次 LLM 呼叫。這代表容量規劃與成本預算的方法要改:不能用「平均成本 × 請求數」,要看成本的分布與 P99。

其二:延遲極長且變異大。 代理跑完可能要 30 秒到 3 分鐘。這讓 Day 13 的同步 API 模式不再適用,要改成非同步任務 + 狀態查詢(提交任務拿到 ID、輪詢或推播結果),並且逾時設定要重新設計。

其三:trace 的資料量很大。 每次執行要記錄多輪的推理、工具參數與結果。這對日誌儲存、查詢效能與保存政策都是新的壓力——而且其中可能含個資(Day 27)。

這三點都是雙主軸交會的具體例子:它們是生成式 AI 帶來的問題,但要用 MLOps 的方法解決。

💡 一個關於「自主性」的觀念

很多人把「需要人工審批」看成 agent 不夠聰明的證據。我的看法相反:願意在正確的地方停下來等人確認的系統,才是成熟的系統。

想想真實世界的組織——新進員工也不會有無限授權。權限是隨著信任逐步擴大的,AI 系統也該如此。 先從唯讀開始,累積足夠的正確率數據後,再逐步放寬。


今日小結

  • MCP 把 N×M 的整合成本降為 N+M:每個系統實作一次 MCP Server,所有應用都能用。但它不會自動解決權限問題。
  • 多代理的四種失敗:傳話遊戲、成本爆炸、責任真空、無限委派。門檻要設得比你想的更高——只有子任務需要明顯不同的工具集時才值得。
  • 五道可靠性護欄:迭代上限、成本上限、動作分級與人工審批、沙箱、trace。
  • 動作依「可逆性與影響範圍」分五級;未知工具預設為最高風險(預設拒絕,不是預設允許)。
  • trace 一定要記 reasoning——沒有它,你只會看到「呼叫了 send_email」,永遠查不出為什麼。
  • 願意在正確的地方停下來等人確認的系統,才是成熟的系統:權限應隨信任逐步擴大。

延伸閱讀


上一篇
Day 21:Agent(一) — 能用工作流就不要用代理
下一篇
Day 23:微調 — 什麼時候該做、LoRA 的帳怎麼算
系列文
RE: 從 4,343 筆職缺到 AI Engineer:MLOps × GenAI Engineering 雙主軸實戰 共 26 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言