01 · 发现与 Agent Card
本章讲一切的起点:远程 agent 怎么描述自己(Agent Card),客户端怎么找到这张名片,以及怎么用密码学签名确认名片没被篡改。
1. 为什么需要“名片”
要把任务托付给一个黑盒 agent,你至少得先知道三件事:它在哪(端点 URL)、它会什么(技能)、怎么安全地连它(鉴权要求)。A2A 把这三类信息打包成一份 JSON 文档,叫 Agent Card(名片)——agent 的“数字名片”。
客户端拿到名片后,据此判断“这个 agent 适不适合干我这活、请求该怎么组、用什么凭证连”。
2. 名片里有什么
Agent Card 的结构由 proto 的 message AgentCard(specification/a2a.proto:361)定义。挑关键字段:
| 字段 | 含义 | 必填? |
|---|---|---|
name / description | 身份:人读的名字与用途 | 是 |
supported_interfaces | 怎么连:一组 AgentInterface,各含 url + 传输绑定 + 协议版本;第一项是首选 | 是 |
provider | 服务提供方(组织名 + URL) | 否 |
version | agent 自身版本(如 "1.0.0") | 是 |
capabilities | 可选能力开关:streaming / push_notifications / extended_agent_card / 扩展列表 | 是 |
security_schemes / security_requirements | 鉴权方案与要求 | 否 / 否 |
default_input_modes / default_output_modes | 默认收发的媒体类型(MIME) | 是 |
skills | 会什么:一组 AgentSkill | 是 |
signatures | 名片的 JWS 签名(见 §5) | 否 |
“技能(skill)”是描述性的,不是 RPC。 AgentSkill(specification/a2a.proto:435)用 id / name / description / tags / examples 描述“这个 agent 大概擅长干哪类事”,还可按技能覆盖输入/输出模式。它帮客户端匹配与挑选,而不是一个可直接调用的函数签名——这再次体现“对等 agent,不是工具”的取向。
AgentInterface 是 v1.0 的关键升级:同一 agent 可以用多种传输暴露同一套能力。 每个接口声明自己的 url、protocol_binding(JSONRPC / GRPC / HTTP+JSON,开放字符串)、和各自的 protocol_version(specification/a2a.proto:336-355)。客户端按顺序读 supported_interfaces,选第一个自己支持的传输——所以服务端应按偏好排序。
3. 客户端怎么找到名片:三种发现策略
名片“长什么样”是标准化的,但“去哪拿”依环境而定。规范列了三种(docs/topics/agent-discovery.md:18-71):
| 策略 | 机制 | 适合 |
|---|---|---|
| Well-Known URI | GET https://{域名}/.well-known/agent-card.json(遵循 RFC 8615) | 公开 / 域内广泛发现 |
| Curated Registry(注册表) | 中心服务收录名片,客户端按 skill/tag 等查询 | 企业内 / 公开市场 |
| Direct Config(直配) | 硬编码、配置文件、环境变量、私有 API | 紧耦合 / 私有 / 开发期 |
注意:注册表的查询 API 目前未被规范标准化(docs/topics/agent-discovery.md:59)——这是协议刻意留白的边界。
最常见的 well-known 流程(三步):
Client 已知/推断出域名 smart-thermostat.example.com
│
▼ GET https://smart-thermostat.example.com/.well-known/agent-card.json
Server 返回 JSON 名片
│
▼ Client 解析 supported_interfaces → 选首选传输 → 据 capabilities/skills 决定怎么用
4. 公开名片 vs 受保护名片(Extended Agent Card)
名片本身可能含敏感信息(内部 URL、敏感技能描述)。A2A 的应对是分层:
- 公开名片:放在 well-known URI 上,只放可公开的基本信息;敏感字段不放。
- Extended(扩展)名片:鉴权后才给的、更详细的版本。客户端先走 OAuth 等拿到凭证,再调
GetExtendedAgentCard(specification/a2a.proto:122)取扩展名片。
要用扩展名片,agent 须在 capabilities.extended_agent_card 声明支持;否则调用要返回 UnsupportedOperationError,声明了但没配置则返回 ExtendedAgentCardNotConfiguredError(docs/specification.md:575)。
规范还建议名片端点用标准 HTTP 缓存(Cache-Control + ETag,ETag 可由 version 派生),客户端用 If-None-Match 做条件请求(docs/topics/agent-discovery.md:96-106)。
5. 让名片可验真:JWS 签名 + RFC 8785 规范化
要解决的问题。 你从某个 URL 拿到一张名片,怎么确认它确实来自声称的提供方、且没被中间人改过?A2A 的答案是给名片加 JWS(JSON Web Signature,RFC 7515) 签名,放进 signatures 字段(AgentCardSignature,specification/a2a.proto:456)。
最巧妙的一步:签名前必须“规范化(canonicalize)”。 同一份 JSON,不同库序列化出来的字节可能不同(键顺序、空格、默认值要不要写)。要让“签名时算的字节”和“验签时重建的字节”一字不差,必须先做 JSON Canonicalization Scheme(JCS,RFC 8785):键按字典序、统一数字/字符串表示、去掉无意义空白(docs/specification.md:2018-2021)。
更微妙的是默认值的取舍——这里 proto 的字段存在性语义直接决定规范化结果(docs/specification.md:2012-2016):
- 标了
optional且显式设过的字段(哪怕值等于默认)→ 保留; - 标了
optional但没设的 → 删除; - 标了
REQUIRED的 → 始终保留(即使是默认值); - 其余带默认值的字段 → 删除(除非 REQUIRED 或 optional)。
规范给的例子(docs/specification.md:2025-2056):一个含 streaming:false、extensions:[] 的片段,因为 streaming 是显式设的 optional 要留、extensions 是空的非必填 repeated 要删,最终规范化成:
{"capabilities":{"pushNotifications":false,"streaming":false},"description":"","name":"Example Agent","skills":[]}
签名/验签流程(简化):
签名方 验签方
────── ──────
1. 删默认值字段 + 删 signatures 1. 取 signatures[i]
2. RFC 8785 规范化 → payload 2. 据 kid/jku 取公钥
3. 造 protected 头(alg/typ/kid) 3. 删默认值字段 + 删 signatures
4. 签名 base64url(头)+"."+payload 4. RFC 8785 规范化
5. 装进 AgentCardSignature 5. 验签
关键点(docs/specification.md:2023、:2117-2134):signatures 字段本身必须排除在被签内容之外(否则循环依赖);客户端应至少验一个签名再信任名片;支持多签名以便密钥轮换。
6. 边界与坑
- 注册表 API 没标准化——跨注册表互通仍需各自约定。
- 名片是“声明”,不是“保证”:agent 声称支持某技能,不等于一定成功。规范明确技能“largely descriptive”(
specification/a2a.proto:391-393)。 - 规范化极易踩坑:默认值删错一个、键序错一处,验签就失败。务必严格按 §5.7 的存在性规则。
7. 代码地图
| 主题 | 文件 | 符号 / 锚点 |
|---|---|---|
| 名片结构 | specification/a2a.proto | message AgentCard(:361) |
| 多传输接口声明 | specification/a2a.proto | message AgentInterface(:336) |
| 技能描述 | specification/a2a.proto | message AgentSkill(:435) |
| 能力开关 | specification/a2a.proto | message AgentCapabilities(:411) |
| 签名对象 | specification/a2a.proto | message AgentCardSignature(:456) |
| 发现策略 | docs/topics/agent-discovery.md | “Discovery Strategies” |
| 签名规则 | docs/specification.md | §8.4(:2002) |
| 扩展名片 RPC | specification/a2a.proto | rpc GetExtendedAgentCard(:122) |