☰
一文吃透 A2A 协议:解决 Agent 协作 N² 集成难题,与 MCP 协议的定位与实战
2026/10/15 18:36:05 网站建设 项目流程

1. 多 Agent 协作为什么总在 N² 集成上翻车

你手里有两个 Agent,一个专门做数据分析,一个专门写报告。你想让它们配合出一份市场调研报告。最直觉的做法是什么?把分析 Agent 包装成一个函数,塞进报告 Agent 的 Function Calling 里。跑起来好像也能用,但你其实做了一件很亏的事:把一个能自主推理、能多轮澄清、有状态记忆的智能体,降维成了一个无状态函数。它不再会问“你说的增长是同比还是环比”,也不再会中途告诉你“这份数据缺了 Q3,要不要先补”。

这就是 A2A 协议要解决的根本问题。A2A 是一套让 Agent 与 Agent 之间对等协作的开放协议,它解决的是多 Agent 场景下的 N² 集成难题,适合正在搭多智能体系统、被点对点适配代码拖慢迭代的程序员和 AI 应用开发者。它和 MCP 协议不是竞争关系,而是分别站在“横向协作”和“纵向接工具”两个位置上。

先把 N² 讲清楚。没有标准协议时,每两个 Agent 要协作就得写一份自定义适配代码。3 个 Agent 需要 3 对集成,10 个 Agent 需要 45 对,100 个 Agent 就是 4950 对。集成量随 Agent 数量平方级增长,这就是 N² 困局。它带来的连锁反应很具体:新 Agent 上线要等所有相关方写适配,周期从“天”拖到“周”;不同框架(LangGraph、CrewAI、ADK)搭出来的 Agent 各说各的方言,形成 Agent 孤岛;临时通信方案有的走自定义 HTTP、有的走 WebSocket,没有统一审计路径,企业不敢往关键业务里放。

A2A 的设计哲学可以压成一句话:用最小的协议复杂度,换最大范围的 Agent 安全协作。它不重新发明传输层,直接复用 HTTP、JSON-RPC 2.0 和 SSE;不自定义序列化格式,不发明新安全模型。你现有的 Web 工程经验基本能直接迁移。它对齐 OAuth2/OIDC、TLS、OpenTelemetry 这些企业标准,异步优先,原生支持长运行任务和流式更新,并且坚持“不透明执行”——协作方只依赖 Agent Card 声明的能力和交换的信息,看不到对方内部的记忆、工具和逻辑。

这里必须把 Agent 和 Tool 的区别钉死,因为它是后面所有判断的地基。Tool 是原始件:结构化输入输出、无状态、单一功能,计算器、天气 API、数据库查询都是。Agent 是自主系统:能推理、能规划、能调用多个工具、维护长时间交互状态、在多轮对话里协商和澄清。判断标准很简单——如果交互对象能被包装成一个函数签名(明确的入参类型和出参类型),用 MCP;如果它是一个需要协商、澄清、精炼、委派的自主 Agent,用 A2A。把 Agent 硬塞进 Function Calling,你也许能拿到答案,但丢掉了它的判断力。

2. TaoToken 前置:把跨 Agent 调用的 Key 和通道先统一

在真正跑通一次跨 Agent 调用之前,有个很现实的工程问题:你的多个 Agent 可能各自接不同的模型、各自配一套 Key、各自维护一份 Base URL。一旦要做 A2A 协作,调用链会变成“客户端 Agent → 远程 Agent → 底层模型”,中间任何一环的凭证和通道不统一,排障就会变成灾难。所以我在动手写 A2A 配置前,会先把模型访问层收敛到一条通道上,这里用的是 TaoToken。

TaoToken 在这里扮演的角色是统一的模型访问入口:一个 API Key、一个 Base URL,就能覆盖对话、编码、Agent 等不同调用场景。对 A2A 实战来说,它的价值在于——你的每个 Agent 内部无论用哪个模型,都走同一套凭证和地址,跨 Agent 调用时不会因为“这个 Agent 用 A 家的 Key、那个 Agent 用 B 家的地址”而互相打架。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个不加 UTM)。

先把 Key 拿到手。进入控制台创建 API Key,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建完先复制保存,后面所有 Agent 的模型调用都用它。

如果你只是想先验证模型通道通不通,可以直接用模型对话页试一句: https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。这一步的意义是:在写 A2A 的 JSON-RPC 之前,先确认“模型这一层是活的”,否则后面报错你分不清是 A2A 协议写错了还是模型通道没通。

对于长期要跑编码类 Agent 或做 Agent 编排的场景,可以考虑 Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。它的定位是给持续性的编码/Agent 任务提供稳定的调用额度,避免你在调试 A2A 多轮交互时被额度打断。

如果你用的是 Claude Code 这类工具做 Agent 开发,接入文档在这里: https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,Claude Code 专项接入页是 https://taotoken.net/claudecode?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。把模型通道统一之后,我们再进入 A2A 的协议配置,这样每一步的变量都是可控的。

3. 可复制配置:Agent 注册与任务委派片段

这一节给你能直接抄的配置。A2A 的发现机制核心是 Agent Card,它是一个 JSON 元数据文档,放在标准路径/.well-known/agent-card.json下。客户端只要知道域名,就能拿到这张“数字名片”。先写一个最小可用的 Agent Card,注意securitySchemes对齐 OpenAPI 的认证定义。

{ "name": "report-agent", "description": "负责把结构化数据整理成市场调研报告", "version": "1.0.0", "provider": { "organization": "demo-lab", "url": "https://agents.example.com" }, "supportedInterfaces": [ { "url": "https://agents.example.com/a2a/report", "protocolBinding": "HTTP+JSON", "protocolVersion": "1.0" } ], "capabilities": { "streaming": true, "pushNotifications": false }, "securitySchemes": { "oauth2": { "type": "oauth2", "flows": { "authorizationCode": { "authorizationUrl": "https://auth.example.com/authorize", "tokenUrl": "https://auth.example.com/token", "scopes": { "read:tasks": "读取任务", "write:tasks": "创建和更新任务" } } } } }, "security": [{"oauth2": ["read:tasks", "write:tasks"]}], "skills": [ { "id": "report-generate", "name": "报告生成", "description": "输入结构化数据,输出 Markdown 报告", "tags": ["report", "markdown"], "examples": ["根据这份销售数据生成季度报告"], "inputModes": ["text", "data"], "outputModes": ["text"] } ] }

把这份文件放到https://agents.example.com/.well-known/agent-card.json,服务端就完成了“可被发现”。注意几个容易写错的点:supportedInterfaces里的url是实际交互端点,不是 Card 自己的地址;capabilities.streaming决定客户端能不能用 SSE;skills[].inputModes和outputModes要和你 Agent 真实支持的模态一致,写错了客户端会按错误模态发 Part。

接下来是任务委派。客户端 Agent 向远程 Agent 发消息,用 JSON-RPC 的SendMessage方法。首次请求不带contextId也不带taskId,服务端会生成它们。

{ "jsonrpc": "2.0", "id": "req-001", "method": "SendMessage", "params": { "message": { "role": "ROLE_USER", "messageId": "msg-user-001", "parts": [ { "text": "根据附件数据生成一份市场调研报告", "mediaType": "text/plain" }, { "data": {"quarter": "Q3", "region": "east"}, "mediaType": "application/json" } ] } } }

这里一个 Message 里塞了两个 Part:一个文本指令,一个结构化数据。这就是 A2A 的 Part 统一容器设计——text、raw、url、data四种内容类型可以混发。小文件用raw内联(base64,体积约涨 33%),大文件用url引用,经验值是 64KB 以下用raw、1MB 以上用url。

服务端返回的 Task 会带上contextId和taskId。后续要基于同一对话继续,就回传contextId;要精炼某个已完成 Task 的产出,就再加referenceTaskIds。

{ "jsonrpc": "2.0", "id": "req-002", "method": "SendMessage", "params": { "message": { "role": "ROLE_USER", "messageId": "msg-user-002", "contextId": "ctx-conversation-abc", "referenceTaskIds": ["task-report-123"], "parts": [ {"text": "把报告里的图表换成柱状图", "mediaType": "text/plain"} ] } } }

注意这里不带taskId——因为原 Task 到达终态后不可重启,精炼必须创建新 Task,靠contextId保持上下文连续、靠referenceTaskIds提示引用原产出。这是 A2A 多轮交互最容易踩的坑:很多人以为可以“更新”一个已完成的 Task,结果一直报状态错误。

如果你要把模型调用也统一到 TaoToken,可以在 Agent 内部把模型客户端指向https://taotoken.net/api,Key 用前面创建的。这样无论哪个 Agent 被委派,底层模型通道都是一致的。

4. 验证请求:跑通一次跨 Agent 调用

配置写完必须验证,否则你不知道是协议写错还是通道没通。我习惯分三步验证,从模型通道到 A2A 协议逐层确认。

第一步,先确认模型通道。用 curl 打一次对话接口,确认 Key 和 Base URL 可用。

curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "回复 OK 两个字母"}] }'

返回里能看到choices[0].message.content就说明模型层通了。这一步不通,后面 A2A 一定失败,先别往下走。

第二步,验证 Agent Card 可发现。直接 GET 标准路径。

curl https://agents.example.com/.well-known/agent-card.json

返回的 JSON 里应该有name、supportedInterfaces、skills。如果返回 404,检查文件是否真的放在/.well-known/下;如果返回 HTML,说明你的 Web 框架把静态 JSON 路由拦截了。

第三步,发一次真实的 A2A 请求。用 curl 模拟客户端 Agent 的SendMessage。

curl -X POST https://agents.example.com/a2a/report \ -H "Authorization: Bearer $A2A_TOKEN" \ -H "Content-Type: application/json" \ -H "A2A-Version: 1.0" \ -d '{ "jsonrpc": "2.0", "id": "req-verify-001", "method": "SendMessage", "params": { "message": { "role": "ROLE_USER", "messageId": "msg-verify-001", "parts": [{"text": "生成一份测试报告", "mediaType": "text/plain"}] } } }'

成功的返回长这样:result.task.id有值,result.task.contextId有值,result.task.status.state是TASK_STATE_SUBMITTED或TASK_STATE_WORKING。如果 Agent 支持流式,改用SendStreamingMessage,你会看到 SSE 事件序列:先SUBMITTED,再WORKING,然后artifactUpdate分块推送产出,最后COMPLETED并关闭流。

验证跨 Agent 调用的关键,是确认“委派”这一跳真的发生了。你可以在客户端 Agent 的日志里看到它调用了远程 Agent 的端点,在远程 Agent 的日志里看到它收到了 Task 并调用了底层模型(走 TaoToken 通道)。两边日志用同一个taskId串起来,这条链路就算跑通了。如果远程 Agent 内部还调了 MCP 工具,那部分对客户端是不可见的——这正是 A2A 不透明执行的设计意图。

5. 本篇常见错排查:401、local proxy failed 与 reading choices

这一节按真实报错来。A2A 实战里最耗时间的不是写配置,是排错,而且很多报错看起来像协议问题,其实是凭证或通道问题。

401 Unauthorized。这是最高频的。A2A 把认证凭证放在 HTTP Header,不放 JSON-RPC payload,所以 401 基本是 Header 问题。先确认Authorization头格式对不对(Bearer <token>,注意空格);再确认 token 没过期;再看 Agent Card 里securitySchemes声明的方案和你实际发的是不是一致——Card 说 OAuth2,你却发 API Key,服务端会拒。401 的语义是“你是谁”,响应里通常带WWW-Authenticate头告诉你支持哪种认证。如果返回的是 403,那是“我知道你是谁但你没权限”,去查 scope 是否覆盖了请求的 skill。

local proxy failed。这个报错通常出现在你的 Agent 通过某个本地转发/网关去访问模型或远程 Agent 时。它和 A2A 协议本身无关,是网络层问题。排查顺序:先确认目标地址可达(curl -v看 TCP 是否连上);再确认本地转发进程是否在跑、端口是否被占;然后确认环境变量里的代理配置有没有把请求导向一个不存在的地址。如果你把模型调用统一到了 TaoToken,确认 Base URL 写的是https://taotoken.net/api,别多写或少写路径段。

reading 'choices' of undefined。这个报错几乎总是模型响应结构和你代码预期不一致。常见原因有三个:一是请求根本没成功,返回的是错误对象而不是正常的 completion 结构,你的代码却直接去读choices;二是流式和非流式响应结构不同,你把流式的 chunk 当完整响应解析了;三是模型名写错,服务端返回了错误但 HTTP 状态码是 200。修法是先打印完整响应体再解析,别上来就.choices[0]。用 curl 先确认返回结构,再改代码。

OAuth 相关报错。如果 Agent Card 声明了 OAuth2,客户端要先走授权流程拿 token。常见错误是tokenUrl配错、scope 不匹配、或者把 token 放进了 payload 而不是 Header。还有一种情况是 In-Task 二次认证:Task 执行中返回AUTH_REQUIRED中断态,需要你补一个额外凭证再继续。这不是错误,是设计——Agent 在需要时才要凭证,而不是预先收集所有权限。

Task 状态相关报错。如果你试图“更新”一个已经COMPLETED的 Task,会失败,因为终态不可重启。正确做法是带contextId创建新 Task,用referenceTaskIds引用原 Task。另外,SSE 连接断开后 Task 仍在跑,别以为任务挂了,用SubscribeToTask重新订阅就能拿到断连期间的状态更新。

Agent Card 缓存问题。改了 Card 但客户端拿到的还是旧的,多半是 HTTP 缓存。服务端应返回Cache-Control和ETag,客户端用If-None-Match做条件请求。调试阶段可以先禁用缓存,确认新 Card 生效。

6. 把 A2A 和 MCP 放回你的系统里

排完错,回到定位问题:这两套协议在你的系统里到底各放哪一层。A2A 是横向的,管 Agent 与 Agent 的对等协作,多轮对话、有状态、任务可能演变;MCP 是纵向的,管 Agent 连接工具和数据源,结构化 I/O、无状态、主从关系。它们不交叉、不替代。

一个 Agent 可以同时是 A2A Server、A2A Client 和 MCP Client。举个修车场景:客户助手 Agent 通过 A2A 找店长 Agent 多轮诊断;店长 Agent 通过 A2A 把维修任务委派给技工 Agent;技工 Agent 通过 MCP 调用诊断工具、查维修手册、抬升平台,同时通过 A2A 向零件供应商 Agent 订货。客户完全不知道技工用了哪些工具——这就是不透明执行的价值。

判断标准再强调一次:能包装成函数签名(明确入参出参类型)的,用 MCP;需要多轮协商、状态管理、Artifact 交付的,用 A2A。A2A 是“一起完成任务”,MCP 是“使用能力”。

落到你的系统,建议这样分层:底层模型访问统一走一条通道(比如 TaoToken 的https://taotoken.net/api),保证凭证和地址一致;Agent 内部接工具用 MCP;Agent 之间协作、尤其是跨框架跨团队的协作,用 A2A。这样你的集成量从 N² 降到 N——每个 Agent 只需要实现一次 A2A 接口和一张 Agent Card,就能和所有其他 Agent 协作。

最后给一个实操建议:别一上来就上多 Agent。先用单 Agent + MCP 把业务跑通,确认模型通道稳定;当你真的出现“两个自主 Agent 需要协商”的需求时,再引入 A2A。引入时先写 Agent Card、再验证发现、再跑一次SendMessage,按第 4 节的顺序逐层验证。这样每一步的变量都可控,出问题也能快速定位到是模型层、发现层还是协议层。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询