Zentrix Gateway 文档

外部 A2A 智能体把平台签发的调用凭证带到模型调用里,平台据此把智能体内部的模型调用关联到具体某一次 A2A 调用,并把费用记到调用方名下

A2A 调用的链路关联与按调用方计费

单位管理员 A 接入的外部 A2A 智能体,会被 B、C 等多位成员调用。智能体处理请求时自己也要调用平台的模型网关。默认情况下,这些模型调用都记在智能体使用的模型密钥名下,也就是 A 名下;平台也无法分辨某一次模型调用是为哪一次 A2A 调用服务的。

接入「A2A 调用凭证」后:

  • 每一次 A2A 调用都能在请求详情里看到智能体内部发起的全部模型调用,以及这些调用的次数、令牌数与花费合计;模型调用详情也能跳回对应的 A2A 调用。
  • 智能体内部的模型调用记在调用方 B、C 名下,额度按调用方的人员额度判定,同时仍受该密钥自身额度的约束。
  • 智能体详情的监控页签按调用方列出模型花费,没有携带凭证的调用单独列为「智能体自身调用」。

关联与计费都不依赖链路追踪编号,也不要求调用方改动任何请求。

1. 工作方式

  1. 调用方通过平台调用智能体时,平台每转发一次 A2A 请求就签发一枚调用凭证(以 zac_ 开头),同时放在两处:请求头 X-Zenova-Call-Context,以及 params.message.metadata 中的 zenova_call_context 字段。
  2. 智能体从入站请求中取出凭证,放进本次请求的作用域(见第 4 节)。
  3. 智能体使用自己的专属模型密钥调用平台模型网关,在请求体 metadata.zenova_call_context 中原样带上凭证。
  4. 模型网关在本地验证凭证的签名与有效期,确认凭证签发给的智能体就是该密钥绑定的智能体,然后把本次模型调用记在调用方名下,并记下它属于哪一次 A2A 调用。

凭证由网关节点在本地签发和验证,不需要访问管理控制台的任何接口。

2. 计费口径

  1. 只有调用方本人申领的凭据才按人计费。 调用方通过应用市场申领的连接、个人 MCP 令牌,或在平台页面上以本人身份发起调用时,平台才签发凭证。管理员在智能体详情页签发、交给业务系统使用的令牌不签发凭证,这类调用记在密钥属主名下。需要按人计费时,请让调用方自己申领连接。
  2. 不带凭证的模型调用记在密钥属主名下。 平台对这类调用一律放行,不报错;监控中归入「智能体自身调用」。智能体自己发起的定时任务等场景就属于这一类。
  3. 凭证有效期 1 小时。 以模型请求的开始时间判定:在有效期内发起、超过有效期才结束的流式调用照常完成。超过 1 小时的长任务,之后发起的模型调用会收到 A2A_CALL_TOKEN_EXPIRED。
  4. 额度同时判定两处。 模型调用既要满足该专属密钥的额度,也要满足调用方的人员额度,任一处不足都会被拒绝。调用方额度不足时返回现有的额度错误码,对外消息中不包含调用方的额度明细。

3. 前置条件

  1. 接入智能体:在管理控制台「A2A 目录」中接入该 A2A 智能体并启用。
  2. 创建智能体专属模型密钥:打开该智能体的「A2A 智能体详情」,在「详情」页签的「模型密钥」区点「新建」。智能体已预填并锁定,选择工作区和模型分组后提交。密钥明文只显示一次,请立即交给智能体的运维人员保存。普通 API 密钥不能用于按调用方计费:普通密钥携带凭证时,凭证会被忽略,调用记在密钥属主名下。
  3. 配置模型网关地址:「模型密钥」区下方的「接入说明」给出本部署的模型网关地址和可直接复制的代码片段。
  4. 调用方申领:调用方在应用市场申领该智能体的连接(也可以使用个人 MCP 令牌,或在平台页面上直接调用),然后照常调用,不需要任何额外配置。

4. 智能体侧接入

4.1 取出凭证

凭证同时位于请求头 X-Zenova-Call-Context 和 params.message.metadata.zenova_call_context,取哪一处都可以。两处都存在但值不同时,说明请求在途中被改动,应直接拒绝该请求。两处都没有时,本次请求不带凭证,模型调用记在密钥属主名下。

4.2 按请求作用域保存凭证

同一个智能体进程会同时处理多位调用方的请求。凭证必须保存在请求作用域内,例如 Python 的 contextvars.ContextVar。

请勿把凭证存进全局变量、模块变量,或模型客户端的默认参数(例如默认请求头、默认请求体)。否则 C 的请求到达时会覆盖 B 的凭证,B 后续的模型调用会带上 C 的凭证,费用记到 C 名下。asyncio.create_task 与 asyncio.gather 会复制当前上下文,工具调用与子任务因此会自动继承本次请求的凭证。

import contextvars

METADATA_KEY = "zenova_call_context"
_current_token = contextvars.ContextVar("zenova_call_context", default=None)


def model_call_metadata():
    token = _current_token.get()
    return {METADATA_KEY: token} if token else None

4.3 在每一次模型调用中携带凭证

OpenAI Python SDK:每次调用时现取凭证,写入 metadata。

metadata = model_call_metadata()
kwargs = {"metadata": metadata} if metadata else {}
response = await client.chat.completions.create(
    model=model,
    messages=messages,
    **kwargs,
)

AgentScope:智能体内部调用模型时只传消息与工具,因此需要子类化 OpenAIChatModel,在 _call_api 中逐次写入 metadata。

from agentscope.model import OpenAIChatModel


class ZenovaChatModel(OpenAIChatModel):
    async def _call_api(self, model_name, messages, tools=None, tool_choice=None, **kwargs):
        metadata = model_call_metadata()
        if metadata is not None:
            kwargs["metadata"] = metadata
        return await super()._call_api(
            model_name, messages, tools=tools, tool_choice=tool_choice, **kwargs
        )

模型网关也接受请求头 X-Zenova-Call-Context,请求头与请求体 metadata.zenova_call_context 任选其一;两处都携带时,值必须相同。多数智能体框架不便按请求修改请求头,因此推荐只使用请求体。

完整的可运行示例(OpenAI Python SDK 版、AgentScope 版及并发自测脚本)位于产品仓库的 lobsterpool/examples/a2a-call-attribution/ 目录。

5. 查看关联与花费

位置内容
A2A 请求详情 →「智能体内部模型调用」本次 A2A 调用期间智能体携带凭证发起的全部模型调用,以及次数、令牌数与花费合计;点击一行进入该模型请求的详情
模型请求详情调用方显示为「智能体 X(代 B 调用)」,并提供「查看 A2A 调用」跳回对应的 A2A 请求
A2A 智能体详情 →「监控」页签 →「按调用方的模型花费」所选时间范围内该智能体专属密钥的模型花费,按调用方汇总;未携带凭证的调用汇总为「智能体自身调用」一行

A2A 调用没有任何内部模型调用时,「智能体内部模型调用」区显示「该次调用没有经平台的模型调用,或智能体未携带调用凭证」。

6. 错误码

模型网关校验凭证失败时会直接拒绝请求,不会改为按密钥属主计费。智能体应把失败原样返回给调用方。

错误码HTTP含义处理方法
A2A_CALL_TOKEN_CONFLICT400请求头与请求体 metadata 中都带了凭证,但值不同只保留一处,或保证两处值相同
A2A_CALL_TOKEN_MALFORMED400凭证不是单个字符串,例如请求体中有重复字段,或请求头出现多个值原样传入站时取得的字符串,不要改写
A2A_CALL_TOKEN_EXPIRED401凭证已超过 1 小时有效期见第 2 节第 3 条
A2A_CALL_TOKEN_INVALID401签名校验失败(凭证被改动、伪造或已轮换),或凭证所属单位与密钥所属单位不同只传平台转发时注入的凭证,并使用本单位创建的专属密钥
A2A_CALL_TOKEN_AGENT_MISMATCH403凭证签发给的智能体与密钥绑定的智能体不同每个智能体使用自己的专属密钥

全部错误码见「错误参考」。