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 預算控制 →
