这是同一跨云货币基准的第三次运行,也是首次
箭头指向相反方向。Cloud Run 上的 Google ADK 主代理
(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] extra 提供。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

值得注意故障模式:无效令牌与被拒绝信任策略是不同错误(InvalidIdentityToken vs AccessDenied),这种区别是区分这两个 bug 的最快方式。

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

这是整个项目版本偏差主题的第三次出现:先通过传递性锁定,再通过框架 extra,现在通过代理。

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 也会如此。百分号编码还会破坏从 shell 解析运行时 ID 以生成作用域 IAM 策略时的朴素 ${VAR##*/runtimes/}


它测量什么

2026-07-31 日,通过已部署的 Cloud Run 协调器(us-central1us-east-1),每种模式 5 次热调用:

模式 中位数 最小值 最大值
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 小时阈值。陈旧规则在真实发布汇率上触发,而非在 fixture 上触发,是此处更有用的信号。


本次运行未确立的内容

  • 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 接受两个鸭子类型适配器,因此哪个云托管主代理是一个部署决策。这点成立:比较逻辑、陈旧规则、故障分类和评估工具在反转后均完好无损。

所有故障都发生在接缝处——协议版本、依赖项 extra、身份联合、头转发、URL 形状。六项缺陷中有五项直到真正部署后才可见。如果你从本文中只记住一件事:从未离开你笔记本的互操作性测试只是在测试你的 mock。