news 2026/10/17 11:52:46

这一篇 详解A2A(Agent2Agent)协议 就够你了解A2A:用TaoToken统一Key跑通JSON-RPC与SSE双通道

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
这一篇 详解A2A(Agent2Agent)协议 就够你了解A2A:用TaoToken统一Key跑通JSON-RPC与SSE双通道

1. 从一次 Agent 联调失败说起:A2A 协议到底解决什么问题

如果你正在做多 Agent 协作,大概率遇到过这种场景:一个负责规划的 Agent 想把子任务交给另一个负责执行的 Agent,结果两边一个是 Python 写的、一个是 Node 写的,工具调用格式对不上,流式输出接不住,最后只能靠手写 HTTP 接口硬拼。A2A(Agent2Agent)协议就是冲着这个痛点来的——它由 Google Cloud 推出,目标很明确:让不同厂商、不同框架构建的 AI 代理之间能够标准化通信和协作。

A2A 是什么?一句话概括:它是一套建立在 HTTP、SSE 和 JSON-RPC 之上的开放协议,定义了 Agent 之间如何发现彼此能力、如何发起任务、如何流式接收结果。能做什么?你可以让一个 Agent 通过标准 JSON-RPC 请求把任务发给远程 Agent,远程 Agent 用 SSE 把中间状态和最终产物一段段推回来。适合谁?适合正在做多 Agent 编排、想让自己的 Agent 接入更大生态、或者单纯想快速验证 Agent2Agent 通信链路的开发者。

A2A 里有三个核心角色:用户(User)、客户端(Client)、服务端(Server)。客户端代表用户向远程代理发起请求,服务端就是那个“黑盒”远程代理。和 MCP 相比,A2A 少了 Host 这个角色,设计上更偏向开放实现而非规范一个固定机制。它的核心概念包括 AgentCard(描述 Agent 能力的 JSON 文件,官方建议托管在/.well-known/agent.json)、Task(有状态的任务实体)、Artifact(任务产出的不可变结果)、Message(承载思考、指令、错误等非工件内容)以及 Part(消息或工件中的内容片段,支持文本、文件、数据三种类型)。

通信机制上,A2A 支持标准请求/响应模式,也支持通过 SSE 进行流式更新。客户端处理长时间运行的任务时可以轮询,代理也可以通过 SSE 主动推送状态更新。更关键的是异步支持:客户端可以注册一个 webhook,异步获取长任务的最终结果,这就是 PushNotification 相关实现。用过 LLM API 的人都知道,输出慢、输出过程中干不了别的事是常态;有了异步回调或重新订阅,客户端开发就能更灵活,用户体验也更好。

错误处理方面,A2A 沿用标准 JSON-RPC 错误码:-32700 JSON 解析错误、-32600 无效请求、-32601 方法未找到、-32602 无效参数、-32603 内部错误,以及 -32001 任务未找到、-32002 任务无法取消、-32003 不支持推送通知、-32004 不支持的操作、-32005 内容类型不兼容等实现特定错误码。这些错误码在你联调 JSON-RPC 握手时会频繁出现,后面排障章节会逐个对照。

理解了这些,你就明白为什么需要一套统一的 Key 和 Base URL 来跑通链路——因为 A2A 客户端和服务端都要调用底层模型,如果每个 Agent 都配一套不同的凭证,联调成本会非常高。这也是我接下来要引入 TaoToken 统一 Key 的原因。

2. 用 TaoToken 统一 Key 打通 A2A 的模型调用层

A2A 协议本身只规定 Agent 之间怎么通信,不规定 Agent 内部用哪个模型。但实际联调时,你的 Coder Agent 需要调用 LLM 来生成代码,这个调用如果直连各家厂商,就会遇到 Key 分散、Base URL 不统一、模型 ID 写法各异的问题。我的做法是用 TaoToken 作为统一的模型接入层,让 A2A 服务端和客户端都指向同一个 Base URL 和同一把 Key。

TaoToken 在这里扮演的角色是“模型调用的统一入口”。你可以在官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 了解它的能力,API 端点则是 https://taotoken.net/api(注意这个地址不加 UTM 参数)。它的价值在于:你只需要维护一套凭证,就能在 A2A 的各个 Agent 里调用不同模型,联调时不用来回切换配置。

具体怎么配?A2A 的示例项目通常用环境变量读取模型配置。以我改造过的 a2a-agent-coder 为例,它原本用 OpenRouter,我把它改成兼容 OpenAI 形式的 TaoToken 接入。你需要在项目根目录创建一个.env文件,内容如下:

OPENAI_API_KEY=你的TaoTokenKey OPENAI_BASE_URL=https://taotoken.net/api OPENAI_MODEL=claude-3.5-haiku

这三行就是 A2A 接入的“三件套”:Base URL、Key、Model ID。Base URL 固定指向 TaoToken 的 API 端点,Key 从控制台获取,Model ID 按你实际要用的模型填写。注意OPENAI_BASE_URL后面不要带/v1之类的后缀,具体以接入文档为准,因为不同 SDK 对路径拼接的处理不一样,带错了会出现 404。

如果你用的是 Claude Code 这类工具做 A2A 服务端的代码润色或生成,配置逻辑是一样的:在 settings 里指定 Base URL 和 Key,模型 ID 填你套餐里可用的。TaoToken 的 Coding Plan 适合长期编码和 Agent 场景,如果你打算把 A2A 链路跑成常态化的开发流程,可以关注一下 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 这个入口。

这里有个容易踩的坑:A2A 服务端和客户端可能跑在两个不同的进程里,环境变量不会自动共享。我的做法是把.env放在项目根目录,服务端启动前用export $(cat .env | xargs)让变量生效,客户端如果也需要调模型,同样处理。如果客户端只是发 JSON-RPC 请求、不直接调模型,那它不需要模型 Key,只需要知道服务端的 URL。

还有一点:A2A 的 AgentCard 里会声明capabilities.streaming是否为 true。如果你打算用 SSE 流式返回,服务端必须支持流式,而流式的前提是底层模型调用也支持流式。TaoToken 的 API 兼容 OpenAI 的流式接口,所以只要你的 A2A 服务端代码正确处理了stream: true的响应,SSE 通道就能跑通。配置阶段先把这三件套写对,后面验证请求时就不会因为凭证问题卡住。

3. 可复制的 A2A 服务端与客户端配置片段

这一节给你可以直接复制粘贴的配置和代码片段。我以 a2a-agent-coder 这个 TypeScript 项目为蓝本,它原本用 OpenRouter,我改成了 TaoToken 接入。你不需要完全照搬,重点是理解配置的结构,然后套用到你自己的 A2A 实现里。

首先是服务端的模型配置。在项目里找到读取环境变量的地方,通常是src/agent.ts或类似的入口文件。确保它用的是 OpenAI 兼容的客户端初始化方式:

import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL, }); const model = process.env.OPENAI_MODEL || "claude-3.5-haiku";

这段代码的关键是baseURL指向 TaoToken 的 API 端点,apiKey从环境变量读取。如果你的 A2A 实现用的是其他语言,比如 Python,对应的初始化是:

from openai import OpenAI client = OpenAI( api_key=os.environ["OPENAI_API_KEY"], base_url=os.environ["OPENAI_BASE_URL"], )

接下来是 A2A 服务端的 AgentCard 配置。AgentCard 是一个 JSON 文件,官方建议托管在/.well-known/agent.json。一个最小可用的 AgentCard 长这样:

{ "name": "Coder Agent", "description": "An agent that generates code based on natural language instructions and streams file outputs.", "url": "http://localhost:41241", "version": "0.0.1", "capabilities": { "streaming": true, "pushNotifications": false }, "defaultInputModes": ["text"], "defaultOutputModes": ["text", "file"], "skills": [ { "id": "code-generation", "name": "Code Generation", "description": "Generate code from natural language instructions", "tags": ["coding", "generation"], "examples": ["implement binary search"] } ] }

注意capabilities.streaming设为 true,这样客户端才知道可以用 SSE 订阅。url填你服务端实际监听的地址和端口。

客户端的配置相对简单,它主要需要知道服务端的 URL。在 a2a-agent-coder 的 CLI 里,默认连的是http://localhost:41241。如果你要改,找到src/cli.ts里的 Agent URL 配置项。客户端发起任务时,核心是构造 JSON-RPC 请求:

{ "jsonrpc": "2.0", "id": "a1a608b3-3015-4404-a83f-6ccc05083761", "method": "tasks/sendSubscribe", "params": { "id": "task-001", "message": { "role": "user", "parts": [ { "type": "text", "text": "implement binary search" } ] } } }

这个请求通过 HTTP POST 发给服务端,method是tasks/sendSubscribe,表示要订阅任务流。服务端收到后,会先返回一个 SSE 流,然后陆续推送状态更新和工件。

如果你用的是 Cline MCP 或类似的工具来管理 A2A 服务端进程,配置里同样要写全三件套。以 Cline 的 MCP 配置为例,在 settings 里添加:

{ "mcpServers": { "a2a-coder": { "command": "bun", "args": ["run", "agents:coder"], "env": { "OPENAI_API_KEY": "你的TaoTokenKey", "OPENAI_BASE_URL": "https://taotoken.net/api", "OPENAI_MODEL": "claude-3.5-haiku" } } } }

这样 Cline 启动 A2A 服务端时,环境变量会自动注入。如果你用 Codex 的 auth.json 方式管理凭证,逻辑类似,把 Base URL 和 Key 写进对应的配置字段即可。

配置写完后,启动服务端:

export $(cat .env | xargs) bun run agents:coder

看到服务端监听在 41241 端口,就说明配置生效了。接下来打开另一个终端跑客户端,验证整条链路。

4. 用 curl 与日志验证 JSON-RPC 握手和 SSE 事件流

配置写好了,怎么确认 JSON-RPC 握手成功、SSE 事件流正常返回?我习惯先用 curl 手动打一发,看原始响应,再跑客户端看完整流程。这样出问题时能快速定位是协议层还是应用层的问题。

第一步,验证 AgentCard 能否获取。A2A 规定 AgentCard 托管在/.well-known/agent.json,用 curl 直接 GET:

curl -s http://localhost:41241/.well-known/agent.json | jq .

如果返回了完整的 JSON,包含 name、description、capabilities、skills 等字段,说明服务端的发现端点正常。如果返回 404,检查你的服务端有没有正确注册这个路由;如果返回 500,看服务端日志里的报错。

第二步,手动发一个 JSON-RPC 请求,验证握手。用 curl 发 POST:

curl -N -X POST http://localhost:41241 \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "test-001", "method": "tasks/sendSubscribe", "params": { "id": "task-001", "message": { "role": "user", "parts": [{"type": "text", "text": "implement binary search"}] } } }'

注意-N参数,它关闭 curl 的缓冲,让你能实时看到 SSE 流。正常返回应该是一系列data:开头的事件,每个事件是一个 JSON 对象,包含任务状态更新或工件内容。你会看到类似这样的输出:

data: {"id":"task-001","status":{"state":"working","message":{"role":"agent","parts":[{"type":"text","text":"Generating code..."}]}},"final":false} data: {"id":"task-001","artifact":{"name":"src/algorithms/binary_search.py","parts":[{"type":"text","text":"def binary_search(arr, target):\n ..."}]},"index":0} data: {"id":"task-001","status":{"state":"completed"},"final":true}

如果看到state从working变成completed,并且final为 true,说明 SSE 事件流完整走通了。如果卡在working不动,可能是模型调用超时或流式响应没接住;如果直接返回错误对象,看error.code对照 JSON-RPC 错误码表。

第三步,跑完整客户端,看端到端流程。在另一个终端执行:

bun run a2a:cli

客户端会先拉取 AgentCard,打印出 Agent 名称和描述,然后提示你输入消息。输入implement binary search后,你会看到状态从 working 到 completed,中间夹杂着生成的代码文件内容。我实测下来,从发送到收到完整工件大约几秒钟,取决于模型响应速度。

日志方面,服务端控制台会打印每个 JSON-RPC 请求的方法名和任务 ID,客户端会打印 SSE 事件的接收情况。如果 SSE 流中断,客户端通常会报SSE stream finished或连接重置。这时候检查服务端有没有正确设置Content-Type: text/event-stream,以及有没有在final: true之后关闭连接。

还有一个验证点:任务状态历史。A2A 的 Task 支持history字段,如果你在请求里带了historyLength,服务端会返回最近的消息记录。用 curl 发一个带historyLength的请求,看返回里有没有history数组,能进一步确认任务状态管理是否正常。

5. 联调常见报错排查:401、local proxy failed、reading choices、OAuth

联调 A2A 时,报错基本集中在模型调用层和协议层。我把踩过的坑按报错信息整理出来,你对照着排查。

401 Unauthorized:这是最常见的。原因通常是 Key 没生效或 Base URL 写错。先确认.env里的OPENAI_API_KEY是不是完整的 TaoToken Key,有没有多余空格。然后确认OPENAI_BASE_URL是不是https://taotoken.net/api,不要带/v1或结尾斜杠。如果你在 Cline MCP 或 Codex auth.json 里配置,检查 JSON 字段名有没有写错,比如把apiKey写成api_key。还有一个隐蔽原因:环境变量没导出。服务端进程如果没读到.env,就会用空 Key 去请求,直接 401。用export $(cat .env | xargs)确保变量注入。

local proxy failed:这个报错通常出现在客户端尝试连接服务端时。检查服务端有没有真正启动,端口是不是 41241,防火墙有没有拦。如果你在容器里跑服务端,客户端在宿主机,localhost是不通的,要把 AgentCard 里的url改成容器 IP 或映射后的地址。另外,有些 A2A 实现会走本地代理转发,如果代理配置和实际监听端口不一致,也会报这个错。确认url字段和实际监听地址完全一致。

reading choices 相关报错:这个报错来自 OpenAI 兼容接口的响应解析。当你用流式请求时,SDK 期望返回的 chunk 里有choices数组,如果 TaoToken 返回的格式和 SDK 预期不一致,就会在读取choices时抛错。排查方法:先用非流式请求测一下,把stream设为 false,看能不能正常返回。如果非流式正常、流式报错,检查你的 A2A 服务端代码有没有正确处理 SSE 的data:行,有没有在 chunk 不完整时就尝试 JSON.parse。另一个原因是模型 ID 写错了,TaoToken 返回了错误对象而不是正常的 choices 结构,SDK 解析时就崩了。确认OPENAI_MODEL是你套餐里真实可用的模型 ID。

OAuth 相关报错:如果你在 A2A 的 AgentCard 里声明了authentication.schemes,但客户端没带对应的凭证,服务端会返回认证错误。A2A 的认证结构参考 OpenAPI,支持 Basic、Bearer 等。如果你只是本地联调,先把 AgentCard 里的authentication字段去掉或留空,避免不必要的认证拦截。如果确实需要认证,确保客户端在请求头里带了Authorization: Bearer <token>,并且 token 和服务端配置的一致。

JSON-RPC 错误码对照:如果服务端返回-32601 Method not found,说明你请求的method服务端没实现,检查是不是拼成了tasks/send而不是tasks/sendSubscribe。返回-32602 Invalid params,检查params里的id、message、parts结构是否符合 A2A 定义。返回-32001 Task not found,说明你查询的任务 ID 不存在,可能是任务已经过期或被清理。返回-32003 Push notifications not supported,说明服务端没实现推送通知,但你的请求里带了pushNotification配置,去掉即可。

SSE 流提前关闭:客户端收到final: true之前连接就断了。检查服务端有没有在模型流式响应结束前就关闭了 HTTP 连接。正确做法是等模型返回[DONE]或流结束信号后,再发送final: true的事件并关闭。另外,某些反向代理会缓冲 SSE,导致事件不是实时推送,检查代理配置里有没有关闭缓冲。

排查时养成看原始响应的习惯。curl 加-v看请求头和响应头,确认Content-Type和状态码。服务端日志打印每个请求的 method 和 params,客户端日志打印每个 SSE 事件的类型。两边日志一对,问题基本就定位了。

6. 跑通之后:A2A 与 MCP 的边界,以及下一步怎么走

链路跑通后,你可能会问:A2A 和 MCP 到底什么关系?我大概总结一下。A2A 主要用于代理间通信和协作,核心结构是客户端-服务端(代理-代理);MCP 主要用于为模型提供工具和上下文、连接外部资源,核心结构是客户端-主机-服务端(应用-LLM-外部资源)。接口标准上,A2A 用 JSON 规范、代理卡、任务、消息、工件;MCP 用 JSON-RPC 2.0、资源、工具、记忆、提示。通信协议上,A2A 走 HTTP、JSON-RPC、SSE;MCP 走 JSON-RPC 2.0 over stdio 或 HTTP with SSE。性能侧重点上,A2A 强调异步通信、处理负载;MCP 强调上下文管理、并行处理、缓存。

从技术上看,A2A 适应的场景更多,某种程度上包含了 MCP 的场景。如果未来 Agent 很多、MCP server 也很多,会构成一个什么样的网络?前者更倾向于去中心化,后者更倾向于中心化;前者偏向分散自治,后者是集中管理。这个边界目前还在演化,没有绝对答案。

回到实操。你现在已经能用 TaoToken 统一 Key 跑通 A2A 的 JSON-RPC 握手和 SSE 事件流了。下一步可以做的:把 AgentCard 托管到真实域名,让其他 Agent 能发现你的服务;实现 PushNotification,让长任务在完成后异步回调;或者把多个 A2A Agent 串起来,做一个多跳的任务编排。如果你想把这条链路用于日常编码,TaoToken 的 Coding Plan 提供了适合长期 Agent 场景的额度方案,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。需要管理多把 Key 或查看用量,去控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。想先验证模型对话效果,可以用模型对话页 https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。API Key 的创建和管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,接入细节看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

最后分享一个实用技巧:A2A 联调时,把服务端的日志级别调到 debug,打印每个 JSON-RPC 请求的完整 params 和每个 SSE 事件的原始内容。这样当客户端报reading choices或流中断时,你能直接看到服务端到底发了什么,比在客户端猜要快得多。另外,AgentCard 的capabilities.streaming一定要和实际实现一致,声明了 true 但服务端不支持流式,客户端会一直等不到事件。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 11:51:42

Chrome DevTools CSS调试完全指南:Styles与Computed面板实战

写CSS写到怀疑人生的时候&#xff0c;有一半的问题不是"代码不会写"&#xff0c;而是"代码写了不知道为什么不生效"。这篇是这个系列的第三篇&#xff0c;专门聊Chrome调试工具里最实用的一块——查看CSS属性。第一篇讲选择器和优先级&#xff0c;第二篇讲…

作者头像 李华
网站建设 2026/10/17 11:51:36

MySQL线程池实战避坑指南:5.7启用与8.0兼容性详解

简介&#xff1a;本资源是一份面向MySQL数据库管理员、性能优化工程师及高并发系统运维人员的实战型技术文档&#xff0c;聚焦解决生产环境中因高并发请求引发的线程创建开销大、响应延迟高、稳定性不足等核心性能瓶颈。文档系统阐述MySQL线程池插件&#xff08;thread_pool&am…

作者头像 李华
网站建设 2026/10/17 11:49:58

MiniCPM+ghfind实现GitHub开发者风格量化分析

1. 项目概述&#xff1a;一个轻量级代码作者风格分析工具的诞生逻辑 “ghfind用MiniCPM模型&#xff0c;锐评了上千开发者”——这个标题乍看像段程序员圈内的玩笑话&#xff0c;实则藏着一套非常务实的技术路径&#xff1a;它不是在搞AI人格扮演&#xff0c;也不是做情绪化网…

作者头像 李华
网站建设 2026/10/17 11:49:39

LabWindows/CVI 计算器完整工程:从界面搭建到四则运算实现

简介&#xff1a;这份资源是面向嵌入式开发初学者与CVI&#xff08;CodeVisionAVR&#xff09;编程学习者的简易计算器完整工程文件&#xff0c;旨在帮助读者理解AVR平台下C语言程序的组织方式与基本运算逻辑实现。工程支持多位数加、减、乘、除运算&#xff0c;允许自定义运算…

作者头像 李华
网站建设 2026/10/17 11:46:03

用K-means聚类实现图片主色提取:从像素到配色方案

1. 项目思路拆解&#xff1a;为什么用K-means来“取色”做图片主色提取&#xff0c;最朴素的想法是直接统计像素直方图&#xff0c;把出现次数最多的颜色挑出来。但这个方案在实际项目里基本不能用&#xff0c;原因很简单&#xff1a;一张照片里背景、主体、光影渐变会产生成千…

作者头像 李华
网站建设 2026/10/17 11:45:35

认识PATH-ViTB:OpenGVLab开源的行人检测ViT检查点仓库完全指南

【免费下载链接】PATH-ViTB 项目地址&#xff1a; https://ai.gitcode.com/OpenGVLab/PATH-ViTB 点击查看 免费下载 PATH-ViTB 是 OpenGVLab 开源的一个 行人检测&#xff08;pedestrian detection&#xff09;ViT 预训练检查点仓库&#xff0c;基于 Hugging Face 模型仓库规范…

作者头像 李华