FDE 面試準備指南(十六):RKK 實戰——Multi-Agent 狀態管理與死鎖排除

面試官不只想聽你說「加 max_loops 限制」。
他想聽的是:你知道為什麼會死鎖、死鎖發生在哪個環節、
以及你的架構設計如何讓問題根本不會發生


面試情境

面試官: 「客戶使用 LangGraph 部署了一個階層式的 Multi-Agent 系統。Router Agent 分發任務給法務審查 Agent 和財務計算 Agent。上線後,特定的複雜查詢會導致系統 Timeout,或是多個 Agent 互相死循環呼叫。你在 Google Doc 看到對話日誌,如何定位問題?架構上如何設計 State Management 與護欄?」


一、核心問題:Multi-Agent 為什麼比 Single-Agent 更容易死鎖

Single-Agent(線性執行):

User → Agent → Tool → Tool → Answer
          ↑
     狀態簡單,只有一個執行者,
     不存在競爭條件

Multi-Agent(網狀執行):

              ┌─────────────────┐
              │   Router Agent   │
              └────────┬────────┘
               ↙               ↘
   ┌──────────────┐    ┌──────────────┐
   │  法務 Agent  │    │  財務 Agent  │
   └──────┬───────┘    └──────┬───────┘
          │                   │
          └──────┬────────────┘
                 ▼
         ┌──────────────┐
         │ Review Agent │  ← 可能再呼叫回 Router
         └──────────────┘
                 │
                 ▼ ???

死鎖發生的三個根本原因:

原因 1:循環依賴(Circular Dependency)
  Router → 法務 → Router → 法務 → ...
  沒有明確的終止條件

原因 2:全域狀態競爭(Race Condition)
  法務 Agent 和財務 Agent 同時寫入同一個 Global State
  後寫者覆蓋先寫者的結果 → 資料遺失 → 下一輪再重試 → 無限循環

原因 3:等待鏈(Wait Chain)
  法務 Agent 等財務 Agent 的結果
  財務 Agent 等法務 Agent 的批准
  → 互相等待,永遠不推進

二、系統架構:階層式 Multi-Agent 的完整設計

┌──────────────────────────────────────────────────────────────┐
│                    入口層(Entry Layer)                       │
│                                                              │
│   User Request → API Gateway → Task Queue (Cloud Pub/Sub)   │
└─────────────────────────────┬────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────┐
│                   協調層(Orchestration Layer)                │
│                                                              │
│   ┌──────────────────────────────────────────────────────┐   │
│   │                  Router Agent                        │   │
│   │                                                      │   │
│   │   ┌───────────────┐      ┌───────────────────────┐   │   │
│   │   │ Intent        │      │  Task Dispatcher      │   │   │
│   │   │ Classifier    │  →   │  (DAG-based routing)  │   │   │
│   │   └───────────────┘      └───────────┬───────────┘   │   │
│   └────────────────────────────────────┬─┘               │   │
└───────────────────────────────────────┼──────────────────┘
                                        │
                          ┌─────────────┼─────────────┐
                          ▼             ▼              ▼
              ┌───────────────┐ ┌───────────────┐ ┌──────────┐
              │  法務 Agent   │ │  財務 Agent   │ │  其他    │
              │               │ │               │ │  Agents  │
              │  [只寫自己的  │ │  [只寫自己的  │ └──────────┘
              │   Substate]   │ │   Substate]   │
              └───────┬───────┘ └───────┬───────┘
                      └────────┬────────┘
                               ▼
┌──────────────────────────────────────────────────────────────┐
│                   狀態持久層(State Store)                    │
│                                                              │
│   ┌──────────────────────────────────────────────────────┐   │
│   │  Global State (Immutable + Append-only)              │   │
│   │  ┌───────────┐  ┌───────────┐  ┌───────────────────┐ │   │
│   │  │ messages  │  │ legal_    │  │ finance_          │ │   │
│   │  │ (append)  │  │ output    │  │ output            │ │   │
│   │  └───────────┘  └───────────┘  └───────────────────┘ │   │
│   └──────────────────────────────────────────────────────┘   │
│   Cloud Memorystore (Redis) / Firestore                      │
└──────────────────────────────────────────────────────────────┘

三、死鎖診斷:如何從日誌中找到問題

面試官給你這段日誌,你怎麼看:

[14:23:01] Router  → 分派任務到法務 Agent (iteration=1)
[14:23:03] 法務    → 需要財務確認金額,呼叫 財務 Agent
[14:23:05] 財務    → 金額有法律風險,需要法務確認,呼叫 法務 Agent
[14:23:07] 法務    → 需要財務確認金額,呼叫 財務 Agent  ← 重複了!
[14:23:09] 財務    → 金額有法律風險,需要法務確認,呼叫 法務 Agent  ← 重複了!
[14:23:11] Router  → 分派任務到法務 Agent (iteration=2)  ← Router 也在循環
...
[14:24:31] TIMEOUT after 90 seconds

診斷步驟:

Step 1:計算迭代次數
  同一個 Agent 被呼叫幾次?次數一直增加 → 確認是無限迴圈

Step 2:追蹤呼叫鏈
  法務 → 財務 → 法務 → 財務
  ↑ 這是雙向等待:典型的「循環依賴」死鎖

Step 3:檢查 State
  Global State 有沒有推進?
  如果 state["legal_status"] 一直是 "pending" → State 沒有更新
  原因可能是:Agent 呼叫彼此但都沒有寫入確定的輸出

Step 4:定位根本原因
  ├── 缺乏「我已完成」的退出信號
  ├── 兩個 Agent 的任務邊界沒有清晰定義
  └── 沒有 max_iteration 護欄

四、解決策略:四個層面的設計

策略一:明確的 Agent 邊界(最根本的解法)

問題根源:法務 Agent 和財務 Agent 的職責邊界模糊
→ 法務 Agent 覺得自己需要財務確認才能完成
→ 財務 Agent 覺得自己需要法務確認才能完成
→ 誰都不敢先給出確定答案

正確設計:每個 Agent 必須能獨立完成自己的子任務

  法務 Agent 的職責定義:
  ├── 輸入:合約文本
  ├── 任務:分析法律風險,輸出風險評級(高/中/低)
  └── 輸出:{ legal_risk: "HIGH", reasons: [...] }
           ↑ 這是確定性的輸出,不依賴財務 Agent

  財務 Agent 的職責定義:
  ├── 輸入:財務條款 + 法務 Agent 的風險評級(可選)
  ├── 任務:計算財務影響
  └── 輸出:{ financial_impact: 5000000, currency: "TWD" }

  Router Agent 的職責:
  └── 收集兩者的輸出 → 綜合判斷 → 最終決策

策略二:State Reducer(防止 Race Condition)

❌ 錯誤設計:多個 Agent 覆寫同一個 Global State

  Agent A 寫入:state["result"] = "批准"
  Agent B 寫入:state["result"] = "拒絕"   ← 覆蓋了 A 的結果!
  系統看到 "拒絕" → 重新執行 A → 無限循環

✅ 正確設計:Append-only Reducer,每個 Agent 只寫自己的 substate

  Global State 結構:
  {
    "messages": [...],          ← 只能 append,不能覆寫
    "legal_output": null,       ← 只有法務 Agent 能寫這個
    "finance_output": null,     ← 只有財務 Agent 能寫這個
    "iteration_count": 0,       ← 全域計數器
    "final_decision": null      ← 只有 Router 能寫這個
  }

  Reducer 規則:
  ├── messages:新訊息只能 append 到末尾
  ├── legal_output:一旦寫入就 immutable,不允許覆蓋
  └── iteration_count:每次 Agent 執行自動 +1

策略三:最大迭代護欄(收斂保證)

Graph 的 Conditional Edge 邏輯:

  任何邊(Edge)在路由前,先檢查:

  ┌─────────────────────────────────────┐
  │  if state["iteration_count"] >= 5:  │
  │      → 強制跳轉到 Fallback Node     │
  │                                     │
  │  elif state["legal_output"] and     │
  │       state["finance_output"]:      │
  │      → 跳轉到 Router(綜合決策)    │
  │                                     │
  │  else:                              │
  │      → 繼續執行下一個 Agent         │
  └─────────────────────────────────────┘

  Fallback Node 的動作:
  ├── 回傳:「系統無法自動完成此任務,已轉交人工審核」
  ├── 觸發 Cloud Logging Alert(嚴重警告)
  └── 將當前 State 快照存入 Firestore 供人工檢視

策略四:分散式 Checkpoint(故障恢復)

執行流程與 Checkpoint 點:

User Request
    │
    ▼
[Checkpoint 0] ← 任務開始,寫入 Firestore
    │
    ▼
Router 分派任務
    │
    ├── 法務 Agent 執行
    │       │
    │       ▼
    │   [Checkpoint 1] ← legal_output 寫入 Redis
    │
    └── 財務 Agent 執行
            │
            ▼
        [Checkpoint 2] ← finance_output 寫入 Redis
    │
    ▼
Router 綜合決策
    │
    ▼
[Checkpoint 3] ← 最終決策存入 Firestore

故障恢復邏輯:
  Worker 崩潰 → 排程器偵測到心跳停止
             → 讀取最後一個 Checkpoint
             → 從 Checkpoint N 重新開始(不是從頭)
             → 避免重複消耗 Token 成本

五、技術選型:各狀態存儲方案的 Trade-off

狀態存儲選型比較:

                   Redis (Memorystore)      Firestore
─────────────────────────────────────────────────────
讀寫延遲           < 1ms                    10~50ms
資料持久性         依配置(可選持久化)        強持久化
查詢能力           Key-Value / 簡單 TTL      富查詢(索引)
成本               較高(記憶體)             較低(磁碟)
適合存什麼         短期 Session State        長期 Checkpoint
                   Hot Checkpoint           完整任務歷史
                   Lock / Semaphore         用戶資料

推薦組合:
  Redis  → 正在執行中的 Agent State(Hot Path)
  Firestore → 已完成任務的歷史 + 可查詢的任務記錄

六、架構演進:從 LangGraph 的角度

LangGraph Graph 結構設計:

節點(Nodes):
  ┌─────────┐  ┌────────────┐  ┌─────────────┐  ┌──────────┐
  │ Router  │  │ LegalAgent │  │FinanceAgent │  │ Fallback │
  └─────────┘  └────────────┘  └─────────────┘  └──────────┘

邊(Edges)與條件路由:

  START → Router
  Router → LegalAgent   (if "legal_required" in task)
  Router → FinanceAgent (if "finance_required" in task)
  LegalAgent  → Router  (if legal_output is set)
  FinanceAgent → Router (if finance_output is set)
  Router → END          (if both outputs are set)

  所有邊都有護欄:
  任何邊 → if iteration_count >= 5: → Fallback → END

關鍵設計原則:
  ✓ 圖必須是有向無環圖(DAG)或有明確收斂條件的有環圖
  ✓ 所有 Cycle 都必須有計數器護欄
  ✓ 每個 Node 的輸出必須是確定性的(寫入 substate 後不再改變)

七、面試答題要點

「問題有兩個層面。第一層是架構設計問題:法務 Agent 和財務 Agent 的職責邊界沒有明確定義,導致兩者互相等待,形成循環依賴。根本解法是讓每個 Agent 能獨立輸出確定性結果,由 Router 負責綜合判斷,而不是讓子 Agent 互相協調。

第二層是 State Management 問題:多個 Agent 同時寫入 Global State 會有 Race Condition。解法是用 Append-only Reducer,每個 Agent 只能寫入自己的 substate,確保互相隔離。

護欄設計:在每條 Conditional Edge 上加 iteration_count >= 5 的硬性跳出,超過就轉 Fallback,觸發 Alert,等待人工處理。

狀態持久化:用 Redis 存 Hot Path 的 Checkpoint,用 Firestore 存完整任務歷史,確保 Worker 崩潰後能從斷點續傳。」


系列導覽:
(十五)RKK 實戰:Agent 規模化與 Cache 策略
(十七)RKK 實戰:MCP 與 Tool-Calling 安全隔離

Yen

Yen

Yen