這是同一項跨雲端貨幣基準測試的第三次執行,也是第一次
箭頭指向反方向。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

值得注意的是失敗模式:無效的權杖與被拒的信任政策是不同的錯誤(InvalidIdentityTokenAccessDenied),這種區別是分辨這兩個錯誤最快的方式。

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-central1us-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-central1us-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 形狀。六個缺陷中有五個直到真正部署後才顯現。如果你從這件事中得到一個教訓:一個永遠不離開你筆電的互通性測試,只是在測試你的模擬。