在這一系列的六篇文章中,我共建立了同一個貨幣基準測試六次,每次只改變哪個雲端發出指令、哪個雲端接收指令。同樣的領域核心、同樣 38 個評估案例、同樣的 Decimal 比較器、兩端皆使用同一個公開匯率來源。唯一改變的是協調方向。
至此,已涵蓋三個雲端之間的所有有向邊緣,每一條都已部署並量測。本文為總結:將六條邊緣並列時,數字所透露的意義、哪幾種問題在多個案例中重複出現,以及仍有待量測的部分。
簡短結論:A2A v1.0 在我部署的每個方向都已互通。 協議從來不是成本最高、也不是最容易失敗或最值得優化的部分。真正棘手的是身分、封裝以及逾時。
涵蓋矩陣
三個雲端、三個框架,共有六條可能的有向邊緣:
| 主控端(協調) | 遠端(回應) | 狀態 |
|---|---|---|
| Microsoft Foundry, Azure | Google ADK, Cloud Run | 已部署 — 完整 38 案例矩陣 + 託管煙霧測試 |
| Bedrock AgentCore, AWS | Google ADK, Cloud Run | 已部署 — 38 案例矩陣(本地測試) + 託管煙霧測試 |
| Bedrock AgentCore, AWS | Microsoft Foundry, Azure | 已部署 — 託管煙霧測試 |
| Microsoft Foundry, Azure | Bedrock AgentCore, AWS | 已部署 — 託管煙霧測試 |
| 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")
進入全螢幕模式 退出全螢幕模式
數字並列比較
閱讀延遲欄位前,請先查看出處欄位。其中兩列為 114 筆評估矩陣;兩列為單次託管煙霧測試;兩列為重複線上執行的小型中位數。它們並非可互換的證據。
| 主控端 → 遠端 | 遠端模型 / 託管 | 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 託管 | ~4.2 s | ~18.8 s | ~18.1 s | 單次託管煙霧測試 |
| Foundry → AgentCore | Nova Micro, AgentCore | 359 ms | 25.1 s | 28.2 s | 單次託管煙霧測試 |
| ADK → Foundry | gpt-5-mini, Foundry 託管 | 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 s - 遠端為
gpt-5-mini於 Foundry 託管: 18.8–23.4 s - 遠端為 Nova Micro 於 AgentCore Runtime: 18.9–25.1 s
此分組跨越兩個方向,且與哪個雲端負責協調無關。誠實的解讀是:委派成本包含一次推論,以及遠端平台請求開銷;兩個託管代理執行環境每次呼叫的成本,比 Cloud Run 上的容器高出一個數量級。至於其中多少來自模型速度、多少來自平台開銷,我尚未分離——這需要同一模型在兩種執行環境上運行,而我尚未進行。
這與協議無關。我已在 ADK → Foundry 路徑上直接量測:已驗證的代理卡擷取在暖啟動時為 0.35 秒(冷啟動 0.51 秒),且權杖會在呼叫間快取。在路徑中加入第二個雲端——從工作站直接呼叫 21.2 秒,透過 Cloud Run ADK 代理則為 23.4 秒——僅增加約 2 秒,而單次運行間的波動範圍為 18.9–29.5 秒。僅主控端的變異就大於跨雲端的額外成本。
若想讓跨雲端代理架構更快,協議與代理並非瓶頸所在。
發現 2:驗證模式僅需一次 A2A 呼叫,而非兩次
在每次執行中,verified 的成本大致等於較慢的那一條腿,而非兩條腿相加,因為 MCP 與 A2A 同時執行:
- Foundry → ADK:
a2a_only1.69 s,verified1.71 s - AgentCore → ADK:
a2a_only2.09 s,verified1.87 s(驗證的中位數低於 a2a_only——相同分布,不同案例組合) - AgentCore → Foundry:A2A 腿在驗證執行中約 18.1 s,而 MCP 腿約 3.1 s
- Foundry → AgentCore:
a2a_only25.1 s,verified28.2 s - ADK → AgentCore:
a2a_only18.92 s,verified16.19 s——驗證再次低於 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)
進入全螢幕模式 退出全螢幕模式
因此,獨立跨雲端驗證的代價是「一次遠端呼叫」,而非「一次遠端呼叫加上基準成本」。
發現 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 小時門檻。陳舊規則在真實發佈的匯率上觸發,而非注入的測試資料,且協調器將其呈現而非讓模型以安慰性文字掩蓋,這是更有用的訊號。
114 筆矩陣中的 96.77% 同意率並不矛盾。38 個案例包含刻意注入的故障(逾時、陳舊匯率、強制不一致、惡意工具文字),而這些故障案例拉低了百分比。所有 60 筆線上配接器記錄皆在容忍範圍內一致。 整個執行中唯一的不一致是我刻意注入的,且驗證器皆正確標記。
這誠實回答了「驗證是否值得」的問題:在順利路徑上,你在準確度上沒有獲得任何額外好處。你多花的一秒——或二十秒——買到的是獨立故障偵測:第二個實作、第二個雲端、不同廠商的模型,當工具被破壞、陳舊或錯誤時,會明確發出不同意見。
重複出現的失敗
六次建置、六組錯誤,有大量重疊。以下是重複出現的問題。
A2A v0.3.0 → v1.0 傳輸中斷
這影響了六次建置中的五次,且通常呈現相同錯誤:
a2a.utils.errors.MethodNotFoundError: Method not found
進入全螢幕模式 退出全螢幕模式
或以原始 JSON-RPC 呈現:
{"error": {"code": -32601, "message": "Method not found",
"data": [{"reason": "METHOD_NOT_FOUND","metadata": {"detail": "'method' field is not a valid A2A method."}}]}}
進入全螢幕模式 退出全螢幕模式
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'.
進入全螢幕模式 退出全螢幕模式
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
進入全螢幕模式 退出全螢幕模式
對於伺服器而言,遺失的標頭與舊版用戶端無法區分。一行執行環境設定即可修正:
"requestHeaderAllowlist": ["A2A-Version"]
進入全螢幕模式 退出全螢幕模式
三次建置、三種機制——傳遞性固定、框架額外功能,以及代理靜默移除標頭——皆導致相同的版本偏差症狀。任何位於兩個 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;在同一個套件中安裝兩者無法滿足。修正方式是架構性的,而非版本提升:將 AgentCore 應用程式套件固定在相容的 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="/"),
])
進入全螢幕模式 退出全螢幕模式
因此,從將代理鎖定在錯誤傳輸版本的額外功能中,有兩種已驗證的脫困方法:分離套件,或繞過額外功能。兩者皆有已部署的執行作為後盾。每邊的鎖定檔測試可防止相依性更新悄悄重新引入不一致。
每個預設逾時都假設單一雲端
協調器原本每條配接器 10 秒的逾時,在本地是寬裕的,但在六次跨雲端執行中都是致命的。它乾淨地失敗,這是唯一的優點:
{"failures": {"a2a": "timeout: adapter timed out"}, "elapsed_ms": 10010}
進入全螢幕模式 退出全螢幕模式
此失敗來自一條直接呼叫已顯示需 25 秒的路徑。配接器沒問題;政策錯誤。最終數值:AgentCore 與 Foundry 主控託管路徑為 60 秒,ADK 用戶端與 Foundry 協調器為 120 秒。
冷啟動不只增加延遲。ADK 工作端的一次冷啟動回應完全省略了請求的目標。因為剖析是嚴格的,這表現為型別化的 protocol 失敗,而非靜默的簡短答案。請為部分回應設計。
身分就是全部工作
六次建置中最強烈的模式:A2A 互通性是一個穿著協議外衣的身分驗證問題。 在每個方向上,JSON-RPC 部分都是簡單的部分。
從 AWS 或 GCP 呼叫 進入 Foundry:
- 傳入的 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,且看起來不像範圍問題。
從 Azure 呼叫 進入 AgentCore:
- AgentCore 的預設執行環境授權是 IAM/SigV4,這對 AWS 呼叫者完全正確,但對無法簽署 AWS 請求的 Foundry 容器毫無用處。直接的 AWS CLI 呼叫可行;跨雲端呼叫則不行。
- 修正方式是自訂 JWT 授權器加上 Cognito 機器對機器用戶端,A2A 配接器執行 OAuth 用戶端憑證交換,快取權杖,並由授權器驗證發行者、用戶端以及必要的
currencybench/invoke範圍。
從 GCP 呼叫 進入 AgentCore——同樣的障礙,不同的解答:
第六次建置一開始採用相同的 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.."
}}
進入全螢幕模式 退出全螢幕模式
| 條件金鑰 | 實際 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)'
進入全螢幕模式 退出全螢幕模式
這也是支持聯合至被呼叫方原生授權器,而非傳送持有人權杖的論點:信任決策最終會在 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>
進入全螢幕模式 退出全螢幕模式
刪除提供者即可修正。主體是裸網域:"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 建置其非同步傳輸,而這不是 azure-identity 的硬性相依性——它存在於一般工作站上,因此本地執行通過:
File "azure/core/pipeline/transport/__init__.py", line 94, in __getattr__
raise ImportError("aiohttp package is not installed")
進入全螢幕模式 退出全螢幕模式
在 AWS 上,將它加入存放庫根目錄的 requirements 是不夠的;CodeZip 獨立解析應用程式套件,因此必須將它鎖定在 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 無法匯入不在部署套件內的單體存放庫同級項目。修正方式是在交給 azd 之前,先用 rsync 將其複製到套件中。不優雅;託管執行環境不在乎你的存放庫佈局。
第六次建置產生了此模式中最乾淨的範例。容器已建置,每個本地測試都通過,但在啟動時失敗:
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 都不會拉入它。與 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
進入全螢幕模式 退出全螢幕模式
推論:透過模型傳遞的錯誤不是可觀測值
除錯上述 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
進入全螢幕模式 退出全螢幕模式
在雲端邊界兩側,請將「代理卡告訴你該將訊息傳送到哪裡」視為建議性資訊。
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
進入全螢幕模式 退出全螢幕模式
移除 /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" }
進入全螢幕模式 退出全螢幕模式
設定後,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 之一,因為「哪一層破壞」是一個研究問題,而不是日誌搜尋練習。
本文未確立的內容
坦白陳述,因為從綠燈煙霧測試中過度宣稱的誘惑,正是本系列建立來抵制的:
- 六列中有兩列是單次觀察。 一次託管請求不是延遲分布。Foundry → AgentCore 執行端到端耗時約 45 秒,而配接器工作量測為 28.2 秒;這個差距是模型與託管回應開銷,而只有一個樣本,稱其中任一數字為基準測試都是不負責任的。
- 只有兩個 GCP 遠端路徑有完整的 38 案例矩陣。 AgentCore ↔ Foundry 配對與兩個 ADK 作為主控端的路徑都尚未針對它執行。
- 權杖使用量與雲端成本在所有地方都未量測。 節流與權杖過期下的行為、重複的暖/冷分布也未量測。
- AgentCore → ADK 矩陣在本地測試環境上執行,而非透過 AgentCore 託管層。只有煙霧測試使用了託管。明確保留此標籤可避免將測試環境延遲歸因於 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。
- 版本偏差失敗得很響亮,但很晚,透過三種不同的機制:傳遞性固定、框架額外功能,以及代理移除版本標頭。套件固定限制你的程序,而非你的傳輸。分離套件或繞過額外功能;兩者都是已驗證的脫困方法。
- 你的映像檔不是你的原始碼樹。 每個躲過綠燈測試套件的錯誤都是封裝或接縫錯誤——而在最後一次建置中,六項缺陷中有五項在部署實際內容前是不可見的。
- 驗證買到的是獨立故障偵測,而非準確度。 在順利路徑上,兩個雲端每次都完全一致。注入的故障——以及在真實 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 跳轉
上游代理來自 開始使用 MCP、ADK 與 A2A。
如果你曾在雲端邊界上執行 A2A——特別是如果你已找出 AgentCore 實際授權的 ARN 形狀,或遇到本系列六次建置都未遇到的代理卡/版本不符——我很樂意交流。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.