在本系列的前六篇文章中,我构建了相同的货币基准
共六次,每次都更换由哪朵云下达指令、哪朵云执行指令。领域核心相同,38 个评估用例相同,Decimal 比较器相同,
每次跳转都使用同一公开汇率源。唯一变化的是编排方向。

这已经覆盖了三朵云之间的全部有向边,每条边都已部署并度量。本文是汇总:把六条边排在一起后数值说明了什么,哪些问题在多条边上反复出现,以及哪些仍未测量。

简而言之:A2A v1.0 在我部署的每一个方向上都能互通。
协议从来不是最耗时的部分,也不是最容易失败或最值得优化的部分。身份才是。打包才是。超时才是。

覆盖矩阵

三朵云、三种框架,六条可能的有向边:

主控方(编排) 远程方(应答) 状态
Microsoft Foundry, Azure Google ADK, Cloud Run 已部署——完整 38 用例矩阵 + 托管冒烟
Bedrock AgentCore, AWS Google ADK, Cloud Run 已部署——38 用例矩阵(本地 harness)+ 托管冒烟
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")

Enter fullscreen mode Exit fullscreen mode

数值并排对比

阅读延迟列前请先阅读来源列。其中两行是 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 hosted ~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 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 s,主控运行多工具推理循环约需 20 s。ADK → AgentCore 的运行推翻了这一理论。其 AgentCore 工作器按任何定义都是叶子,仅用一次工具调用回答一个问题,却仍耗时 18.92 s

六行真正对齐的是远程的运行时和模型,而非角色:

  • 远程为 gemini-2.5-flash on Cloud Run 的:耗时 1.69–2.09 s
  • 远程为 gpt-5-mini on Foundry 托管的:耗时 18.8–23.4 s
  • 远程为 Nova Micro on AgentCore Runtime 的:耗时 18.9–25.1 s

此分组在两个方向上均成立,与哪朵云负责编排无关。诚实的解读是:委托成本等于一次推理加上远程平台的请求开销,而两款托管智能体运行时的单次调用成本比 Cloud Run 上的容器高一个数量级。究竟是模型速度还是平台开销,我尚未分离——这需要同一模型在两种运行时上运行,我尚未完成。

不是协议。我在 ADK → Foundry 路径上直接测量:一次带认证的 agent-card 获取热启动 0.35 s(冷启动 0.51 s),令牌在调用间缓存。把第二朵云加入路径——工作站直连 21.2 s,通过 Cloud Run ADK 代理 23.4 s——仅增加约 2 秒,而单次运行的波动范围是 18.9–29.5 s。主控自身的方差已大于整条额外云路径的成本。

若想让跨云智能体架构更快,协议和代理并非瓶颈所在。

发现 2:verified 模式只消耗一次 A2A 调用,而非两次

每次运行中,verified 的耗时大致等于较慢的那条腿,而非两条腿相加,因为 MCP 与 A2A 并发执行:

  • Foundry → ADK:a2a_only 1.69 s,verified 1.71 s
  • AgentCore → ADK:a2a_only 2.09 s,verified 1.87 s(verified 矩阵中位数甚至低于 a2a_only——相同分布,不同用例组合)
  • AgentCore → Foundry:A2A 腿在 verified 运行中约 18.1 s,而 MCP 腿约 3.1 s
  • Foundry → AgentCore:a2a_only 25.1 s,verified 28.2 s
  • ADK → AgentCore:a2a_only 18.92 s,verified 16.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: truerelative_difference: 0
  • ADK → Foundry,实测:五次运行中每一次报价完全相同
  • ADK → AgentCore,实测:EUR 0.8713887.138 及 CHF 0.8124881.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 小时阈值。陈旧规则在真实发布的汇率而非注入的 fixture 上触发,且协调器将其显式呈现而非让模型用安慰性文字掩盖,这才是更有用的信号。

114 条记录矩阵中的 96.77% 一致率并不矛盾。38 个用例包含故意注入的故障(超时、陈旧汇率、强制不一致、恶意工具文本),正是这些故障用例将数值拉低到 100% 以下。全部 60 条实测适配器记录均在容差内一致。整个运行中唯一的不一致是我注入的,而验证器全部标记了出来。

这诚实地回答了「验证是否值得」的问题:在快乐路径上,你在准确性上什么也没买到。你为额外的一秒——或额外二十秒——买到的是独立故障检测:第二朵云、不同厂商模型下的第二种实现,当工具被破坏、陈旧或错误时,它会大声反对。

反复出现的故障

六次构建,六组 bug,大量重叠。以下是反复出现的:

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 路由带 proto-JSON 参数的 SendMessage
没有回退协商:客户端获取一张明确声明 protocolVersion: 0.3.0 的 agent card,却仍调用 v1.0 方法。

若在已完全授权的端点上看到 MethodNotFoundError,在调试其他任何内容前,请先检查双方 a2a-sdk 的主版本——并检查自己客户端的方法拼写,因为 Foundry 的 card 在同一 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

三次构建,三种机制——传递性 pin、框架 extra,以及现在代理悄无声息地剥离头——最终都指向同一版本偏差症状。任何位于两个 A2A 端点之间的中介都可能丢失协议版本,而把缺失默认视为更旧版本,会把传输 bug 变成看似客户端 bug 的东西。

依赖 pin 是协议约束,直到它不是

在这些构建的不同阶段,生态 pin 互斥:

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(直至 0.4.0) <0.4.0strands-agents[a2a](最新) <0.4

google-adk 2.5.0 是解锁 GCP 侧的版本。A2UI 被完全移除,因为一个与协议相邻的 extra 将整个智能体锁在 v0.3.0——提交前请审计你的 extra。

Foundry → AgentCore 构建提供了有用的教训。AgentCore 的 A2A server extra 需要 0.3 行;Foundry 侧客户端使用 1.x;把两者装进同一个 bundle 无法满足。修复是架构性的,而非版本升级:将 AgentCore 应用 bundle 固定在其兼容 SDK 上,客户端保持独立,让两者对话。网络协议互通了,尽管 Python 包版本不一致。包版本一致是同一进程的要求,而非线协议要求。

第六次构建采用了另一个可行方案:保留 agent 框架用于 agent 循环,移除其 [a2a] extra,直接用 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

因此,面对把线协议版本 pin 错的 extra,有两种已验证的逃生路线:分离 bundle,或绕过 extra。两者都有已部署的运行支持。每侧的 lockfile 测试可防止依赖升级悄然重新引入不匹配。

每个默认超时都假设单云环境

协调器最初每适配器 10 秒的超时在本地足够宽裕,在全部六次跨云运行中却致命。它干净地失败了,这是唯一的好处:

{"failures": {"a2a": "timeout: adapter timed out"}, "elapsed_ms": 10010}

Enter fullscreen mode Exit fullscreen mode

这条失败路径早已通过直连调用证明需要 25 秒。适配器没问题;策略错了。最终值:AgentCore 和 Foundry-master 托管路径 60 s,ADK 客户端和 Foundry 协调器 120 s。

冷启动不仅增加延迟。ADK 工作器的一次冷启动回复完全省略了请求的目标。因为解析是严格的,这表现为类型化的 protocol 失败,而非静默的短答案。需为部分回复设计。

身份才是全部工作

全部六次构建中最强烈的模式:A2A 互通是一个披着协议外衣的认证问题。JSON-RPC 部分在每个方向上都是容易的部分。

从 AWS 或 GCP 调用 进入 Foundry:

  • 入站 A2A 是可选且预览的——托管 Foundry 智能体默认使用 Responses,直到你 PATCH 启用 A2A。
  • PATCH 的 protocol_configuration替换而非合并。我实际探测而非假设:仅 PATCH {"a2a": {}} 就会丢弃 Responses,并同时让 A2A agent card 404(返回 400)——而请求本身返回 200
  • 不存在匿名 /.well-known/agent-card.json。card 位于 <agent>/endpoint/protocols/a2a/agentCard/v1.0 后面,受 Entra 保护。发现本身就是特权调用,因此 card 上的 404 或 401 更可能是缺少角色分配而非路径错误。
  • 调用方需要在项目上拥有 Foundry Agent Consumer 数据平面角色;部署方需要 Foundry Project Manager。Azure Owner 并不包含这两者。
  • 首先把令牌 audience 设对:https://ai.azure.com/.default,而非 ARM audience。一个对错误 scope 完全有效的令牌会产生看起来不像 scope 问题的 401。

从 Azure 调用 进入 AgentCore:

  • AgentCore 默认运行时授权是 IAM/SigV4,这对 AWS 调用方完全正确,对无法签署 AWS 请求的 Foundry 容器则毫无用处。直接 AWS CLI 调用可行;跨云调用不行。
  • 修复是自定义 JWT authorizer 加上 Cognito 机器对机器客户端,A2A 适配器执行 OAuth client-credentials 交换、缓存令牌,并由 authorizer 验证 issuer、client 及必需的 currencybench/invoke scope。

从 GCP 调用 进入 AgentCore——同样的墙,不同的答案:

第六次构建最初采用相同的 CUSTOM_JWT 方案,后改为无密钥联邦到 AgentCore 默认的 AWS_IAM authorizer: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 claim
accounts.google.com:oaud 令牌的 aud——你的 audience 字符串
accounts.google.com:aud 令牌的 azp——服务账号的数字 client id
accounts.google.com:sub 令牌的 sub

audience 字符串与一个数字比较,因此 STS 永远返回「Incorrect token audience」。用 oaud 固定 audience。

同时也要固定 subject,因为仅 audience 并不构成授权。
这一点在键修正后仍然重要。任何 Google 主体都可以为任意 audience 字符串请求 ID 令牌——audience 是调用方选择的值,而非平台授予的权限。因此仅基于 audience 的信任策略所能证明的只是「某个 Google 身份签发了此令牌」,在共享组织中这几乎等于什么也没证明。

因此策略同时基于 sub——且基于服务账号不可变的数字 ID 而非其 email,因为 email 是一个名称,若账号被删除后重建即可被释放并重新绑定:

gcloud iam service-accounts describe "$SA_EMAIL" --format='value(uniqueId)'

Enter fullscreen mode Exit fullscreen mode

这也是支持联邦到被调用方原生 authorizer 而非发送 bearer token 的理由:信任决策最终落在 IAM 策略中,CloudTrail 中有真实主体归属,可通过编辑策略撤销,而非存在于应用配置中的 claim-string 匹配器。

将 Google 注册为 OIDC 提供程序会破坏 Google 联邦。显而易见的下一步——为 accounts.google.com 执行 aws iam create-open-id-connect-provider——是错误的。AWS 原生与 Google 联邦;创建显式提供程序会使 STS 改为根据该提供程序的 thumbprint 验证,每次交换都会失败:

<Code>InvalidIdentityToken</Code>
<Message>The web identity token provided could not be validated.</Message>

Enter fullscreen mode Exit fullscreen mode

删除该提供程序即可修复。主体是裸域:"Federated": "accounts.google.com"

这里有用的诊断是这两个 bug 产生不同的错误——InvalidIdentityToken 表示令牌根本无法验证,而信任策略不匹配是 AccessDenied。这一区分是判断「我的提供程序设置错了」还是「我的条件错了」的最快方法。

于是同一 AWS 运行时收到两次跨云调用,得到两个不同答案:来自 Azure 的 bearer token,来自 GCP 的无密钥联邦。两者均已部署并测量。

凭证始终存在于调用方。Google 调用 Azure 意味着 Azure 客户端密钥放在 Google Secret Manager。AWS 调用 Azure 意味着 Entra 服务主体凭证放在 AWS Secrets Manager,限定在单个 secret ARN。没有办法绕过 secret 的存在;但可以让它不在你的源码树、清单或 shell 历史中。

一个值得预算的时间陷阱:数据平面 RBAC 传播需要 105 秒,而此时 az role assignment list 已显示分配存在。整个过程中 Agent 写入持续返回 403。这是传播问题,而非配置错误。在重新分配角色前请等待。已软删除的 Foundry 账号在被清除前也会阻止重新创建——Azure 资源状态也有记忆。

你的测试导入源码树,你的用户运行镜像

这类 bug 在三次构建中出现,且每次都被绿色的测试套件完全忽略。

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-master 侧也出现同样模式:MCP stdio 工具会启动一个真实子进程并使用真实导入路径,因此 -m mcp_server.server 无法导入不在部署 bundle 内的 monorepo 同级。修复是在交给 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] extra 提供。google-adk[a2a] 和裸 a2a-sdk 都不会拉入它。与 aiohttp 形状相同——一个可选的传递依赖,你的工作站恰好有——但它在启动时而非首次调用时失败,因此 Cloud Run 将其报告为健康检查超时,gcloud 则显示一个误导性的 Resource 'currency-adk-coordinator' already exists 冲突。实际错误仅存在于修订日志中。

发现这类 bug 且仅靠它们才能发现的两种方法:本地加载容器的真实目录布局,或部署并调用它。

from google.adk.cli.utils.agent_loader import AgentLoader
AgentLoader("/app").load_agent("app")   # 修复前会 ModuleNotFoundError

Enter fullscreen mode Exit fullscreen mode

推论:通过模型传递的错误不是可观测项

调试上述 STS 失败因一个愚蠢的原因多花了两个部署周期。适配器抛出了精确、可操作的消息——而它回到我这里变成了:

The remote agent reported an issue with the web identity token.

失败文本作为工具结果返回,因此会经过主控模型,而模型会改写。任何你打算用于调试的内容都必须在故障点服务端记录,而不仅仅是抛出。同样适用于上游细节:适配器丢弃了 STS 响应体,仅报告 HTTP 状态,而这正是无法区分 audience 不匹配与无法验证令牌的那一半。

Agent card 的可移植性仅与其内部 URL 相当

ADK 的 to_a2a() 将 host、port 和 scheme 硬编码进发布的 card。若保留本地默认,Cloud Run 智能体会发出一张声明 http://127.0.0.1:10001 的 card,而远程客户端会 dutifully 尝试调用自己。容器必须读取 A2A_PUBLIC_URL——而部署脚本只能在第二次传递时设置它,因为 Cloud Run 直到首次部署完成后才会透露服务 URL。

我在第一次构建后将其写成 ADK 的怪癖。其实不是。AgentCore 工作器的 card 同样声明 http://127.0.0.1:9000——它自己的容器绑定地址——原因完全相同。a2a-sdk 客户端按 card URL 路由,因此本系列中每个跨云客户端最终都会把 card 的接口重写为它实际拨打的端点:

for interface in card.supported_interfaces:
    interface.url = endpoint

Enter fullscreen mode Exit fullscreen mode

跨云边界时,请将「card 告诉你该把消息发到哪里」视为仅供参考,双向皆然。

A2A 基础 URL 并非看起来像基础 URL 的那个

agentcore status 将运行时 ARN 百分号编码后打印到路径中,并带 /invocations 后缀。那个完整 URL(含后缀)就是 A2A 基础,而 card 位于其下方:

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 以获得更整洁的基础 URL 会返回 UnknownOperationException,用裸运行时 id 替换编码后的 ARN 也会如此。百分号编码还有第二口:用天真的 ${VAR##*/runtimes/} 从作用域 IAM 策略中推导运行时 id,会得到一个格式错误的 ARN——部署成功,但仅在调用时失败。拆分前请先解码。

反过来,Foundry 要求你手动编写 card。在 MCP 工具会自动显示为 skills 的 ADK 之后,这感觉像退步。但这也是向诚实迈进的一步:card 是一份契约,而在那一边你必须声明它。

模型层失败不是协议失败

值得单独列出,因为它们会被误读为互操作 bug,但其实不是:

  • Nova Micro 读取「将 100 USD 转换为 EUR」,随后声称目标货币缺失并要求用户确认。工具可用性不是问题;自然语言参数提取才是。主控提示现在禁止对已提供信息请求确认,回归测试保留了这一约束。
  • Gemini 2.5 Flash 将一个真正实时的 hosted-adk-a2a 报价报告为非实时,因为该来源名称与 fixture 来源 hosted-local-a2a 共享 hosted- 前缀,而共享指令通过子串区分它们。数据正确;只有描述文字错误。同一条指令在 Nova Micro 和 gpt-5-mini 上都能正确标记——这正是它成为共享指令 bug 而非模型 bug 的原因。
  • gpt-5-minigemini-2.5-flash 在所有运行中都遵守「调用工具,永不计算」。将算术留在 Decimal 中意味着一致性检查比较的是数字,而不是模型的描述文字。

让基准在失败时闭合

最重要的非技术教训。一个托管基准工具若在其端点配置缺失时静默回退到确定性 fixture,就会给你从无到有的干净数字,而你会发布它们。两个托管协调器现在都拒绝:

{ "name": "CURRENCY_REQUIRE_GCP_ADK", "value": "1" }

Enter fullscreen mode Exit fullscreen mode

设置后,a2a_onlyverified 将返回 gcp_adk_not_configured,而非一个看似合理的 fixture 答案。本地 fixture 运行仍可工作,但你必须按名称请求(CURRENCY_ALLOW_LOCAL_FIXTURES=true)。

经六次翻转后仍存活的部分

领域核心。两个接口——MCP 支持的 ExchangeRateTool 和 A2A 支持的 RemoteCurrencyAgent——Microsoft Agent Framework、Strands 和 ADK 都在它们之外,每种托管运行时也都在它们之外。

翻转哪朵云负责编排,从未需要重写领域服务。与框架无关的 Python 在全部六次构建中都拥有算术、并发、超时策略和故障分类,同样的 38 个用例和同样的比较器在每一次上运行。这种可移植性才是本项目的实际结果——比任何单个延迟数字更重要,也远比把两个 SDK 塞进一个进程更重要。

所有跨六次的故障都发生在接缝处:协议版本、依赖 extra、身份联邦、头转发、URL 形状。领域逻辑中没有任何东西损坏。这就是整个项目结果的形态。

类型化失败在使六次可比较方面发挥了很大作用。每个适配器异常都被规范化为边界上的 validationproviderauthenticationtransporttimeoutprotocol 之一,因为「哪一层坏了」是一个研究问题,而非日志搜索练习。

本文并未确立的内容

直白陈述,因为从一次绿色冒烟测试中过度声明的诱惑正是本系列构建时力图抵制的:

  • 六行中有两行是单次观测。一次托管请求不是延迟分布。Foundry → AgentCore 运行端到端耗时约 45 s,而测得的适配器工作为 28.2 s;这一差距是模型和托管响应开销,仅凭一个样本就称其中任一数值为基准都是不负责任的。
  • 仅两条 GCP-remote 路径拥有完整的 38 用例矩阵。AgentCore ↔ Foundry 配对以及两条 ADK 作为主控的路径均未针对该矩阵运行。
  • 令牌使用量和云成本在所有地方都未测量。节流和令牌过期下的行为、重复的冷/热分布也未测量。
  • AgentCore → ADK 矩阵在本地 harness 上运行,而非通过 AgentCore 托管层。仅冒烟测试使用了托管。明确保留这一标签可避免将 harness 延迟归因于 AgentCore。
  • 本文未显示任何框架或云优于另一个。延迟差异跟踪的是远程使用的模型和运行时,而非平台质量——且我尚未将模型速度与平台请求开销分离,这需要同一模型在两种运行时上运行。
  • ADK → AgentCore 数值是在宽松 IAM 策略下取得的。bedrock-agentcore:InvokeAgentRuntime 作用域限定为 runtime/<id>runtime/<id>/* 在获取 agent card 时被拒绝(403);仅 Resource: "*" 有效。AgentCore 针对某种我尚未识别的 ARN 形状进行授权,且数据平面事件默认不出现在 CloudTrail 中,因此我无法读取拒绝。因此该行不是最小权限配置,我不会将其作为最小权限配置发布。
  • 两行是 n=5,一天,一个区域对,一对模型。无 p95,无令牌计数,无成本核算,无冷/热分离。

六行汇总

  1. A2A v1.0 在全部六个方向上互通。无需模式转换,无需框架特定胶水,只需请求可解析 JSON。
  2. 协议从来不是慢的部分。A2A 腿跟踪远程的模型和运行时——Cloud Run 容器 1.7–2.1 s,两款托管智能体运行时 18.8–25.1 s。一次带认证的 card 获取耗时 0.35 s;把第二朵云加入路径仅增加约 2 s。
  3. 跨云 A2A 是一个身份项目。为服务主体、数据平面角色、令牌 audience、被调用方上的非原生认证,以及你看不到的 RBAC 传播做好预算。若调用方可以铸造工作负载 OIDC 令牌,则联邦到被调用方的原生 authorizer 优于发送 bearer token——且要固定 subject,而非仅固定 audience。
  4. 版本偏差失败得响亮但晚,通过三种独立机制:传递性 pin、框架 extra,以及代理剥离版本头。包 pin 约束你的进程,而非你的线。分离 bundle 或绕过 extra;两者都是已验证的逃生路线。
  5. 你的镜像不是你的源码树。所有在绿色测试套件中幸存的 bug 都是打包或接缝 bug——而在最后一次构建中,六处缺陷有五处在真正部署前不可见。
  6. 验证买来的是独立故障检测,而非准确性。在快乐路径上,两朵云每次都完全一致。注入的故障——以及在真实 Frankfurter 报价上触发的陈旧汇率警告——才是第二朵云证明自己价值的场景。

来源

各仓库,均包含部署脚本、测试、评估用例和原始结果:

上游智能体来自
Getting Started with MCP, ADK and A2A

如果你曾在云边界上运行过 A2A——尤其是如果你已找出 AgentCore 实际授权的 ARN 形状,或遇到了这六次构建中均未出现的 card/版本不匹配——我很想交流笔记。