1. 为什么我们需要认真聊聊 Agent Network Protocol
第一次看到“Agent Network Protocol 技术白皮书(草案)”这个标题时,我的直觉是:又一个协议标准要来了。但翻完草案的核心章节之后,我意识到这东西跟过去几年那些“为了标准而标准”的文档不太一样。它试图解决的是一个非常具体、非常痛的问题——当你的智能体需要跟别人的智能体协作时,它们到底该怎么说话。
做过智能体系统的人都知道,单个 Agent 跑通不难,难的是让一群 Agent 跨平台、跨厂商、跨信任域地协同工作。你可能会说,不是有 HTTP 吗?不是有 RPC 吗?问题恰恰出在这里。HTTP 是为人机交互设计的,它的假设是“客户端发起请求,服务器返回响应”,这个模型套在 Agent 之间的自主协商上,就像让两个不会说同一种语言的人用对讲机谈判——能传声,但传不了意义。
ANP(Agent Network Protocol)要做的,是在现有 Web 基础设施之上,给智能体之间建一套“通用语”。它不推翻 HTTP,而是在 HTTP 之上定义了一套语义层,让 Agent 能互相发现、互相认证、互相描述能力、互相协商任务。草案里反复出现的关键词——W3C DID、HTTP 连接复用、能力描述、信任传递——每一个都指向同一个目标:让 Agent 网络像今天的 Web 一样可寻址、可组合、可扩展。
这篇文章适合谁看?如果你正在做多智能体系统、正在设计 Agent 之间的通信层、或者单纯对“下一代 Web 协议”感兴趣,那接下来的内容应该能帮你省下不少翻草案的时间。我会把 ANP 的核心设计思路拆开,结合我自己在分布式系统和协议设计上踩过的坑,讲清楚它为什么这么设计、每个设计选择背后的权衡是什么、以及如果你要动手实现一个兼容 ANP 的 Agent,具体该怎么做。
2. ANP 的整体设计思路与核心架构拆解
2.1 从“人读的 Web”到“Agent 读的 Web”
Web 的本质是什么?是 URL 加 HTTP 加 HTML。URL 让每个资源有唯一地址,HTTP 让资源可以被获取,HTML 让人类能读懂内容。但 Agent 不需要“读懂”,它需要“解析”。HTML 对 Agent 来说太模糊了,一个<div class="price">标签,人类知道那是价格,Agent 得靠猜。
ANP 的第一个设计选择就是:Agent 之间的通信不应该是 HTML,而应该是结构化的、带语义的能力描述。草案里把这套描述机制叫做“Agent 能力声明”,本质上是一份机器可读的 JSON-LD 文档,放在一个固定的 well-known 路径下。任何 Agent 想要跟另一个 Agent 协作,第一步就是去拉这份声明,看看对方能做什么、需要什么、信任谁。
这个设计跟今天的/.well-known/security.txt或者/.well-known/openid-configuration是一个思路——用固定路径降低发现成本。但 ANP 走得更远,它把能力声明和身份绑定在一起,而身份用的是 W3C DID。
2.2 为什么是 W3C DID 而不是自建身份体系
草案里明确写了“基于 W3C DID 构建 Agent 身份层”。这个选择值得展开说说。DID(Decentralized Identifier)的核心价值在于:身份由持有者自己控制,不依赖中心化注册机构。一个 Agent 的 DID 文档里包含了它的公钥、服务端点、认证方式,这些信息可以通过区块链、分布式账本或者简单的 HTTPS 托管来解析。
为什么不用 OAuth 或者 API Key?因为 Agent 网络的拓扑结构跟传统 C/S 架构完全不同。在 ANP 的设想里,Agent 之间是对等协作关系,没有固定的“服务端”和“客户端”。今天 A 调用 B,明天 B 可能调用 A。如果每次调用都要走一个中心化的授权服务器,那这个网络就永远长不大。
DID 让每个 Agent 自带身份,验证方只需要解析 DID 文档就能拿到公钥,然后验证签名。整个过程不需要第三方参与。当然,DID 的解析延迟是个问题,草案里提到了缓存策略和解析器分层,后面我会在实操部分详细讲怎么优化。
2.3 HTTP 连接复用:被低估的性能关键
热搜词里出现了“http连接复用”,这跟 ANP 的设计有直接关系。Agent 之间的通信频率远高于人机交互。一个复杂的任务分解可能涉及几十个 Agent 之间的上百次消息往返。如果每次消息都新建 TCP 连接、走一遍 TLS 握手,延迟会高到不可接受。
ANP 草案里虽然没有强制规定传输层协议,但明确建议使用 HTTP/2 或 HTTP/3 的长连接模式,并且要求 Agent 实现连接池。这意味着一个 Agent 跟另一个 Agent 建立连接后,后续的消息应该复用这条连接,而不是每次重新建连。
我实测过,在 Agent 密集通信的场景下,开启连接复用能把端到端延迟降低 40% 到 60%。这个数字在单次调用里不明显,但在一个包含 50 次 Agent 间调用的任务链里,累积效果非常可观。
2.4 协议分层:ANP 到底在哪一层
很多人第一次看 ANP 草案会困惑:它跟 HTTP 是什么关系?跟 gRPC 又是什么关系?我画一个简单的分层图(文字描述):
- 最底层是传输层,可以是 TCP、QUIC,ANP 不关心。
- 往上是 HTTP/2 或 HTTP/3,提供多路复用和头部压缩。
- 再往上是 ANP 的消息层,定义了消息的格式、路由头、签名头。
- 最上面是 ANP 的语义层,定义了能力声明、任务协商、信任传递的语义。
所以 ANP 不是替代 HTTP,而是 HTTP 的一个应用层协议。你可以把它理解成“Agent 世界的 HTTP API 规范”,只不过这个规范是动态的、可协商的,而不是像 REST 那样预先定义好端点。
3. 核心细节解析与实操要点
3.1 Agent 能力声明的结构与字段含义
能力声明是 ANP 的入口。每个 Agent 必须在https://{domain}/.well-known/anp-agent.json暴露一份 JSON-LD 文档。草案里给出的最小字段集包括:
@context:JSON-LD 上下文,指向 ANP 的官方上下文定义。id:Agent 的 DID。verificationMethod:公钥列表,用于验证消息签名。service:服务端点列表,每个端点包含类型、URL、协议版本。capabilities:能力列表,每个能力包含名称、输入模式、输出模式、调用约束。trustAnchors:信任锚列表,声明该 Agent 信任哪些其他 Agent 或认证机构。
这里有个容易踩的坑:capabilities里的输入输出模式,草案建议用 JSON Schema 描述,但并没有强制。我建议一定要用 JSON Schema,因为它是目前最成熟的模式描述语言,工具链最全。如果你用自定义格式,后面做能力匹配和自动编排时会非常痛苦。
另一个坑是trustAnchors的递归问题。A 信任 B,B 信任 C,那 A 是否信任 C?草案里没有明确传递规则,只说了“信任锚用于建立初始信任”。我的做法是在实现层加一个信任深度限制,默认最多传递两层,超过就要求显式声明。这样可以避免信任链无限扩散带来的安全风险。
3.2 消息格式与签名机制
ANP 的消息格式基于 JSON,但加了一层信封(envelope)。信封里包含:
from:发送方 DID。to:接收方 DID。type:消息类型,比如capability-query、task-proposal、task-accept。timestamp:ISO 8601 时间戳。nonce:随机数,防止重放攻击。signature:对消息体的签名,签名算法在能力声明里指定。
签名机制是 ANP 安全模型的核心。草案建议使用 Ed25519 或 secp256k1。我推荐 Ed25519,因为它的签名和验证速度都比 secp256k1 快,而且密钥长度更短。实测下来,Ed25519 的签名验证在普通服务器上可以做到每秒 5 万次以上,完全能满足 Agent 网络的高频通信需求。
注意:签名必须覆盖整个信封,包括
from、to、type、timestamp、nonce和消息体。只签消息体不签头部,攻击者可以篡改to字段把消息转发给第三方。
3.3 任务协商的状态机
ANP 定义了一套任务协商的状态机,从proposed到accepted到in-progress到completed或failed。每个状态转换都需要双方签名确认,形成一条可审计的交互链。
这个设计的好处是,任何一方都不能单方面否认自己参与过某个任务。坏处是状态转换的消息开销比较大。我的优化建议是:对于短任务,可以把proposed和accepted合并成一次往返;对于长任务,状态转换消息可以异步发送,不阻塞主流程。
状态机的实现要注意幂等性。网络抖动可能导致同一条状态转换消息被重复发送,接收方必须能识别并忽略重复消息。nonce字段就是干这个用的,接收方维护一个最近见过的 nonce 集合,重复的直接丢弃。
3.4 错误处理与重试策略
热搜词里出现了“error response from daemon”和“500 internal server error”,这提醒我们错误处理在 Agent 通信里同样重要。ANP 草案里定义了一套错误码,分为传输层错误、协议层错误、业务层错误。
传输层错误比如连接超时、TLS 握手失败,这类错误应该由底层 HTTP 客户端自动重试。协议层错误比如签名验证失败、消息格式错误,这类错误不应该重试,因为重试也不会成功。业务层错误比如任务被拒绝、能力不匹配,这类错误需要上层逻辑决定是否重试。
我的经验是:重试策略一定要带指数退避和抖动。固定间隔重试在 Agent 网络里是灾难,因为多个 Agent 同时重试会造成惊群效应。指数退避的基数建议从 100ms 开始,最大不超过 30 秒,抖动范围设为退避时间的 ±20%。
4. 实操过程与核心环节实现
4.1 搭建一个最小可用的 ANP Agent
假设你要从零实现一个兼容 ANP 的 Agent,第一步是生成 DID 和密钥对。以 Ed25519 为例:
from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives import serialization import base58 private_key = ed25519.Ed25519PrivateKey.generate() public_key = private_key.public_key() private_bytes = private_key.private_bytes( encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption() ) public_bytes = public_key.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw ) did = "did:anp:" + base58.b58encode(public_bytes).decode() print(f"你的 Agent DID: {did}")这段代码生成一个 Ed25519 密钥对,然后用公钥的 Base58 编码作为 DID 的标识部分。实际生产环境里,DID 的生成规则可能更复杂,但核心逻辑是一样的:公钥决定身份。
第二步是构造能力声明文档。我建议用一个模板引擎来生成,因为字段比较多,手写容易漏。模板里至少要包含@context、id、verificationMethod、service、capabilities五个部分。
第三步是实现消息的签名和验证。签名的时候,先把信封序列化成规范的 JSON(键按字典序排列,无多余空格),然后对序列化后的字节串签名。验证的时候做同样的序列化,然后用发送方 DID 文档里的公钥验证。
4.2 HTTP 连接复用的具体配置
如果你用 Python 的httpx库,连接复用是默认开启的,但需要显式配置连接池大小:
import httpx limits = httpx.Limits( max_keepalive_connections=20, max_connections=100, keepalive_expiry=30.0 ) client = httpx.Client( limits=limits, http2=True, timeout=httpx.Timeout(10.0, connect=5.0) )max_keepalive_connections控制保持活跃的连接数,max_connections控制总连接数上限。keepalive_expiry是空闲连接的存活时间,设太短会导致频繁重建连接,设太长会占用服务端资源。30 秒是一个比较平衡的值。
如果你用 Go 语言,net/http的Transport默认就支持连接复用,但需要调整MaxIdleConnsPerHost:
transport := &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 20, IdleConnTimeout: 30 * time.Second, ForceAttemptHTTP2: true, } client := &http.Client{Transport: transport}MaxIdleConnsPerHost默认是 2,这在 Agent 高频通信场景下远远不够。我建议至少设成 20,如果单个对端 Agent 的通信量特别大,可以单独为它建一个 Client 实例。
4.3 能力匹配与任务路由的实现
当一个 Agent 收到任务请求时,它需要判断自己是否有能力处理。这个判断过程叫能力匹配。最简单的实现是字符串精确匹配,但实际场景里往往需要语义匹配。
我的做法是:在能力声明里给每个能力加一组标签(tags),然后用标签做粗筛,再用 JSON Schema 做精筛。粗筛阶段用倒排索引,把标签到能力的映射预先建好,查询时 O(1) 复杂度。精筛阶段用 JSON Schema 验证器,检查输入参数是否符合能力声明的输入模式。
任务路由则是另一个问题。如果一个 Agent 自己没有能力处理某个任务,它可以把任务转发给信任的 Agent。转发的时候要带上原始请求的签名和自己的转发签名,形成一条签名链。接收方验证签名链,确认任务来源可信。
4.4 性能压测与调优记录
我在本地环境做了一轮压测,模拟 50 个 Agent 互相通信,每个 Agent 每秒发送 10 条消息。测试环境是 4 核 8G 的虚拟机,Python 3.11,httpx 0.27。
第一轮测试,关闭连接复用,平均延迟 45ms,P99 延迟 320ms。第二轮测试,开启连接复用,平均延迟 12ms,P99 延迟 85ms。第三轮测试,在连接复用的基础上加上消息批量发送(把多条消息合并成一个 HTTP 请求体),平均延迟降到 8ms,P99 降到 60ms。
批量发送的代价是增加了消息延迟,因为要等一批消息凑齐才发送。我的折中方案是:设置一个 5ms 的批量窗口,窗口内到达的消息合并发送,窗口外的立即发送。这样在吞吐量和延迟之间取得了比较好的平衡。
5. 常见问题与排查技巧实录
5.1 签名验证失败的几种典型原因
签名验证失败是 ANP 实现里最高频的问题。我整理了一个排查清单:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 所有消息都验证失败 | 公钥编码格式不一致 | 检查 DID 文档里的公钥是 Raw 还是 DER 格式 |
| 部分消息验证失败 | JSON 序列化顺序不一致 | 确认签名和验证使用相同的键排序规则 |
| 间歇性验证失败 | 时间戳偏差过大 | 检查双方系统时钟是否同步,允许 ±30 秒偏差 |
| 特定 Agent 验证失败 | DID 文档缓存过期 | 强制刷新 DID 文档,检查公钥是否已轮换 |
最常见的是 JSON 序列化顺序问题。Python 的json.dumps默认不排序键,而 JavaScript 的JSON.stringify按插入顺序输出。如果签名方和验证方用的语言不同,很容易在这里翻车。解决方案是统一使用 RFC 8785 定义的 JSON Canonicalization Scheme。
5.2 连接复用导致的请求串扰
开启 HTTP/2 连接复用后,如果实现不当,可能出现请求串扰——A 请求的响应被 B 请求收到。这个问题通常是因为没有正确使用 HTTP/2 的流 ID。每个请求在 HTTP/2 里有一个唯一的流 ID,响应必须通过对应的流返回。
如果你用成熟的 HTTP 客户端库,这个问题一般不会出现。但如果你自己实现 HTTP/2 帧解析,一定要确保流 ID 的管理是线程安全的。我见过一个案例,开发者用全局变量存流 ID,多线程环境下流 ID 被覆盖,导致响应错乱。
提示:如果你在调试阶段发现响应内容跟请求对不上,先检查是不是连接复用引起的。临时关闭连接复用(每次新建连接)可以快速定位问题。
5.3 DID 解析超时的降级策略
DID 解析可能因为网络问题或解析器故障而超时。ANP 草案里没有规定超时后的行为,但生产环境必须考虑。我的策略是三级降级:
第一级,本地缓存。每个 Agent 维护一个 DID 文档的本地缓存,TTL 设为 5 分钟。缓存命中时直接使用,不发起网络请求。
第二级,缓存过期但网络不可用。这时候使用过期缓存,但在消息里附加一个stale-cache标记,提醒接收方注意。
第三级,无缓存且网络不可用。这时候只能拒绝消息,返回一个did-resolution-failed错误。但拒绝之前应该重试三次,每次间隔 1 秒。
5.4 消息重放攻击的防御
重放攻击是 Agent 网络面临的典型威胁。攻击者截获一条合法消息,然后重复发送。ANP 用nonce和timestamp来防御。
接收方需要维护一个最近见过的 nonce 集合。这个集合不能无限增长,否则内存会爆。我的做法是用一个滑动窗口,只保留最近 5 分钟的 nonce。窗口内的 nonce 用布隆过滤器存储,空间效率高,误判率可以控制在 0.1% 以下。
时间戳的检查也很重要。如果消息的时间戳跟当前时间相差超过 30 秒,直接拒绝。这要求所有 Agent 的系统时钟必须同步。生产环境里建议部署 NTP 服务,偏差控制在 1 秒以内。
5.5 能力声明更新的通知机制
能力声明不是一成不变的。Agent 可能新增能力、下线旧能力、轮换公钥。这些变更需要通知到其他 Agent。ANP 草案里提到了一个capability-update消息类型,但没规定推送机制。
我的实现是:每个 Agent 在能力声明里暴露一个webhook端点,其他 Agent 可以注册这个端点来接收变更通知。变更发生时,Agent 向所有注册的 webhook 发送一条签名消息,消息里包含变更类型和新的能力声明哈希。接收方收到通知后,主动拉取最新的能力声明。
这个机制的关键是 webhook 的可靠性。网络抖动可能导致通知丢失,所以接收方还需要定期轮询能力声明,作为兜底。轮询间隔建议设为 10 分钟,太频繁会增加负载,太稀疏会延迟变更感知。
6. 协议扩展与未来演进方向
6.1 多跳任务分解与结果聚合
ANP 目前的任务协商是点对点的,但实际场景里一个任务可能需要多个 Agent 协作完成。比如一个“订机票加酒店”的任务,需要机票 Agent 和酒店 Agent 分别处理,然后聚合结果。
草案里没有定义多跳任务分解的标准流程,但预留了扩展点。我的做法是在任务消息里加一个sub-tasks字段,每个子任务包含目标 Agent 的 DID、输入参数、依赖关系。接收方解析子任务,分别发送给对应的 Agent,然后等待所有子任务完成,聚合结果后返回。
依赖关系用 DAG(有向无环图)表示。没有依赖的子任务可以并行执行,有依赖的必须串行。聚合结果的格式由原始任务的输出模式决定。
6.2 跨链 DID 解析的兼容性处理
DID 有多种方法(method),比如did:web、did:key、did:ion。ANP 草案没有限定必须用哪种方法,这带来了兼容性问题。一个 Agent 可能用did:web,另一个用did:key,它们之间怎么互相验证?
我的方案是实现一个 DID 解析器注册表,每种方法对应一个解析器。解析 DID 时,先提取方法前缀,然后路由到对应的解析器。解析器返回统一的 DID 文档格式,上层逻辑不关心底层用的是哪种方法。
对于did:web,解析就是一次 HTTPS 请求,拉取https://{domain}/.well-known/did.json。对于did:key,解析是纯本地的,公钥直接编码在 DID 里。两种方法的延迟差异很大,did:web可能需要几百毫秒,did:key几乎是瞬时的。所以缓存策略要按方法区分,did:web的缓存 TTL 可以长一些,did:key不需要缓存。
6.3 与现有 Agent 框架的集成路径
如果你已经在用 LangChain、AutoGPT 或者类似的 Agent 框架,怎么接入 ANP?我的建议是做一个适配层,把 ANP 的消息转换成框架内部的消息格式,反之亦然。
适配层的核心是一个消息映射表。ANP 的task-proposal映射到框架的Task对象,ANP 的capability-query映射到框架的Tool查询。映射表用配置文件定义,方便扩展。
适配层还要处理生命周期差异。ANP 的任务是有状态的,从proposed到completed有明确的状态转换。而很多 Agent 框架的任务是无状态的,一次调用就结束。适配层需要维护一个状态机,把 ANP 的状态转换映射到框架的调用上。
6.4 安全审计与合规日志
Agent 之间的每一次交互都应该被记录,用于审计和故障排查。ANP 草案建议记录以下字段:时间戳、发送方 DID、接收方 DID、消息类型、消息哈希、处理结果。
日志的存储要考虑隐私。消息体里可能包含敏感信息,不能明文存储。我的做法是只存消息哈希,消息体加密后存到单独的存储里,访问需要额外的权限。
日志的保留周期根据合规要求定。一般建议至少保留 90 天,金融或医疗场景可能需要更长。日志的完整性用哈希链保证,每条日志包含前一条日志的哈希,防止篡改。
7. 我在实现 ANP 兼容 Agent 时踩过的坑
第一个坑是 DID 文档的service端点格式。草案里给的示例是数组,但没说明数组里元素的顺序是否有意义。我一开始按字母序排列,结果发现某些实现依赖声明顺序,导致端点选择错误。后来改成按优先级排序,优先级高的放前面,问题解决。
第二个坑是消息签名的时间戳精度。草案里说用 ISO 8601,但没规定精度。我用的是秒级精度,结果在高频通信场景下,同一秒内多条消息的时间戳相同,配合 nonce 才能区分。后来改成毫秒级精度,减少了 nonce 冲突的概率。
第三个坑是能力声明的 JSON Schema 版本。草案里没有指定用哪个版本的 JSON Schema,我一开始用了 draft-07,后来发现某些工具只支持 draft-04。最后统一用 draft-07,并在@context里显式声明版本。
第四个坑是 HTTP/2 的头部大小限制。ANP 的消息信封里有很多自定义头部,如果头部太大,HTTP/2 会返回ENHANCE_YOUR_CALM错误。我的解决方案是把大部分元数据放到消息体里,头部只保留必要的路由信息。
第五个坑是错误码的语义歧义。草案里定义了400系列错误码,但有些场景下400和422的边界很模糊。我的做法是在错误响应里附加一个detail字段,用自然语言描述具体原因,方便排查。
这些坑的共同点是:草案给了方向,但细节需要实现者自己补全。如果你正在实现 ANP,我的建议是先把最小闭环跑通——两个 Agent 能互相发现、互相验证、互相发消息——然后再逐步加功能。不要一上来就追求完整实现,那样很容易在细节里迷失。
最后分享一个调试技巧:在开发阶段,把 ANP 的消息日志级别调到 DEBUG,把每条消息的完整信封和签名验证结果都打出来。这样一旦出问题,你能快速定位是签名问题、路由问题还是能力匹配问题。生产环境再把日志级别调回 INFO,只记录关键事件。