這是同一項跨雲端貨幣基準測試的第三次執行,也是第一次
箭頭指向反方向。Google ADK 主代理 執行於 Cloud Run
(Gemini 2.5 Flash,us-central1),擁有基準測試政策。它透過 A2A v1.0
呼叫 Amazon Bedrock AgentCore 工作者(Strands Agents on Nova Micro,us-east-1),
並與 MCP 匯率工具交叉核對答案。
前一次執行是以 Bedrock 為主、ADK 為工作者。反轉方向原本只是部署上的變更——
領域核心與框架無關,因此比較邏輯從未移動。實際上,這次反轉暴露出六項互通性缺陷,其中五項是本地測試無法發現的。這才是有趣之處,因此本文從失敗案例開始。
這個專案想要做什麼?
大多數 A2A 示範到「HTTP 200 回應」就結束。那只是冒煙測試,不是互通性基準。
基準測試執行三種模式,以便將遠端驗證成本與工作成本分開:
| 模式 | 執行內容 | 目的 |
|---|---|---|
mcp_only |
ADK 主代理透過 MCP stdio 呼叫匯率工具 | 基準線 |
a2a_only |
ADK 主代理委派給 AgentCore 工作者 | 遠端代理成本 |
verified |
兩者同時執行,再以確定性方式比較 | 準確度/額外負擔 |
比較是算術運算,而非模型判斷。金額與匯率皆使用 Decimal。模型永遠不會被詢問兩個數字是否一致。
六個出錯的地方
1. Strands A2A 額外套件使用錯誤的通訊協定版本
這個問題在部署前就發現了,並決定了整個工作者的設計。
strands-agents[a2a] 鎖定 a2a-sdk<0.4 —— A2A v0.3 線路方法
(message/send)。google-adk[a2a]==2.5.0 使用 a2a-sdk 1.x —— v1.0
(SendMessage)。v1.0 用戶端無法呼叫 v0.3 伺服器,而代理卡片也沒有版本協商機制。
這與專案第一次執行時,迫使 A2UI 擴充功能從 ADK 代理中移除的相同分歧。這次從相反方向再度出現。
解決方式:只使用 strands-agents 負責代理迴圈,並直接以 a2a-sdk 建置 A2A v1.0
伺服器介面。
# app/CurrencyWorker/main.py -- NOT strands.multiagent.a2a.A2AServer
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
app = Starlette(routes=[
*create_agent_card_routes(AGENT_CARD),
*create_jsonrpc_routes(_request_handler, rpc_url="/"),
])
Enter fullscreen mode Exit fullscreen mode
測試會確認 lockfile 維持 a2a-sdk 在 1.x 版本,避免依賴項目更新悄悄重新引入不匹配。
2. google-adk[a2a] 不會安裝自己的伺服器依賴
Cloud Run 容器建置成功,通過所有本地測試,卻在啟動時失敗:
ModuleNotFoundError: No module named 'sse_starlette'
File ".../google/adk/a2a/_compat.py", line 769, in attach_a2a_routes_to_app
Enter fullscreen mode Exit fullscreen mode
ADK 的 to_a2a() 會匯入 a2a.server.routes,而這需要 sse_starlette,此套件僅隨 a2a-sdk[http-server] 額外套件提供。無論是 google-adk[a2a]
還是單純的 a2a-sdk 都不會拉進此依賴。
成本:一次失敗的部署。雙方現在都宣告 a2a-sdk[http-server]。
3. IAM 信任政策永遠無法匹配
協調器不持有 AWS 金鑰。它使用聯盟身分:Cloud Run 的中繼資料伺服器
簽發 Google OIDC 權杖,STS AssumeRoleWithWebIdentity 將其交換為
臨時憑證,並以 SigV4 簽署請求。
信任政策看起來明顯正確,卻是靜默地不可能實現:
"Condition": { "StringEquals": {
"accounts.google.com:aud": "currencybench-agentcore-worker",
"accounts.google.com:sub": "1019138736740282766.."
}}
Enter fullscreen mode Exit fullscreen mode
AWS 並未將這些條件金鑰對應到其名稱所暗示的宣告:
| 條件金鑰 | 實際的 Google 宣告 |
|---|---|
accounts.google.com:oaud |
權杖的 aud
|
accounts.google.com:aud |
權杖的 azp(數值型客戶端 ID) |
accounts.google.com:sub |
權杖的 sub
|
聽眾字串正被拿來與數字比較。應使用 oaud 而非 aud 來鎖定聽眾。
4. 為 Google 建立 OIDC 提供者會破壞 Google 聯盟
自然的下一步——將 accounts.google.com 註冊為 IAM OIDC 身分提供者——是錯誤的。AWS 原生支援與 Google 聯盟。建立明確的提供者後,STS 會改為根據該提供者的指紋驗證,每一次交換都失敗:
<Code>InvalidIdentityToken</Code>
<Message>The web identity token provided could not be validated.</Message>
Enter fullscreen mode Exit fullscreen mode
刪除提供者後問題解決。主體應為裸網域:
"Principal": { "Federated": "accounts.google.com" }
Enter fullscreen mode Exit fullscreen mode
值得注意的是失敗模式:無效的權杖與被拒的信任政策是不同的錯誤(InvalidIdentityToken 對 AccessDenied),這種區別是分辨這兩個錯誤最快的方式。
5. AgentCore 移除 A2A 版本標頭,SDK 預設使用 v0.3
聯盟運作正常且 SigV4 簽署被接受後,工作者卻拒絕了自己版本正確的用戶端:
A2A version '0.3' is not supported by this handler. Expected version '1.0'.
Enter fullscreen mode Exit fullscreen mode
用戶端是 a2a-sdk 1.1.2,且確實會傳送正確的標頭
(client_factory.py 設定 A2A-Version: 1.0)。工作者是 v1.0。卡片也廣告了 "protocolVersion": "1.0"。
標頭從未送達。AgentCore 的 A2A 執行時期僅轉發允許清單中的請求標頭——而伺服器端的驗證器會將缺少標頭視為 v0.3:
# a2a/utils/version_validator.py
if not actual_version:
return constants.PROTOCOL_VERSION_0_3
Enter fullscreen mode Exit fullscreen mode
掉落的標頭與舊版用戶端對伺服器而言是無法區分的。修正方式只需一行執行時期設定:
"requestHeaderAllowlist": ["A2A-Version"]
Enter fullscreen mode Exit fullscreen mode
這是整個專案版本偏差主題的第三種呈現:先是透過傳遞鎖定,接著是框架額外套件,現在則是透過代理。
6. 代理卡片在兩個方向上都廣告綁定位址
AgentCore 工作者的卡片廣告 http://127.0.0.1:9000 —— 其容器的自身綁定位址。ADK 的 to_a2a() 同樣使用 http://127.0.0.1:8080。
a2a-sdk 用戶端依卡片 URL 路由,因此跨雲端呼叫除非用戶端將介面改寫為實際撥號的端點,否則會失敗。
已在第一次執行時記錄於 ADK;這不是 ADK 的特有問題。
額外:A2A 端點不是看起來像基礎 URL 的那個 URL
agentcore status 會印出一個 URL,路徑中包含執行時期 ARN 的百分比編碼,以及 /invocations 後綴。那個包含後綴的完整 URL 才是 A2A
基礎:
https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn%3Aaws%3A...%2F<id>/invocations
└── card at <base>/.well-known/agent-card.json
Enter fullscreen mode Exit fullscreen mode
移除 /invocations —— 那個看起來更整潔的基礎 —— 會回傳
UnknownOperationException。使用裸執行時期 ID 而非編碼的 ARN 也會一樣。百分比編碼也會破壞從 ${VAR##*/runtimes/} shell 剖析衍生執行時期 ID 以用於範圍 IAM 政策時的解析。
它測量什麼
每種模式各五次暖呼叫,透過已部署的 Cloud Run
協調器驅動,us-central1 → us-east-1,日期 2026-07-31:
| 模式 | 中位數 | 最小值 | 最大值 |
|---|---|---|---|
mcp_only |
2.96 s | 2.37 s | 3.84 s |
a2a_only |
18.92 s | 16.30 s | 19.68 s |
verified |
16.19 s | 15.64 s | 22.86 s |
有兩件事值得注意。
遠端代理跳轉成本約為工具基準的 6 倍。這不是網路延遲——us-central1 到 us-east-1 只有數十毫秒。這是第二次模型回合:工作者是一個代理,因此委派意味著 Nova Micro 規劃工具呼叫、執行呼叫,並寫回結構化回覆,而這一切都在 Gemini 做完同樣的事之後。A2A 委派帶來獨立性,而獨立性需要一次推論。
verified 並不比 a2a_only 慢。驗證模式同時執行 MCP 呼叫和 A2A 呼叫並比較它們,但其中位數卻更低。協調器會並行發出兩者:
mcp_task = self._call("mcp", self._mcp, request, failures)
a2a_task = self._call("a2a", self._a2a, request, failures)
mcp_quotes, a2a_quotes = await asyncio.gather(mcp_task, a2a_task)
Enter fullscreen mode Exit fullscreen mode
因此 verified ≈ max(mcp, a2a),而不是兩者相加。一旦你已經在支付遠端代理的費用,獨立驗證幾乎是免費的。兩個中位數的差異小於執行間的變異,因此應將它們視為相等,而不是 verified 真的更快。
正確性,驗證模式,100 USD → EUR 與 CHF:
EUR rate 0.87138 -> 87.138 CHF rate 0.81248 -> 81.248
primary: mcp-stdio:frankfurter-live
verifier: aws-agentcore-a2a-worker
Enter fullscreen mode Exit fullscreen mode
兩個目標都同意。兩者也都帶有 rate is stale; check the observation timestamp —— MCP 端回傳 Frankfurter 的每日參考匯率,時間戳記為 2026-07-30T00:00:00Z,而執行時間是 31 日 04:09Z,超過 24 小時門檻。陳舊規則在真實發佈的匯率上觸發,而非在固定資料上,這在這裡是更有用的訊號。
這次執行未建立的結論
-
IAM 政策尚未達到最小權限。將
bedrock-agentcore:InvokeAgentRuntime範圍限定在runtime/<id>與runtime/<id>/*會被 403 拒絕;只有Resource: "*"有效。AgentCore 針對某種我尚未識別的 ARN 形狀授權,且資料平面事件預設不會出現在 CloudTrail 中。上述測量是在寬鬆政策下取得的。 - n = 5,一組區域、一天、一對模型。沒有 p95、沒有成本會計、沒有 token 計數、沒有冷/暖分佈。38 案例 × 3 模式的矩陣尚未在此方向執行。
- 這裡沒有任何關於哪個雲端較快的宣稱。Gemini 2.5 Flash 與 Nova Micro 是不同價格點的不同模型;6 倍是代理委派的成本,而不是 AWS 的成本。
值得保留的部分
領域核心沒有改變。CurrencyCoordinator 接受兩個 duck-typed 配接器,因此哪個雲端託管主代理是部署決策。這一點成立:比較邏輯、陳舊規則、失敗分類與評估工具在反轉後都毫髮無傷地存續。
所有出錯的地方都在接縫處——通訊協定版本、依賴額外套件、身分聯盟、標頭轉發、URL 形狀。六個缺陷中有五個直到真正部署後才顯現。如果你從這件事中得到一個教訓:一個永遠不離開你筆電的互通性測試,只是在測試你的模擬。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.