ANP协议解析:智能体通信的身份、发现与消息分层设计 1. 为什么智能体之间需要一套自己的通信语言过去一年我接触了不少智能体项目从扣子、Dify 这类平台搭建的到用 Python 从零手写的 ReAct 智能体几乎每个团队都会在某个阶段撞上同一堵墙单个智能体跑得挺好一旦想让两个或多个智能体协作整个系统就开始变得脆弱、混乱、难以调试。Agent Network ProtocolANP这份技术白皮书草案想解决的正是这堵墙背后的根本问题——智能体之间到底该怎么说话。先把结论摆在前面ANP 要做的不是又一个 RPC 框架也不是又一个消息队列协议而是一套面向开放网络中的异构智能体的通信与身份层。它要回答三个问题我是谁身份、我怎么找到你发现、我们用什么格式交换信息消息与语义。这三个问题听起来简单但每一个在真实工程里都能把人折磨到怀疑人生。为什么不能用现成的 HTTP JSON 直接搞定我一开始也是这么想的。实测下来问题出在几个地方。第一智能体是活的它的能力会动态变化今天能查天气明天可能接入了新的工具静态的接口定义跟不上。第二智能体可能由不同厂商、不同框架构建扣子搭的和 Python 手写的语义理解方式完全不同需要一个双方都能接受的中间语义层。第三也是最容易被忽略的——身份。当 A 智能体收到 B 智能体的请求时它凭什么相信 B 就是 B而不是某个冒充者这在多智能体协同、智能体行为审计场景里是绕不过去的。ANP 的关键词里出现了 DID这不是偶然。DID去中心化标识符提供了一种不依赖中心化账号体系的身份表达方式每个智能体可以拥有一个可验证的、自主控制的身份标识。把 DID 和通信协议结合意味着智能体的身份和它的消息可以绑定验证这对构建可信的多智能体网络是基础性的。这篇文章我会从协议设计的核心动机讲起拆解 ANP 的几层结构重点讲清楚身份层、发现层、消息层各自解决什么问题然后落到实操——如果你现在就想在自己的项目里引入类似 ANP 的思路该怎么起步有哪些坑我踩过。适合正在做智能体开发、多智能体协同、或者对智能体架构感兴趣的读者不管你是用平台还是手写代码都能从中找到可复用的部分。2. ANP 的分层设计身份、发现、消息三件事分开做2.1 为什么不能把三件事揉在一起很多团队做智能体通信时习惯性地把身份认证、服务发现、消息格式全塞进一个自定义的 JSON 结构里比如{ from: agentA, to: agentB, auth: xxx, payload: {...} }。这种一锅炖的做法在只有两三个智能体、且都是自己写的时候没问题但一旦规模上去、参与方变多就会变成灾难。原因很简单这三件事的变化频率完全不同。身份体系一旦确定可能几个月都不动服务发现会随着智能体上下线频繁变化消息格式则可能因为业务需求每周都在调整。把它们耦合在一起改任何一层都要动全部测试成本指数级上升。ANP 把这三层拆开各自有独立的抽象和演进节奏。这是我认为这份草案最值得借鉴的设计决策哪怕你最后不用 ANP这个分层思路也值得抄。2.2 身份层DID 到底解决了什么先说身份层。传统做法是给每个智能体分配一个 ID存在数据库里通信时带上这个 ID。问题在于这个 ID 是中心化系统发的跨系统就不认了。你的智能体在 A 平台有个 ID到了 B 平台就是陌生人。DID 的思路是让标识符本身携带可验证性。一个 DID 通常长这样did:example:123456789abcdefghi它是一串自描述的字符串配套一份 DID Document里面包含公钥、服务端点等信息。验证方拿到 DID 后可以通过解析 Document 拿到公钥进而验证对方签名。落到智能体场景这意味着智能体 A 用自己的私钥对消息签名智能体 B 通过 A 的 DID 解析出公钥来验签。整个过程不需要任何中心化机构介入也不需要 B 事先认识 A。这就是开放网络的前提。我在实际项目里试过用简单的 HMAC 共享密钥做智能体认证小规模能用但密钥分发本身就是个麻烦事而且一旦某个智能体被攻破所有共享密钥的智能体都受影响。DID 这种非对称的方式每个智能体独立持有私钥风险隔离得好得多。注意DID 的解析在真实网络里是有成本的尤其是跨链或跨方法did:web、did:key 等解析时。如果你的智能体通信是高频低延迟场景别每条消息都去解析 DID应该做本地缓存并设置合理的过期策略。2.3 发现层智能体怎么找到彼此身份解决了你是谁接下来是我怎么找到你。发现层要处理的是给定一个能力需求比如我需要一个能做图像识别的智能体如何找到具备这个能力的智能体。ANP 草案里这块的思路偏向于能力描述 注册/查询。每个智能体在 DID Document 或独立的描述文档里声明自己具备哪些能力、支持哪些消息格式、服务端点在哪儿。查询方通过某种目录服务或点对点方式获取这些描述。这里有个工程上的取舍中心化目录 vs 去中心化发现。中心化目录实现简单、查询快但单点故障和信任问题明显去中心化发现更符合开放网络的理念但实现复杂、查询效率低。我的经验是早期项目用中心化目录快速跑通等规模上来再逐步引入去中心化机制不要一上来就追求理想架构。2.4 消息层语义互操作才是真难点消息层是最容易被低估的一层。很多人以为定义个 JSON schema 就完事了但真正的难点在语义。智能体 A 说帮我订一张明天去北京的票智能体 B 理解成订机票智能体 C 理解成订火车票这就是语义鸿沟。ANP 在消息层需要处理的不只是传输格式还有意图表达、上下文传递、错误语义等。这部分草案里还在演进但从关键词通信协议服务通信协议层来看它试图在传输之上建立一个语义协商机制。我的实践体会是语义互操作没有银弹最务实的做法是定义一套核心意图词汇表类似对话行为理论里的 speech act复杂语义通过组合核心意图来表达。这样既保证了互操作性又留了扩展空间。3. 从零跑通一个最小 ANP 风格通信我的实操路径3.1 环境与依赖准备假设你现在想在自己的项目里落地一套 ANP 风格的通信我建议从最小可用版本开始。下面是我实际用过的一套技术选型你可以直接参考。语言Python 3.10生态成熟智能体相关库多身份先用did:key方法实现最简单不需要网络解析消息传输HTTP JSON别一上来就上消息队列签名Ed25519性能好、密钥短序列化JSON可读性优先调试方便安装依赖pip install cryptography httpx pydanticcryptography负责 Ed25519 签名验签httpx做 HTTP 通信pydantic做消息结构的校验。这三个库足够跑通最小版本。3.2 生成智能体身份并构造 DID第一步是给智能体生成身份。用 Ed25519 生成密钥对然后基于公钥构造一个did:key标识符。from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives import serialization import base58 private_key Ed25519PrivateKey.generate() public_key private_key.public_key() public_bytes public_key.public_bytes( encodingserialization.Encoding.Raw, formatserialization.PublicFormat.Raw ) did did:key:z base58.b58encode(public_bytes).decode() print(did)这里用 base58 编码是为了让 DID 字符串更紧凑、更适合在 URL 里传递。did:key的特点是公钥直接编码在 DID 里解析时不需要任何网络请求这对早期开发非常友好。提示生产环境如果要用did:web或更复杂的方法解析逻辑会不一样但签名验签的核心流程是一致的。先把did:key跑通理解原理后再换。3.3 消息签名与验签的完整流程有了身份接下来是消息签名。每条发出的消息都要带上发送方的 DID 和签名接收方验签后才处理。import json, time def sign_message(private_key, did, payload): message { from: did, timestamp: int(time.time()), payload: payload } canonical json.dumps(message, sort_keysTrue, separators(,, :)) signature private_key.sign(canonical.encode()) message[signature] base58.b58encode(signature).decode() return message注意这里用了sort_keysTrue和紧凑分隔符目的是让序列化结果确定化。如果签名和验签两边的 JSON 序列化方式不一致验签必然失败。这是我在实际项目里踩过的坑——不同语言的 JSON 库默认行为不同跨语言通信时一定要约定好规范化方式。验签方拿到消息后从from字段解析出公钥重新序列化去掉签名的部分然后验签def verify_message(message): signature base58.b58decode(message.pop(signature)) did message[from] public_bytes base58.b58decode(did.replace(did:key:z, )) public_key Ed25519PublicKey.from_public_bytes(public_bytes) canonical json.dumps(message, sort_keysTrue, separators(,, :)) public_key.verify(signature, canonical.encode())验签通过才继续处理 payload否则直接拒绝。这一步看似简单但它是整个可信通信的基石。3.4 能力声明与发现的最小实现发现层的最小实现可以很简单每个智能体暴露一个/agent-card端点返回自己的能力描述。agent_card { did: did, name: image-recognizer, capabilities: [image.classify, image.describe], endpoint: http://localhost:8001, protocol_version: anp-draft-0.1 }查询方通过已知的目录服务或直接访问对方的 card 端点来获取能力信息。早期不需要复杂的目录维护一个静态的智能体列表就够了。3.5 跑通第一个跨智能体调用把上面几块拼起来就能跑通一个最小调用链智能体 A 发现智能体 B 的能力构造请求消息签名后通过 HTTP 发给 BB 验签、处理、返回结果。import httpx def call_agent(target_card, payload, private_key, my_did): message sign_message(private_key, my_did, payload) resp httpx.post(f{target_card[endpoint]}/invoke, jsonmessage) return resp.json()实测下来这套最小实现大概两百行代码就能跑通足够验证你的多智能体协作想法。等验证通过再考虑引入更复杂的发现机制、消息队列、错误重试等。4. 那些文档不会告诉你的坑4.1 时间戳与重放攻击签名能保证消息没被篡改但保证不了消息没被重放。攻击者可以把一条合法消息原样再发一次。解决办法是在消息里加时间戳和 nonce接收方维护一个近期 nonce 集合重复的直接拒绝。我一开始没做这个测试时发现同一个请求被处理了两次排查了半天才意识到是重试机制导致的重复投递。加上 nonce 校验后问题解决。时间戳窗口别设太大一般 5 分钟足够设太大等于没设。4.2 跨语言序列化的规范化陷阱前面提过 JSON 序列化的问题这里展开说。Python 的json.dumps默认会在,和:后加空格JavaScript 的JSON.stringify不加。如果签名方用 Python、验签方用 Node.js不做规范化处理必然失败。解决方案有两种一是约定统一的规范化算法类似 JCSJSON Canonicalization Scheme二是干脆用二进制序列化如 CBOR。我倾向第一种因为可读性对调试太重要了。跨语言项目里规范化这块一定要写测试用例覆盖别靠人肉检查。4.3 DID 解析的性能与缓存did:key不需要网络解析但did:web需要。如果你的智能体网络里有大量did:web身份每次验签都去解析会拖垮性能。必须做缓存而且要处理缓存失效。我的做法是本地维护一个 DID Document 缓存带 TTL默认 1 小时。同时提供一个强制刷新的接口当验签失败且怀疑是公钥轮换导致时主动刷新一次再验。这个失败后刷新重试的逻辑很实用能覆盖大部分密钥轮换场景。4.4 错误语义的统一智能体调用失败时返回什么HTTP 500 加一段文字还是结构化的错误对象ANP 风格的做法是定义统一的错误语义让调用方能程序化地处理。我用的错误结构{ error: { code: CAPABILITY_NOT_SUPPORTED, message: requested capability image.segment not available, retryable: false } }code用枚举retryable告诉调用方要不要重试。这个字段看似小但在多智能体协同里能省掉大量猜测逻辑。踩过的坑是早期没定义retryable调用方对所有错误都重试结果把不可恢复的错误重试到超时。4.5 智能体行为审计的埋点关键词里出现了智能体行为审计这在多智能体系统里非常重要。每个智能体的每次调用、每次决策都应该留下可追溯的记录。我的做法是在消息层统一埋点记录消息 ID、发送方、接收方、时间戳、处理结果写入独立的审计日志。审计日志和业务日志要分开存因为审计日志的保留周期通常更长且不能被业务逻辑修改。这块早期不做后期补起来会很痛苦因为历史数据已经丢了。5. 平台智能体与手写智能体在 ANP 下的差异5.1 平台智能体的通信封闭性用扣子、Dify 这类平台搭建的智能体通信方式通常由平台定义对外暴露的接口有限。想让平台智能体参与 ANP 网络往往需要一层适配把平台的调用接口包装成 ANP 消息格式把平台的身份体系映射到 DID。我做过一个扣子智能体接入自建 ANP 网络的适配层核心工作是把平台的 webhook 回调转换成 ANP 消息并给平台智能体分配一个代理 DID。代理 DID 的私钥由适配层持有平台智能体本身不感知 DID 的存在。这样既接入了网络又不用改平台。5.2 手写智能体的灵活性优势用 Python 手写的智能体在 ANP 接入上灵活得多因为你可以完全控制消息的构造和签名流程。缺点是所有基础设施都要自己搭身份管理、发现服务、审计日志都得从零写。我的建议是混合架构核心的、需要深度定制的智能体手写外围的、功能标准的智能体用平台搭中间用适配层打通。这样既保证了关键路径的可控性又利用了平台的开发效率。5.3 两种智能体协同时的语义对齐平台智能体和手写智能体协同时最大的问题是语义对齐。平台智能体的意图理解由平台模型决定手写智能体的意图理解由你自己的 prompt 或逻辑决定两者对同一句话的理解可能不同。解决办法是在 ANP 消息层之上再定义一层意图契约双方都按契约表达和解析意图不直接传递自然语言。这增加了前期设计成本但大幅降低了协同的不确定性。我在一个客服场景的多智能体项目里用了这个思路把意图收敛到二十几个标准动作后协同成功率从七成提升到九成以上。6. 我对 ANP 落地节奏的一点个人判断ANP 现在还处于草案阶段很多细节还在演进现在就想找一个标准实现直接用是不现实的。但这不影响它作为设计参考的价值。我的建议是把 ANP 的分层思想、DID 身份、消息签名验签这几块先在自己的项目里落地哪怕用的是自定义的消息格式只要分层清晰、身份可验证就已经比大多数一锅炖的实现强很多。等 ANP 规范稳定后再把自定义部分替换成标准实现迁移成本主要在消息格式的适配核心的身份和签名逻辑基本不用动。这个渐进路径是我认为最务实的。最后分享一个小技巧在项目早期就引入一个协议版本字段每条消息都带上。这样将来协议升级时你可以根据版本号做兼容处理而不是被迫一次性全量迁移。这个字段现在加进去几乎零成本但将来能救你一命。