1. 为什么我要用 Wireshark 拆 A2A 协议的 HTTP 报文
A2A 协议(Agent-to-Agent Protocol)解决的是两个独立 Agent 之间怎么互相说话的问题。你可以把它理解成:MCP 是 Agent 内部 LLM 调工具的约定,而 A2A 是 Agent 和 Agent 之间的通信约定。每个支持 A2A 的 Agent 本质上就是一个 HTTP 服务器,调度 Agent 通过 HTTP 请求把任务分发给执行 Agent,执行 Agent 再把结果回传。
问题在于,这套通信一旦出问题,日志往往只告诉你“调用失败”,不告诉你失败在哪一层。是请求头缺字段?是流式响应中途断了?还是任务状态没置为 completed?这时候光看应用日志不够,得看真实的 HTTP 报文。Wireshark 就是干这个的——它能把 Agent 之间每一次 HTTP 请求和响应完整还原出来,包括请求头、请求体、流式分块和最终状态。
这篇文章适合两类人:一是正在搭多 Agent 协作系统、想搞清楚 A2A 通信细节的开发者;二是已经跑起来但遇到 Agent 通信异常、需要定位问题的排障者。我会给出可复制的 Wireshark 过滤表达式、A2A 端点配置片段,以及对照 TaoToken 统一 Key/API 通道验证调用链的抓包比对步骤。实测下来,把抓包和统一通道结合起来看,定位效率会高很多。
先说清楚一个前提:A2A 的通信是明文 HTTP 还是 HTTPS,直接决定你能不能抓到内容。如果是 HTTPS,Wireshark 只能看到 TLS 握手和加密后的数据,看不到请求头。所以本地调试时,我一般让 Agent 之间走 HTTP,或者配置 SSLKEYLOGFILE 让 Wireshark 解密。下面所有示例都基于本地 HTTP 通信,端口用 8001(调度 Agent)和 8002(天气 Agent)。
2. TaoToken 前置:统一 Key 与 API 通道怎么接进 Agent 调用链
在拆 A2A 报文之前,得先让 Agent 能正常调用模型。多 Agent 场景下最烦的是每个 Agent 各配一套 Key,管理起来乱。我的做法是用 TaoToken 的统一 Key 和 API 通道,所有 Agent 的模型调用都走同一个入口,这样抓包时调用链是清晰的,不会因为 Key 不同导致请求头差异干扰分析。
TaoToken 的 API 地址是https://taotoken.net/api,官网是https://taotoken.net/?utm_source=taotoken_aicg_blog_end。你需要在控制台创建一个 API Key,然后把它写进 Agent 的模型配置里。注意,A2A 协议本身不规定模型怎么调,它只管 Agent 之间的 HTTP 通信;模型调用是 Agent 内部的事。但正因为如此,抓包时你会看到两类流量:一类是 Agent 之间的 A2A 请求,另一类是 Agent 内部调模型的请求。把模型调用统一到 TaoToken,第二类流量的目标地址就固定了,过滤起来方便。
具体操作:登录控制台,进入 API Keys 页面创建一个 Key,复制保存。然后在你每个 Agent 的配置里,把模型 Base URL 指向https://taotoken.net/api,Key 填刚创建的,Model ID 按你实际用的填。这样调度 Agent 和天气 Agent 内部调模型时,都会走同一条通道。
如果你用的是 Claude Code 这类工具做 Agent 开发,可以在 settings 里配置。如果是自己写的 Python Agent,用 openai 兼容的 SDK 就行,把 base_url 和 api_key 换掉即可。这一步做完,抓包时你就能在 Wireshark 里同时看到 A2A 的 HTTP 流量和调模型的 HTTPS 流量,前者明文可读,后者加密但目标地址固定,便于区分。
这里有个坑要提醒:有些 Agent 框架默认把模型调用和 A2A 通信混在同一个端口,抓包时不好区分。建议把 A2A 服务端口和模型调用分开,A2A 走 8001/8002,模型调用走外部 HTTPS,这样过滤表达式可以精准命中。
3. 可复制配置:A2A 端点与 Wireshark 过滤表达式
先给 A2A 端点的配置片段。假设你用 Python 写一个天气 Agent,暴露 A2A 接口。核心是定义一个 HTTP 服务,接收调度 Agent 发来的任务请求,返回结果。下面是一个最小化的配置示例,用 JSON 描述 Agent 的能力卡片(Agent Card),这是 A2A 协议里 Agent 注册自己能力的方式:
{ "name": "weather-agent", "description": "查询指定城市天气", "url": "http://127.0.0.1:8002/a2a", "version": "1.0.0", "capabilities": { "streaming": true, "pushNotifications": false }, "skills": [ { "id": "get-weather", "name": "查询天气", "description": "输入城市名,返回当前天气", "inputModes": ["text"], "outputModes": ["text", "stream"] } ] }调度 Agent 注册天气 Agent 时,就是把这个卡片 POST 到调度 Agent 的注册端点。抓包时你会看到这个注册请求,请求体就是上面的 JSON。
接下来是 Wireshark 过滤表达式。打开 Wireshark,选中回环网卡(Windows 上是Adapter for loopback traffic capture,Linux 上是lo),在过滤栏输入:
tcp.port == 8001 || tcp.port == 8002这条能抓到两个 Agent 端口的所有 TCP 流量。如果你想只看 HTTP 请求,用:
http && (tcp.port == 8001 || tcp.port == 8002)想只看调度 Agent 发给天气 Agent 的请求(假设调度 Agent 源端口 8001,天气 Agent 目标端口 8002):
http.request && tcp.srcport == 8001 && tcp.dstport == 8002想只看流式响应(chunked 传输):
http && tcp.port == 8002 && http.transfer_encoding == "chunked"如果你要跟踪一次完整的 A2A 调用链,用 Follow HTTP Stream:右键某个包,选 Follow → HTTP Stream,Wireshark 会把这次 TCP 连接上的所有请求和响应拼在一起显示,流式分块也能看到。
再给一个调度 Agent 发起任务分发的请求示例,用 curl 模拟:
curl -X POST http://127.0.0.1:8002/a2a \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -d '{ "jsonrpc": "2.0", "method": "tasks/send", "id": "task-001", "params": { "message": { "role": "user", "parts": [{"type": "text", "text": "西雅图天气怎么样"}] }, "streaming": true } }'注意Accept: text/event-stream和streaming: true,这两个是触发流式响应的关键。抓包时你会看到请求头里有这两个字段,响应头里会有Transfer-Encoding: chunked。
4. 验证请求:抓包还原任务分发与流式响应
配置好之后,启动两个 Agent,打开 Wireshark 开始抓包,然后模拟一次用户输入:“西雅图天气怎么样”。调度 Agent 会先分析意图,然后选择天气 Agent 进行 HTTP 通信。
第一步,抓注册流量。调度 Agent 启动时会把天气 Agent 的卡片注册进来,这时你会看到两个 HTTP 数据流:一个是 POST 注册请求,一个是 200 响应。在 Wireshark 里过滤http.request.method == "POST",找到请求体里含weather-agent的那个包,展开 HTTP 层,能看到完整的 JSON 卡片。
第二步,抓任务分发。用户输入后,调度 Agent 向天气 Agent 发 POST 请求。过滤http.request.uri contains "/a2a",找到这个包。展开后重点看三处:请求头的Accept是不是text/event-stream,请求体的params.streaming是不是true,以及params.message.parts里的文本是不是用户输入。
第三步,抓流式响应。天气 Agent 返回的是分块数据。在 Wireshark 里过滤http.response并且目标端口是 8001 的包,你会看到多个 chunk。每个 chunk 是一段 SSE 格式的数据,类似:
data: {"jsonrpc":"2.0","id":"task-001","result":{"status":"working","artifact":{"parts":[{"type":"text","text":"正在查询西雅图天气..."}]}}} data: {"jsonrpc":"2.0","id":"task-001","result":{"status":"working","artifact":{"parts":[{"type":"text","text":"西雅图当前多云,15摄氏度"}]}}} data: {"jsonrpc":"2.0","id":"task-001","result":{"status":"completed"}}最后一个 chunk 的status是completed,这表示任务结束。如果你在抓包里看到流式数据中途断了,没有 completed,那说明 Agent 内部处理出错或者连接被提前关闭。
第四步,对照 TaoToken 调用链。天气 Agent 内部调模型时,会向https://taotoken.net/api发 HTTPS 请求。这部分在 Wireshark 里是加密的,但你能看到目标 IP 和端口。如果你配置了 SSLKEYLOGFILE,可以解密看到请求体里的 prompt 和响应。对比 A2A 的明文流量和模型调用的加密流量,你能确认:调度 Agent 分发的任务文本,和天气 Agent 发给模型的 prompt 是否一致;模型返回的天气结果,和天气 Agent 回传给调度 Agent 的 artifact 是否一致。这条链对上了,说明调用链没问题。
实测下来,最容易出问题的是流式响应的分块边界。有些框架把多个 SSE 事件塞进一个 TCP 包,有些拆得很碎,Wireshark 里看起来乱。用 Follow HTTP Stream 看整体最清楚。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
抓包时遇到报错,对照下面几个真实场景。
401 Unauthorized:在 Wireshark 里过滤http.response.code == 401,找到响应包,看WWW-Authenticate头。如果是 A2A 通信报 401,说明 Agent 之间的认证没配好,检查调度 Agent 发请求时有没有带正确的 token。如果是调模型报 401,检查 TaoToken 的 Key 是否填对、是否过期。注意,A2A 的认证和模型调用的认证是两套,别混。
local proxy failed:这个报错通常出现在 Agent 内部调模型时,说明请求没发出去。在 Wireshark 里过滤目标地址是taotoken.net的包,如果完全没有,说明请求在本地就被拦了。检查 Agent 的 HTTP 客户端配置,是不是设了错误的 proxy 环境变量。把HTTP_PROXY和HTTPS_PROXY清掉再试。
reading choices 相关报错:这通常是模型返回格式和 Agent 解析代码不匹配。抓包看模型响应(需解密),确认返回的 JSON 结构里choices字段是否存在。如果 Agent 用的是 OpenAI 兼容格式,TaoToken 返回的也是兼容格式,理论上没问题。但如果 Agent 代码里写死了某个字段路径,而实际返回多了层包装,就会报 reading choices 失败。对照抓包里的实际响应结构改解析代码。
OAuth 相关报错:如果 A2A 通信用了 OAuth 做 Agent 间认证,抓包时过滤http.request.uri contains "oauth"或http.request.uri contains "token",看 token 请求和响应。常见问题是 token 过期没刷新,或者 scope 不对。检查请求体里的grant_type和scope字段。
再补一个 Codex auth.json 的场景。如果你用 Codex 类工具做 Agent,认证信息存在auth.json里。这个文件里要有三件套:Base URL、Key、Model ID。Base URL 填https://taotoken.net/api,Key 填控制台创建的,Model ID 按实际填。抓包时如果发现请求发到了错误的地址,先检查这个文件。
排查顺序建议:先看 Wireshark 里有没有请求发出,再看请求头对不对,再看响应码,最后看响应体。大部分问题在前两步就能定位。
6. 把抓包习惯固化进 Agent 开发流程
抓包不是出问题才做的事。我现在搭多 Agent 系统,第一步就是开 Wireshark,把注册、分发、流式响应三段流量各抓一次,存成 pcap 文件。这样后面改代码,对比新旧 pcap,一眼能看出通信行为变了没有。
几个实用技巧:给 Wireshark 设个显示过滤器按钮,把常用的tcp.port == 8001 || tcp.port == 8002存下来,一键切换。用File → Export Specified Packets把一次完整调用链导出,方便分享给同事。流式响应如果太长,用tshark命令行提取:
tshark -r a2a.pcap -Y "http" -T fields -e http.file_data | grep "completed"这条能快速确认流式响应里有没有 completed 状态。
最后说下模型通道的选择。如果你只是验证 A2A 通信,用模型对话页面手动发几条请求就够了。如果你要长期跑多 Agent 协作、做 Agent 编排,建议用 Coding Plan,配额和稳定性更适合持续调用。接入文档里有完整的 Base URL、Key、Model ID 配置说明,照着填就行。抓包验证时,确保所有 Agent 的模型调用都走同一条通道,这样调用链才是干净的、可对比的。