這是同一個跨雲端貨幣基準測試的第三次執行,也是首次
箭頭指向另一方的測試。位於 Cloud Run 的 Google ADK master
(Gemini 2.5 Flash,us-central1)負責基準測試政策。它透過 A2A v1.0 呼叫
Amazon Bedrock AgentCore worker(Strands Agents on Nova Micro,us-east-1),
並以 MCP 匯率工具交叉驗證答案。
前一次執行是 Bedrock 擔任 master,ADK 擔任 worker。反轉方向原本只是部署變更——領域核心與框架無關,因此比較邏輯從未移動。實際上,反轉暴露了六個互通性缺陷,其中 五個是本地測試無法捕捉到的。這就是有趣的部分,因此本文以這些失敗開頭。
這個專案試圖做什麼?
大多數 A2A 示範停留在「HTTP 200 回應」。這只是煙霧測試,而不是互通性基準。
基準測試執行三種模式,以便將遠端驗證成本與工作成本分開:
| 模式 | 執行的內容 | 目的 |
|---|---|---|
mcp_only |
ADK master 透過 MCP stdio 呼叫匯率工具 | 基線 |
a2a_only |
ADK master 委派給 AgentCore worker | 遠端代理成本 |
verified |
兩者同時執行,然後以確定性方式比較 | 準確性/開銷 |
比較是算術而非模型判斷。金額與匯率皆為 Decimal。模型從未被問及兩個數字是否一致。
六個壞掉的地方
1. Strands A2A 額外套件使用錯誤的通訊協定版本
這是在部署前發現的,並且決定了整個 worker 的設計。
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="/"),
])
進入全螢幕模式 離開全螢幕模式
測試會斷言 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
進入全螢幕模式 離開全螢幕模式
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.."
}}
進入全螢幕模式 離開全螢幕模式
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>
進入全螢幕模式 離開全螢幕模式
刪除提供者即可修復。主要身分是裸網域:
"Principal": { "Federated": "accounts.google.com" }
進入全螢幕模式 離開全螢幕模式
值得注意的是失敗模式:無效的權杖與被拒絕的信任政策
是不同的錯誤(InvalidIdentityToken 對 AccessDenied),而這種
區別是區分這兩個錯誤的最快方法。
5. AgentCore 移除 A2A 版本標頭,而 SDK 預設為 v0.3
在聯合身分運作且 SigV4 簽署被接受後,worker 拒絕了它自己
版本正確的用戶端:
A2A version '0.3' is not supported by this handler. Expected version '1.0'.
進入全螢幕模式 離開全螢幕模式
用戶端是 a2a-sdk 1.1.2,確實會傳送正確的標頭
(client_factory.py 設定 A2A-Version: 1.0)。worker 是 v1.0。卡片
廣告 "protocolVersion": "1.0"。
標頭從未到達。AgentCore 的 A2A 執行環境只轉發允許清單中的
請求標頭——而伺服器端驗證器會將遺失的標頭視為
v0.3:
# a2a/utils/version_validator.py
if not actual_version:
return constants.PROTOCOL_VERSION_0_3
進入全螢幕模式 離開全螢幕模式
遺失的標頭與舊的用戶端對伺服器來說是無法區分的。修復方法
是執行環境設定的一行:
"requestHeaderAllowlist": ["A2A-Version"]
進入全螢幕模式 離開全螢幕模式
這是整個專案的版本偏移主題以第三種方式出現: 首先透過傳遞性鎖定,然後透過框架額外套件,現在透過 代理。
6. 代理卡在兩個方向上都廣告綁定位址
AgentCore worker 的卡片廣告 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
進入全螢幕模式 離開全螢幕模式
移除 /invocations——看起來更整潔的基礎——會回傳
UnknownOperationException。使用裸執行環境 ID 而非
編碼的 ARN 也會如此。百分比編碼也會破壞天真的 ${VAR##*/runtimes/} shell
剖析,當為範圍 IAM 政策衍生執行環境 ID 時。
它測量什麼
透過已部署的 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 只是數十毫秒。這是第二個
模型回合:worker 是一個代理,因此委派意味著 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)
進入全螢幕模式 離開全螢幕模式
因此 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
進入全螢幕模式 離開全螢幕模式
兩個目標都同意。兩者也都帶有 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,沒有成本 會計,沒有權杖計數,沒有冷/暖分佈。38 案例 × 3 模式的 矩陣尚未在此方向執行。
- 這裡沒有任何關於哪個雲端更快的說法。Gemini 2.5 Flash 和 Nova Micro 是不同價格點的不同模型;6 倍是代理委派的成本, 而不是 AWS 的成本。
值得保留的部分
領域核心沒有改變。CurrencyCoordinator 接受兩個 duck-typed
配接器,因此哪個雲端託管 master 是一個部署決定。這成立:
比較邏輯、陳舊規則、失敗分類和評估工具在反轉後都完好無損。
所有壞掉的地方都在接縫處——通訊協定版本、依賴項額外套件、 身分聯合、標頭轉發、URL 形狀。六個缺陷中有五個在部署真實東西之前是看不見的。如果你從這裡得到一個教訓: 一個從未離開你筆電的互通性測試是在測試你的 模擬。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.