FDE 面試準備指南(二十二):RKK 實戰——動態並行 Tool-Calling 與依賴解析引擎

LLM 說「我需要查 User Profile、Order History、Risk Score」。
最差的工程師說:「好,我一個一個查。」
FDE 說:「三個互相獨立——我同時查,總延遲從 T₁+T₂+T₃ 降到 max(T₁,T₂,T₃)。」
這就是這題考的核心思維。


面試情境

面試官: 「Gemini 判定需要同時呼叫三個工具:get_user_profile、get_order_history、get_risk_score。這三個工具執行時間不同。有時候工具 B 的輸入必須依賴工具 A 的輸出。你如何設計動態工具執行引擎最大化並行,並處理這種動態依賴關係?」


一、核心問題:順序執行的延遲代價

場景:LLM 決定需要呼叫三個工具

  get_user_profile  → 150ms
  get_order_history → 400ms
  get_risk_score    → 300ms

順序執行(最差的方案):

  時間軸:
  0     150   550   850ms
  │─────│─────│─────│
  [Profile] [Orders] [Risk]
  
  Total: 150 + 400 + 300 = 850ms

並行執行(最優方案,如果互相獨立):

  時間軸:
  0                 400ms
  │─────────────────│
  [Profile 150ms]
  [Orders  400ms  ] ← 決定總延遲
  [Risk    300ms  ]

  Total: max(150, 400, 300) = 400ms(節省 53%)

在 Multi-turn Agent 中,每次推理前的 Tool 執行延遲會直接累積到 E2E 延遲:

5 輪對話 × 每輪 3 個工具 × 850ms(順序)= 12,750ms
5 輪對話 × 每輪 3 個工具 × 400ms(並行)= 6,000ms

用戶體驗差距:12.75 秒 vs 6 秒

二、依賴關係的三種模式

模式 1:完全獨立(Full Independence)
  → 可以全部並行

  get_user_profile ─────────────────────────────────── ✓ 並行
  get_order_history ────────────────────────────────── ✓ 並行
  get_risk_score ──────────────────────────────────── ✓ 並行
  
  所有工具同時啟動,等最慢的那個完成

模式 2:線性依賴(Sequential Dependency)
  → 必須按順序執行

  get_user_credit_limit
        │ 依賴輸出
        ▼
  calculate_max_loan(credit_limit=?)
        │
        ▼
  generate_offer(max_loan=?)
  
  不能並行,每一步都需要上一步的結果

模式 3:部分依賴(Partial Dependency)─── 最常見也最複雜
  → 混合策略:能並行的並行,有依賴的順序

  get_user_profile ────────────────────────┐
  get_account_balance ─────────────────────┤
                                           ▼
                                   calculate_risk(profile+balance)
                                           │
                                           ▼
                                   get_recommended_products(risk_level)

三、DAG-based 動態工具執行引擎架構

┌──────────────────────────────────────────────────────────────┐
│                   LLM 回傳工具呼叫請求                        │
│                                                              │
│  [                                                           │
│    { id: "t1", tool: "get_user_profile", depends_on: [] },  │
│    { id: "t2", tool: "get_order_history", depends_on: [] }, │
│    { id: "t3", tool: "get_risk_score", depends_on: ["t1"] },│
│    { id: "t4", tool: "generate_offer", depends_on: ["t2","t3"] }
│  ]                                                           │
└──────────────────────────┬───────────────────────────────────┘
                           │
                           ▼
┌──────────────────────────────────────────────────────────────┐
│            DAG Dependency Resolver                           │
│                                                              │
│  解析依賴關係,建立執行圖:                                    │
│                                                              │
│       t1 ──────────────────────────────┐                    │
│                                        ▼                    │
│       t2 ──────────────┐         t3 (depends t1)           │
│                        │               │                    │
│                        ▼               ▼                    │
│                   t4 (depends t2, t3) ─────────────────     │
│                                                              │
│  執行層次(Execution Tiers):                               │
│  Tier 1: [t1, t2]  → 無依賴,立即並行執行                   │
│  Tier 2: [t3]      → 等 t1 完成後執行                       │
│  Tier 3: [t4]      → 等 t2 和 t3 都完成後執行               │
└──────────────────────────┬───────────────────────────────────┘
                           │
                           ▼
┌──────────────────────────────────────────────────────────────┐
│             Parallel Execution Engine                        │
│                                                              │
│  Tier 1 執行(並行):                                        │
│  ├── Coroutine: get_user_profile() ────── 150ms             │
│  └── Coroutine: get_order_history() ───── 400ms             │
│                                                              │
│  等 Tier 1 全部完成(400ms)                                 │
│                                                              │
│  Tier 2 執行(t1 的結果已就緒):                             │
│  └── Coroutine: get_risk_score(profile=t1.result) ─ 300ms  │
│                                                              │
│  等 Tier 2 完成(300ms)                                     │
│                                                              │
│  Tier 3 執行(t2, t3 的結果都就緒):                         │
│  └── Coroutine: generate_offer(orders=t2, risk=t3) ─ 200ms │
│                                                              │
│  Total: 400 + 300 + 200 = 900ms                             │
│  vs 順序執行: 150 + 400 + 300 + 200 = 1,050ms               │
└──────────────────────────────────────────────────────────────┘

注意這個例子只省了約 14%(1,050 → 900ms):總延遲的下限是 DAG 最長依賴鏈(關鍵路徑)的長度,而不是 max(T₁,T₂,T₃)。依賴層數越深,並行能省的越少;只有大多數工具彼此獨立時,才接近開頭說的 max(…) 效果。


四、執行引擎的設計選型

選項比較:

                asyncio.gather()     ThreadPoolExecutor    Google ADK
──────────────────────────────────────────────────────────────────────
適用場景         I/O 密集型工具       阻塞式 I/O 呼叫        ADK 整合的工具
                (HTTP API 呼叫)     (舊版同步 SDK)
GIL 影響         不受影響             I/O 等待時會釋放 GIL; 不受影響
                                     CPU 密集工作會被 GIL 序列化,
                                     應改用 ProcessPoolExecutor
延遲              最低                有線程切換開銷          ADK 管理
DAG 支援         需自建               需自建                 需自建(可用 Sequential/ParallelAgent 組合)
Tool Registry    需自建               需自建                 需自建(見第五節設計提案)
錯誤隔離         需手動              需手動                  內建
可觀測性         需手動加 Trace       需手動加 Trace          內建 Trace

推薦策略:
  └── 新建系統、在 Google Cloud 上:Google ADK(最省開發成本)
  └── 已有 LangChain/LangGraph 系統:asyncio.gather() 為主
  └── 有大量同步遺留 SDK:ThreadPoolExecutor(只適合阻塞 I/O)
  └── 真正 CPU 密集的工具:ProcessPoolExecutor

五、在 ADK 上自建 Tool Registry(設計提案)

以下是建議自行實作的設計,不是 ADK 現成功能:ADK 的工具定義並沒有 depends_on 這類依賴宣告欄位,也不會自動解析工具依賴圖。ADK 原生提供的是 SequentialAgent / ParallelAgent 等工作流 Agent,以及模型層的平行 function calling;依賴圖解析需要自己在 Orchestrator 層實作。

ADK Tool Registry 解決什麼問題:

傳統做法:
  工具定義分散在程式碼各處
  依賴關係靠 LLM 自行推斷或手動 hardcode
  每次新增工具都要修改 Agent 邏輯

自建 Tool Registry(提案):

  ┌─────────────────────────────────────────────────────┐
  │  Tool Registry(工具目錄)                           │
  │                                                     │
  │  每個工具都宣告:                                    │
  │  ├── name: "get_risk_score"                         │
  │  ├── description: "計算用戶的信用風險評分"           │
  │  ├── input_schema: { user_id, profile_data }        │
  │  ├── output_schema: { risk_level, risk_score }      │
  │  └── depends_on: ["get_user_profile"]  ← 顯式宣告  │
  └─────────────────────────────────────────────────────┘
           │ LLM 查詢 Registry,自動理解依賴關係
           ▼
  ┌─────────────────────────────────────────────────────┐
  │  自建 Orchestrator(包在 ADK Agent 外層)            │
  │  ├── 自動解析依賴圖                                  │
  │  ├── 自動並行執行獨立工具                            │
  │  └── 自動將上游工具輸出注入下游工具輸入              │
  └─────────────────────────────────────────────────────┘

六、錯誤處理策略

並行執行時的錯誤處理:

策略 1:Fail-fast(一個失敗,全部停止)
  適用:所有工具的結果都必須有,缺一不可
  例:生成財務報告必須同時有用戶資料和帳戶餘額,缺一則報告無意義

策略 2:Partial Success(允許部分失敗)
  適用:某些工具的結果是 optional 的
  例:產品推薦可以在沒有 Risk Score 的情況下降級處理

策略 3:Retry with Fallback
  ┌──────────────────────────────────────────────────────┐
  │  工具執行失敗                                         │
  │       │                                              │
  │       ▼                                              │
  │  重試 1 次(立即)                                    │
  │       │ 仍失敗                                       │
  │       ▼                                              │
  │  重試 2 次(延遲 1s)                                 │
  │       │ 仍失敗                                       │
  │       ▼                                              │
  │  使用 Fallback 值或 Cached 舊結果                    │
  │       │                                              │
  │  記錄 Warning,讓 LLM 知道某個工具不可用              │
  │  讓 LLM 決定是否能在部分資訊下繼續                   │
  └──────────────────────────────────────────────────────┘

七、動態依賴:LLM 自行生成依賴關係

進階設計:讓 LLM 自己宣告工具間的依賴

Prompt 設計(讓 LLM 輸出帶依賴資訊的 Tool Call):

  System Prompt 補充:
  「當你決定呼叫多個工具時,請在 tool_calls 中標明:
   - 哪些工具可以並行執行(depends_on: [])
   - 哪些工具依賴其他工具的輸出(depends_on: ["tool_id"])
   
   這有助於系統最佳化執行順序。」

LLM 可能輸出:
  [
    { "id": "t1", "tool": "get_market_data", "depends_on": [] },
    { "id": "t2", "tool": "get_competitor_info", "depends_on": [] },
    { "id": "t3", "tool": "analyze_opportunity",
      "depends_on": ["t1", "t2"],
      "note": "需要市場數據和競品資訊才能分析" }
  ]

這讓執行引擎能動態構建 DAG,而不是每次都重新推斷

八、面試答題要點

「這道題的核心是:識別哪些工具可以並行,哪些有依賴關係,然後設計執行引擎最大化並行度。

架構設計:LLM 輸出 tool_calls 後,中間層先建立 DAG 依賴圖。無依賴的工具分為同一個 Execution Tier,並行執行(asyncio.gather() 或 Google ADK 的並行框架);有依賴的工具等上游完成後才啟動,並自動注入上游的輸出結果。

延遲改善量化:原本 T₁+T₂+T₃=850ms,並行後 max(T₁,T₂,T₃)=400ms,降低 53%。在多輪對話中效果更顯著。

Google ADK 的優點:Tool Registry 可以讓工具顯式宣告依賴關係,Orchestrator 自動管理並行和輸出傳遞,省去手動 DAG 解析的工程量。

錯誤策略:依業務需求選 Fail-fast(缺任何工具都不行)或 Partial Success(部分工具失敗可降級處理),並有 Retry with Fallback 機制。」


系列導航

← Part 21:RKK 實戰——長任務 Agent 的異步分散式架構 | Part 23:RKK 實戰——多租戶 Agent 的限流、Fair-Share 與 Token 預算控制 →

Yen

Yen

Yen