在本系列前六篇文章中,我總共建立了六次相同的貨幣基準,
每次都更換哪個雲端負責下指令、哪個雲端負責回答。核心領域相同、38 個評估案例相同、Decimal 比較器相同、
每次跳轉兩端都使用相同的公開匯率來源。唯一改變的是協調方向。
現在三個雲端之間的每條有向邊緣都已部署並量測完成。本文為總結:把六條結果並列時,數字說明了什麼、哪些問題重複發生,以及哪些部分仍未量測。
簡短版本:A2A v1.0 在我部署的每個方向都能互通。
協定本身從來不是最昂貴的部分、不是失敗的原因,也不是值得優化的部分。真正關鍵的是身分、打包方式,以及逾時設定。
涵蓋矩陣
三個雲端、三個框架,共有六條可能的單向邊緣:
| 主控端(協調) | 遠端(回應) | 狀態 |
|---|---|---|
| Microsoft Foundry, Azure | Google ADK, Cloud Run | 已部署 — 完整 38 案例矩陣 + 託管 smoke 測試 |
| Bedrock AgentCore, AWS | Google ADK, Cloud Run | 已部署 — 38 案例矩陣(本地 harness)+ 託管 smoke 測試 |
| Bedrock AgentCore, AWS | Microsoft Foundry, Azure | 已部署 — 託管 smoke 測試 |
| Microsoft Foundry, Azure | Bedrock AgentCore, AWS | 已部署 — 託管 smoke 測試 |
| Google ADK, Cloud Run | Microsoft Foundry, Azure | 已部署 — 線上量測,5–6 次執行中位數 |
| Google ADK, Cloud Run | Bedrock AgentCore, AWS | 已部署 — 線上量測,5 次執行中位數 |
六條全數部署、六條全數量測。最後一條也最具啟發性:
本地套件已完成且通過綠燈一天後才進行部署,結果卻暴露出六個缺陷,其中五個是本地測試無法捕捉的。這個比例是本系列最可靠的發現。
另外一個存放庫 gcp-adk-a2a-currency 內含在 Cloud Run 上執行的 ADK 協調器,呼叫另一個 ADK 葉代理,但其預設 A2A 跳轉仍停留在 GCP 內部。它測試了協定,但並未跨越雲端邊界,因此不列入上述六條涵蓋範圍。
每次執行都使用相同三種模式:
| 模式 | 路徑 |
|---|---|
mcp_only |
主控端呼叫自己的 MCP 匯率工具 |
a2a_only |
主控端透過 A2A v1.0 委派給遠端代理 |
verified |
兩者同時執行;確定性程式碼比較結果 |
在每次執行中,模型都會選擇工具並解釋答案。它從不自行計算,也不自行判斷一致性。這些工作永遠由以下程式碼負責:
difference = abs(primary.converted_amount - verifier.converted_amount)
relative_difference = difference / abs(primary.converted_amount)
agreed = relative_difference <= Decimal("0.005")
Enter fullscreen mode Exit fullscreen mode
數字並列
閱讀延遲欄位前,請先閱讀來源欄位。其中兩列是 114 筆評估矩陣;兩列是單次託管 smoke 案例;兩列是重複線上執行的中位數。它們並非可互換的證據。
| 主控端 → 遠端 | 遠端模型 / 託管 | mcp_only |
a2a_only |
verified |
來源 |
|---|---|---|---|---|---|
| Foundry → ADK | gemini-2.5-flash, Cloud Run | 297 ms | 1.69 s | 1.71 s | 114 筆矩陣,中位數 |
| AgentCore → ADK | gemini-2.5-flash, Cloud Run | 286 ms | 2.09 s | 1.87 s | 114 筆矩陣,中位數 |
| AgentCore → Foundry | gpt-5-mini, Foundry hosted | ~4.2 s | ~18.8 s | ~18.1 s | 單次託管 smoke |
| Foundry → AgentCore | Nova Micro, AgentCore | 359 ms | 25.1 s | 28.2 s | 單次託管 smoke |
| ADK → Foundry | gpt-5-mini, Foundry hosted | n/a | 23.4 s | n/a | 5 次線上執行中位數 |
| ADK → AgentCore | Nova Micro, AgentCore | 2.96 s | 18.92 s | 16.19 s | 5 次線上執行中位數 |
兩條矩陣列的 p95:Foundry → ADK a2a_only 4.82 s,verified 4.15 s;
AgentCore → ADK a2a_only 6.10 s,verified 4.33 s。ADK → AgentCore 列的範圍:mcp_only 2.37–3.84 s,a2a_only 16.30–19.68 s,verified 15.64–22.86 s。
發現 1:差異來自遠端執行環境,而非協定
表中 A2A 單程時間從 1.69 s 到 25.1 s 不等,差距達 15 倍,但使用的是相同協定、相同 SDK、相同跨洲跳轉。協定本身並非變異來源。
在第六條邊緣出現前,我曾提出較整潔的解釋:遠端的「角色」是決定因素——葉代理回答單一問題約需 2 秒,主控端執行多工具推理迴圈約需 20 秒。但 ADK → AgentCore 的結果推翻了此理論。它的 AgentCore 工作者依任何定義都是葉代理,只用單一工具呼叫回答一個問題,卻仍花費 18.92 秒。
六列真正對齊的是遠端的「執行環境與模型」,而非角色:
- 遠端為
gemini-2.5-flash且託管於 Cloud Run:約 1.69–2.09 秒 - 遠端為
gpt-5-mini且託管於 Foundry:約 18.8–23.4 秒 - 遠端為 Nova Micro 且託管於 AgentCore Runtime:約 18.9–25.1 秒
此分組在兩個方向皆成立,與哪個雲端負責協調無關。誠實的解讀是:委派成本包含一次推論,加上遠端平台的請求開銷。兩個託管代理執行環境每次呼叫的成本,比 Cloud Run 上的容器高出一個數量級。至於差異來自模型速度還是平台開銷,我尚未分離——這需要將相同模型同時部署在兩個執行環境上,而我尚未執行此測試。
差異「不是」協定。我在 ADK → Foundry 路徑上直接量測:已驗證的代理卡片擷取在熱快取時為 0.35 秒(冷快取 0.51 秒),且權杖會跨呼叫快取。加入第二個雲端到路徑中——工作站直接呼叫 21.2 秒,透過 Cloud Run ADK 代理則為 23.4 秒——只增加了約 2 秒,相對於執行間的 18.9–29.5 秒波動,主控端本身的變異就已大於額外雲端的全部成本。
若想讓跨雲代理架構更快,協定與代理並非時間消耗所在。
發現 2:verified 模式只花費一次 A2A 呼叫,而非兩次
在每次執行中,verified 的成本大致等於較慢的那一腿,而非兩腿相加,因為 MCP 與 A2A 是並行執行:
- Foundry → ADK:
a2a_only1.69 s,verified1.71 s - AgentCore → ADK:
a2a_only2.09 s,verified1.87 s(verified 的矩陣中位數甚至低於 a2a_only——相同分布,不同案例組合) - AgentCore → Foundry:A2A 單腿約 18.1 s,位於 verified 執行內,其 MCP 單腿約 3.1 s
- Foundry → AgentCore:
a2a_only25.1 s,verified28.2 s - ADK → AgentCore:
a2a_only18.92 s,verified16.19 s——verified 再次低於 a2a_only,差值小於執行間波動。請將這兩者視為相等,而非驗證是免費加退款。
並行是明確設計,而非偶然:
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
因此,獨立跨雲驗證的代價是「一次遠端呼叫」,而非「一次遠端呼叫加上你的基準」。
發現 3:在正常路徑上,準確度差異為零
我部署的每一次跨雲 verified 執行都回報完全一致——relative_difference: "0",agreed: true,無任何警告:
- AgentCore → ADK,託管:EUR 與 CHF,差異為零
- AgentCore → Foundry,託管:匯率
0.87873,金額87.87300,雙方一致 - Foundry → AgentCore,託管:匯率
0.87138,雙方一致 - Foundry → ADK,託管:
agreed: true,relative_difference: 0 - ADK → Foundry,線上:五次執行皆回報相同報價
- ADK → AgentCore,線上:EUR
0.87138→87.138與 CHF0.81248→81.248,雙方一致,主控端mcp-stdio:frankfurter-live,驗證端aws-agentcore-a2a-worker
這是刻意設計。雙方刻意讀取相同的 Frankfurter 每日參考匯率,因此不一致只可能來自協定、模型或程式碼——絕非資料來源偏差。
最後一次執行有一個細節值得注意:兩個目標都帶有 rate is stale; check the observation timestamp。Frankfurter 的每日參考匯率時間戳為 2026-07-30T00:00:00Z,而執行時間為 31 日 04:09Z——已超過 24 小時門檻。過時規則在真實發布的匯率上觸發,而非注入的測試資料,且協調器將其 surfaced 而非讓模型圓滑地掩蓋,這是更有用的訊號。
114 筆矩陣中 96.77% 的一致率並不矛盾。38 個案例包含刻意注入的故障(逾時、過時匯率、強制不一致、惡意工具文字),正是這些故障案例將數字拉低至 100% 以下。所有 60 筆線上配接器記錄都在容忍範圍內一致。整個執行中唯一的不一致是我注入的,而驗證器全部標記出來。
這誠實地回答了「驗證是否值得」的問題:在正常路徑上,你在準確度上沒有獲得任何東西。你用額外的一秒——或額外的二十秒——換取的是獨立故障偵測:第二個實作、第二個雲端、不同供應商的模型,當工具被破壞、過時或錯誤時,它會大聲表示不同意。
重複出現的失敗
六次建置、六組錯誤,但有大量重疊。以下是重複出現的問題。
A2A v0.3.0 → v1.0 線路中斷
這影響了六次建置中的五次,且通常呈現相同錯誤:
a2a.utils.errors.MethodNotFoundError: Method not found
Enter fullscreen mode Exit fullscreen mode
或透過原始 JSON-RPC:
{"error": {"code": -32601, "message": "Method not found",
"data": [{"reason": "METHOD_NOT_FOUND",
"metadata": {"detail": "'method' field is not a valid A2A method."}}]}}
Enter fullscreen mode Exit fullscreen mode
v0.3.0 路由 message/send。v1.x 路由 SendMessage 並使用 proto-JSON 參數。
沒有回退協商:用戶端擷取明確宣告 protocolVersion: 0.3.0 的代理卡片,卻仍呼叫 v1.0 方法。
若在你有完整授權呼叫的端點上看到 MethodNotFoundError,請先檢查雙方的 a2a-sdk 主要版本——並檢查你自己用戶端的方法拼寫,因為 Foundry 卡片在單一 URL 上宣告三種介面(JSONRPC 1.0、JSONRPC 0.3、HTTP+JSON 0.3),且會樂意讓你選擇錯誤的介面。
第六次建置找到了第三種產生相同偏差的方式,而這一次更麻煩,因為兩端都是正確的。v1.0 用戶端呼叫宣告 "protocolVersion": "1.0" 的 v1.0 工作者,卻收到:
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 確實傳送 A2A-Version: 1.0。AgentCore Runtime 只允許列入白名單的請求標頭,因此標頭從未到達——而伺服器端驗證器將「缺少標頭」視為 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
三次建置、三種機制——傳遞性釘選、框架額外套件,以及代理靜默移除標頭——全都指向相同的版本偏差症狀。任何位於兩個 A2A 端點之間的中介,都是協定版本可能遺失的地方,而將「缺少」預設為較舊版本,會把傳輸錯誤轉化為看起來像用戶端錯誤。
相依性釘選在成為限制前是協定限制
在這些建置的各個階段,相依性釘選彼此互斥:
| 套件 |
a2a-sdk 需求
|
|---|---|
strands-agents 1.50.2 |
>=1.0.0,<2 |
agent-framework-a2a 1.0.0b260721 |
>=1.0.0,<2 |
google-adk 2.1.0 – 2.4.0 |
>=0.3.4,<0.4 ❌
|
google-adk 2.5.0 |
>=0.3.4,<2 ✅
|
a2ui-agent-sdk (through 0.4.0)
|
<0.4.0 ❌
|
strands-agents[a2a] (latest)
|
<0.4 ❌
|
google-adk 2.5.0 是解除 GCP 端封鎖的版本。A2UI 已被完全移除,因為一個協定相關的擴充功能將整個代理鎖定在 v0.3.0——在承諾使用前請先稽核你的額外套件。
Foundry → AgentCore 建置提供了有用的教訓。AgentCore 的 A2A 伺服器額外套件需要 0.3 系列;Foundry 端用戶端使用 1.x;在同一個 bundle 中安裝兩者無法滿足。修復是架構性的,而非版本升級:將 AgentCore 應用程式 bundle 釘選在其相容的 SDK 上,將用戶端保持分離,讓兩者透過網路通訊。網路協定可以互通,即使 Python 套件版本不相同。套件版本一致性是同一程序的需求;它不是線路需求。
第六次建置採用另一個可行的答案,且同樣有效:保留代理框架負責代理迴圈,移除其 [a2a] 額外套件,直接使用 a2a-sdk 建置 A2A v1.0 伺服器介面。
# 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
因此,從將錯誤線路版本釘選的額外套件中脫身的兩種已驗證方法是:分離 bundle,或繞過額外套件。兩者都有已部署的執行作為後盾。每側的 lockfile 測試可防止相依性更新悄悄重新引入不匹配。
每個預設逾時都假設只有一個雲端
協調器原本的每配接器 10 秒逾時在本地是寬裕的,但在六次跨雲執行中都是致命的。它乾淨地失敗,這是唯一的好事:
{"failures": {"a2a": "timeout: adapter timed out"}, "elapsed_ms": 10010}
Enter fullscreen mode Exit fullscreen mode
該失敗來自一條直接呼叫已顯示需要 25 秒的路徑。配接器沒問題;政策有問題。最終數值:AgentCore 與 Foundry 主控端託管路徑為 60 秒,ADK 用戶端與 Foundry 協調器為 120 秒。
冷啟動不只增加延遲。來自 ADK 工作者的冷啟動回應曾完全省略請求的目標。由於剖析是嚴格的,這會以型別化的 protocol 失敗呈現,而非靜默的短答案。請為部分回應設計。
身分就是全部工作
六次建置中最強烈的模式:A2A 互通性是一個穿著協定外衣的身分驗證問題。在每個方向上,JSON-RPC 那一半都是簡單的那一半。
呼叫 進入 Foundry,來自 AWS 或 GCP:
- 傳入的 A2A 是選擇性加入且為預覽——託管的 Foundry 代理會說 Responses,直到你 PATCH 啟用 A2A。
- PATCH 的
protocol_configuration是取代,而非合併。我實際測試而非假設:單獨 PATCH{"a2a": {}}會移除 Responses,並同時移除 A2A 代理卡片(400)——而請求本身回傳200。 - 沒有匿名的
/.well-known/agent-card.json。卡片位於<agent>/endpoint/protocols/a2a/agentCard/v1.0且位於 Entra 後方。探索本身就是特權呼叫,因此卡片上的 404 或 401 更可能是缺少角色指派,而非路徑錯誤。 - 呼叫者需要在專案上擁有 Foundry Agent Consumer 資料平面角色;部署者需要 Foundry Project Manager。Azure
Owner並不包含上述任一角色。 - 首先取得正確的權杖 audience:
https://ai.azure.com/.default,而非 ARM audience。一個對錯誤範圍有效的權杖會產生看起來完全不像範圍問題的 401。
呼叫 進入 AgentCore,來自 Azure:
- AgentCore 的預設執行環境授權是 IAM/SigV4,這對 AWS 呼叫者完全正確,但對無法簽署 AWS 請求的 Foundry 容器毫無用處。直接的 AWS CLI 呼叫可以工作;跨雲呼叫則不行。
- 修復是自訂 JWT 授權器加上 Cognito 機器對機器用戶端,A2A 配接器執行 OAuth 用戶端憑證交換,快取權杖,並由授權器驗證發行者、用戶端,以及必要的
currencybench/invoke範圍。
呼叫 進入 AgentCore,來自 GCP——同樣的牆,不同的答案:
第六次建置從相同的 CUSTOM_JWT 方法開始,後來改為無金鑰聯合到 AgentCore 的預設 AWS_IAM 授權器:Cloud Run 的中繼資料伺服器產生 Google OIDC 權杖,STS AssumeRoleWithWebIdentity 將其交換為臨時憑證,請求以 SigV4 簽章。容器中不存在任何長期 AWS 憑證。
達到此狀態花費了兩個 IAM 缺陷,兩者看起來都是正確的設定。
條件金鑰的意義與其名稱不同。以下信任政策在語法上有效、讀起來正確,但永遠無法匹配:
"Condition": { "StringEquals": {
"accounts.google.com:aud": "currencybench-agentcore-worker",
"accounts.google.com:sub": "1019138736740282766.."
}}
Enter fullscreen mode Exit fullscreen mode
| 條件金鑰 | 實際 Google 聲明 |
|---|---|
accounts.google.com:oaud |
權杖的 aud——你的 audience 字串 |
accounts.google.com:aud |
權杖的 azp——服務帳戶的數字用戶端 ID |
accounts.google.com:sub |
權杖的 sub
|
audience 字串與數字比較,因此 STS 永遠回應「不正確的權杖 audience」。請使用 oaud 釘選 audience。
同時也要釘選 subject,因為僅有 audience 並不等於授權。
這是金鑰正確後仍重要的部分。任何 Google 主體都可以為任意 audience 字串請求 ID 權杖——audience 是呼叫者選擇的值,而不是平台授予的權限。因此僅以 audience 為條件的信任政策,除了證明「某個 Google 身分產生了這個權杖」之外,什麼也證明不了,而在共享組織中,這幾乎等於什麼也證明不了。
因此政策也對 sub 設條件——且使用服務帳戶的不可變數字 ID 而非其電子郵件,因為電子郵件是名稱,若帳戶被刪除並重新建立,該名稱可能被釋出並重新綁定:
gcloud iam service-accounts describe "$SA_EMAIL" --format='value(uniqueId)'
Enter fullscreen mode Exit fullscreen mode
這也是聯合到被呼叫者原生授權器而非傳送持有者權杖的論據:信任決策最終在 IAM 政策中,CloudTrail 中有真實的主體歸屬,且可透過編輯政策撤銷,而不是在應用程式設定中以聲明字串匹配器進行。
將 Google 註冊為 OIDC 提供者會破壞 Google 聯合。顯而易見的下一步——為 accounts.google.com 執行 aws iam create-open-id-connect-provider——是錯誤的。AWS 原生與 Google 聯合;建立明確的提供者會切換 STS 改為針對該提供者的指紋進行驗證,每次交換都會失敗:
<Code>InvalidIdentityToken</Code>
<Message>The web identity token provided could not be validated.</Message>
Enter fullscreen mode Exit fullscreen mode
刪除提供者即可修復。主體是裸網域:
"Federated": "accounts.google.com"。
這裡有用的診斷是,這兩個錯誤會產生不同的錯誤訊息——InvalidIdentityToken 表示權杖根本無法驗證,而信任政策不匹配則是 AccessDenied。這個區別是分辨「我的提供者設定錯誤」與「我的條件錯誤」的最快方法。
因此,呼叫同一個 AWS 執行環境的兩次跨雲呼叫,有兩個不同的答案:來自 Azure 的持有者權杖,來自 GCP 的無金鑰聯合。兩者都已部署並量測。
憑證永遠存在於呼叫端。Google 呼叫 Azure 意味著 Azure 用戶端密碼存在於 Google Secret Manager 中。AWS 呼叫 Azure 意味著 Entra 服務主體憑證存在於 AWS Secrets Manager 中,且範圍限定在單一秘密 ARN。無法避免秘密的存在;可以避免的是將其放在你的原始碼樹、資訊清單或 shell 歷史記錄中。
一個值得納入預算的時序陷阱:資料平面 RBAC 傳播花費 105 秒,而 az role assignment list 已顯示指派存在。代理寫入在整個期間持續回傳 403。這是傳播問題,而非設定錯誤。在重新指派角色前請等待。已軟刪除的 Foundry 帳戶也會阻擋重新建立,直到清除為止——Azure 資源狀態也有記憶。
你的測試匯入原始碼樹;你的使用者執行映像檔
這類錯誤在三次建置中出現,且每次測試套件都顯示綠燈。
aiohttp 獨立地破壞了其中兩次。azure.identity.aio 使用 aiohttp 建置其非同步傳輸,而 aiohttp 並非 azure-identity 的硬性相依性——它存在於一般工作站上,因此本地執行通過:
File "azure/core/pipeline/transport/__init__.py", line 94, in __getattr__
raise ImportError("aiohttp package is not installed")
Enter fullscreen mode Exit fullscreen mode
在 AWS 上,將其加入存放庫根目錄的 requirements 並不足夠;CodeZip 會獨立解析應用程式 bundle,因此必須將其鎖定在 app/CurrencyCoordinator/pyproject.toml 中。在 GCP 上,uv sync --frozen 在容器中產生相同的缺失。
另外兩個屬於同一家族,都來自 ADK 呼叫 Foundry 的用戶端映像檔:一行 COPY pyproject.toml uv.lock agent.py ./ 從未提及 entra_auth.py,因此模組根本不在映像檔中;以及平坦的 from entra_auth import EntraAuth 無法解析,因為 ADK 的單一代理模式會將你的檔案匯入為 app.agent——套件的子模組,sys.path 中有 / 而非 /app。相對匯入在兩種情境中都能運作。
在 Foundry 主控端側也有相同的形狀:MCP stdio 工具會產生真正的子程序與真正的匯入路徑,因此 -m mcp_server.server 無法匯入不在部署 bundle 內的單一存放庫兄弟。修復是在交給 azd 前使用 rsync 將其複製到 bundle 中。不優雅;託管執行環境不在乎你的存放庫佈局。
第六次建置產生了此模式最乾淨的樣本。容器已建置、所有本地測試通過,但在啟動時死亡:
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 都不會拉入它。與 aiohttp 相同的形狀——工作站恰好擁有的選擇性傳遞相依性——但它在啟動時失敗而非首次呼叫,因此 Cloud Run 將其回報為健康檢查逾時,而 gcloud 則顯示誤導性的 Resource 'currency-adk-coordinator' already exists 衝突。實際錯誤僅存在於修訂記錄中。
有兩件事能找出這類錯誤,且其他方法都做不到:本地載入容器的真實目錄佈局,或部署它並呼叫它。
from google.adk.cli.utils.agent_loader import AgentLoader
AgentLoader("/app").load_agent("app") # ModuleNotFoundError before the fix
Enter fullscreen mode Exit fullscreen mode
推論:透過模型傳遞的錯誤不是可觀測項
除錯上述 STS 失敗花費了兩個部署週期,原因很蠢。配接器引發了精確、可採取行動的訊息——但它回到我這裡時變成:
The remote agent reported an issue with the web identity token.
失敗文字是作為工具結果回傳的,因此會通過主控端模型,而模型會改寫。你打算用來除錯的任何東西都必須在失敗點伺服器端記錄,而不僅是引發。同樣適用於上游細節:配接器會丟棄 STS 回應主體,僅回報 HTTP 狀態,而這正是無法區分 audience 不匹配與無法驗證權杖的那一半。
代理卡片的可攜性取決於其內部的 URL
ADK 的 to_a2a() 會將主機、連接埠與配置寫入發布的卡片。若保留本地預設值,Cloud Run 代理會交出宣告 http://127.0.0.1:10001 的卡片,而遠端用戶端會 dutifully 嘗試呼叫自己。容器必須讀取 A2A_PUBLIC_URL——而部署指令檔只能在第二次執行時設定它,因為 Cloud Run 直到首次部署完成後才會揭露服務 URL。
我在第一次建置後將此記錄為 ADK 的怪癖。但事實並非如此。AgentCore 工作者的卡片宣告 http://127.0.0.1:9000——其自身的容器繫結位址——原因完全相同。a2a-sdk 用戶端會依卡片 URL 路由,因此本系列中的每個跨雲用戶端最終都會將卡片的介面改寫為它實際撥號的端點:
for interface in card.supported_interfaces:
interface.url = endpoint
Enter fullscreen mode Exit fullscreen mode
在雲端邊界兩側,請將「卡片會告訴你該將訊息傳送到哪裡」視為建議,而非絕對。
A2A 基礎 URL 不是看起來像基礎 URL 的那個
agentcore status 會將執行環境 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/} 從範圍化的 IAM 政策中推導執行環境 ID,會產生格式錯誤的 ARN,部署成功但在呼叫時失敗。分割前請先解碼。
從另一個方向來看,Foundry 要求你手動撰寫卡片。在 ADK 會自動將 MCP 工具顯示為技能之後,這感覺像是倒退。但這也是朝向誠實的一步:卡片是一份合約,而在那裡你必須陳述它。
模型層失敗不是協定失敗
值得分離出來,因為它們會被讀成互通性錯誤,但其實不是:
- Nova Micro 讀取「將 100 USD 轉換為 EUR」,然後聲稱目標貨幣遺失,並要求使用者確認。工具可用性不是問題;自然語言參數擷取才是。主控端提示現在禁止對已存在資訊要求確認,而回歸測試會保留此行為。
-
Gemini 2.5 Flash 將真正的線上
hosted-adk-a2a報價回報為非線上,因為該來源名稱與測試資料來源hosted-local-a2a共享hosted-前綴,而共享指令會依子字串區分它們。資料是正確的;只有敘述是錯誤的。同樣的指令在 Nova Micro 與gpt-5-mini上標記正確——這使得它成為共享指令錯誤,而非模型錯誤。 gpt-5-mini與gemini-2.5-flash在每次執行中都遵守「呼叫工具,永不計算」。將算術保留在Decimal中,意味著一致性檢查比較的是數字,而非模型敘述。
讓基準在失敗時關閉
最重要的非技術教訓。若託管的基準工具在其端點設定遺失時靜默回退到確定性測試資料,它會從虛無中交給你看起來乾淨的數字,而你會發布它們。兩個託管協調器現在都會拒絕:
{ "name": "CURRENCY_REQUIRE_GCP_ADK", "value": "1" }
Enter fullscreen mode Exit fullscreen mode
設定後,a2a_only 與 verified 會回傳 gcp_adk_not_configured,而非合理的測試資料答案。本地測試資料執行仍可運作,但你必須明確要求(CURRENCY_ALLOW_LOCAL_FIXTURES=true)。
經歷六次翻轉後仍存活的部分
領域核心。兩個介面——MCP 支援的 ExchangeRateTool 與 A2A 支援的 RemoteCurrencyAgent——Microsoft Agent Framework、Strands 與 ADK 都在它們之外,每個託管執行環境也都在它們之外。
翻轉哪個雲端負責協調,從未需要重寫領域服務。與框架無關的 Python 在六次建置中擁有算術、並行、逾時政策與失敗分類,而相同的 38 個案例與相同的比較器針對每一次執行。這可攜性才是本專案的真正結果——比任何單一延遲數字更重要,也比將兩個 SDK 放入同一個程序重要得多。
在所有六次中,壞掉的所有東西都壞在「接縫」處:協定版本、相依性額外套件、身分聯合、標頭轉發、URL 形狀。領域邏輯中沒有任何東西壞掉。這就是整個專案結果的形狀。
型別化失敗在讓六次可比較方面發揮了很大作用。每個配接器例外都會在單一邊界被正規化為 validation、provider、authentication、transport、timeout 或 protocol 其中之一,因為「哪一層壞掉」是一個研究問題,而不是 grep 日誌的練習。
本文未確立的內容
坦白陳述,因為從綠燈 smoke 測試中過度宣稱正是本系列建立來抗拒的:
- 六列中有兩列是單次觀測。一次託管請求不是延遲分布。Foundry → AgentCore 執行端到端花費約 45 秒,而配接器工作量測為 28.2 秒;這個差距是模型與託管回應開銷,且在只有一個樣本的情況下,將任一數字稱為基準都是不負責任的。
- 只有兩條 GCP 遠端路徑有完整的 38 案例矩陣。AgentCore ↔ Foundry 配對與兩條 ADK 作為主控端的路徑都尚未對其執行。
- 權杖使用量與雲端成本在所有地方都未量測。節流與權杖過期下的行為、重複的熱/冷分布也未量測。
- AgentCore → ADK 矩陣是在本地 harness 上執行,而非透過 AgentCore 託管層。只有 smoke 測試使用了託管。保留此標籤可避免將 harness 延遲歸因於 AgentCore。
- 這裡沒有顯示任何框架或雲端比另一個更好。延遲差異追蹤的是遠端使用的模型與執行環境,而非平台品質——且我尚未將模型速度與平台請求開銷分離,這需要將相同模型部署在兩個執行環境上。
-
ADK → AgentCore 的數字是在寬鬆的 IAM 政策下取得的。將
bedrock-agentcore:InvokeAgentRuntime範圍限定在runtime/<id>與runtime/<id>/*會在代理卡片擷取時被 403 拒絕;只有Resource: "*"有效。AgentCore 會針對我尚未識別的 ARN 形狀進行授權,且資料平面事件預設不會出現在 CloudTrail 中,因此我無法讀取拒絕。因此該列不是最小權限設定,我不會將其作為最小權限設定發布。 - 兩列是 n=5、一天、一個區域配對、一個模型配對。沒有 p95、沒有權杖計數、沒有成本會計、沒有冷/熱分離。
六行總結
- A2A v1.0 在所有六個方向上都能互通。沒有結構描述轉換,除了要求可剖析的 JSON 之外,沒有框架特定的膠水。
- 協定永遠不是慢的那一部分。A2A 單腿追蹤遠端的模型與執行環境——到 Cloud Run 容器為 1.7–2.1 秒,到任一託管代理執行環境為 18.8–25.1 秒。已驗證的卡片擷取成本 0.35 秒;在路徑中加入第二個雲端成本約 2 秒。
- 跨雲 A2A 是一個身分專案。請為服務主體、資料平面角色、權杖 audience、被呼叫端的非原生驗證,以及你看不到的 RBAC 傳播編列預算。若呼叫端可以產生工作負載 OIDC 權杖,聯合到被呼叫端的原生授權器優於傳送持有者權杖——且請釘選 subject,而不僅是 audience。
- 版本偏差會大聲但晚期失敗,透過三種不同的機制:傳遞性釘選、框架額外套件,以及代理移除版本標頭。套件釘選限制你的程序,而非你的線路。分離 bundle 或繞過額外套件;兩者都是已驗證的脫身方法。
- 你的映像檔不是你的原始碼樹。所有在綠燈測試套件中存活的錯誤都是打包或接縫錯誤——而在最後一次建置中,六個缺陷中有五個在真正部署之前是不可見的。
- 驗證換取的是獨立故障偵測,而非準確度。在正常路徑上,兩個雲端每次都完全一致。注入的故障——以及在真實 Frankfurter 報價上觸發的過時匯率警告——才是第二個雲端賺取其第二個位置的地方。
來源
存放庫,每個都包含其部署指令檔、測試、評估案例與原始結果:
- xbill9/foundry-adk-a2a-currency — Foundry → ADK,以及 ADK → Foundry 主控端
- xbill9/bedrock-adk-a2a-currency — AgentCore → ADK
- xbill9/bedrock-foundry-a2a-currency — AgentCore → Foundry
- xbill9/foundry-bedrock-a2a-currency — Foundry → AgentCore
- xbill9/adk-bedrock-a2a-currency — ADK → AgentCore,無金鑰 GCP→AWS 聯合
- xbill9/gcp-adk-a2a-currency — ADK 協調器,GCP 內部 A2A 跳轉
上游代理來自
Getting Started with MCP, ADK and A2A。
如果你曾在雲端邊界上執行 A2A——特別是如果你已找出 AgentCore 實際授權的 ARN 形狀,或遇到這六次都未遇到的卡片/版本不匹配——我很樂意交流心得。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.