A2A 协议代理卡怎么写,任务端点怎么调
先理清 A2A 配置到底在配什么
A2A(Agent-to-Agent)协议要解决的是让两个独立部署的 AI 代理能互相发现、互相派活。
配置工作主要落在三处:
- 代理卡(Agent Card):一个 JSON 文件,声明“我是谁、我能干什么、你该往哪个地址发任务”。
- 服务端入口:接收任务请求、返回任务对象、支持查询任务状态的 HTTP 端点。
- 客户端配置:知道去哪里拿到代理卡,并按卡里的地址发起调用。
如果目标是让本地代理被另一个代理调用,先把这三件事分开,再逐项填。
下面用最小示例走一遍,不依赖特定厂商 SDK。
准备一份能读懂的代理卡
代理卡通常放在服务端根路径下,例如 https://your-agent.example.com/.well-known/agent-card.json。
文件内容至少要让调用方知道身份、能力和技能入口:
{
"name": "weather-agent",
"description": "提供城市天气查询",
"url": "https://your-agent.example.com/a2a",
"version": "0.1.0",
"capabilities": {
"streaming": false,
"pushNotifications": false
},
"skills": [
{
"id": "query-weather",
"name": "查询天气",
"description": "输入城市名,返回当前天气"
}
]
}
几个容易填错的地方:
url是任务请求端点,不是代理卡本身的地址。客户端拿到卡后,会往这个地址 POST 任务。capabilities.streaming表示服务端是否支持流式返回。如果填了true但服务端没实现对应处理,客户端可能一直等不到完整响应。skills里的id建议保持稳定,客户端可能按技能 ID 路由请求。
代理卡本身是公开可读的,不要在里面放密钥、内部 IP 或数据库连接串。
把服务端任务入口跑起来
A2A 的任务请求通常走 JSON-RPC 风格,方法名和消息体格式以你所用的协议版本为准。
下面是一个最小 HTTP 处理逻辑的示意(Node.js,仅用于说明请求形态,不是完整生产代码):
// POST /a2a
// body: { "jsonrpc": "2.0", "id": "1", "method": "tasks/send", "params": { ... } }
app.post('/a2a', (req, res) => {
const { method, params } = req.body;
if (method === 'tasks/send') {
const taskId = crypto.randomUUID();
// 先返回任务对象,异步处理
res.json({
jsonrpc: '2.0',
id: req.body.id,
result: {
id: taskId,
status: { state: 'submitted' }
}
});
// 后续再通过 tasks/get 查询状态
}
});
关键点:任务提交和任务完成是两件事。
客户端拿到 submitted 状态后,需要按协议约定轮询 tasks/get,或者由服务端在支持的情况下推送更新。
如果服务端同步处理完才返回,长任务容易触发客户端超时。
如果你的服务端前面有 Nginx,确认 /a2a 路径没有被改写成静态文件,并适当调大 proxy_read_timeout,否则流式返回会被提前切断。
客户端发现代理与发起调用
客户端配置的核心是“卡从哪来、任务往哪发”。
一个最小调用流程:
- 请求
/.well-known/agent-card.json拿到代理卡。 - 解析
url字段,得到任务端点。 - 向该端点 POST 任务请求,携带 JSON-RPC 方法名和参数。
- 根据返回的任务 ID 轮询状态,直到
completed或failed。
用 curl 验证发现阶段是否通:
curl -s https://your-agent.example.com/.well-known/agent-card.json | jq .
返回 200 且 JSON 可解析,说明发现这一步没问题。
再验证任务端点:
curl -s -X POST https://your-agent.example.com/a2a \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"tasks/send","params":{}}'
如果代理卡能拿到但任务端点返回 404,优先检查 url 字段是否写成了代理卡地址,以及反向代理是否把 /a2a 转发到了后端服务。
调不通时按这个顺序查
代理卡返回 404:检查文件是否真的放在 /.well-known/ 路径下,以及 Web 服务器是否允许访问以点开头的目录。
Nginx 常见配置是 location /.well-known/ { root /var/www/agent; },确认 root 拼写和文件权限。
任务一直停在 pending 或 submitted:说明提交成功了,但服务端没有推进状态。
检查后台任务队列是否在消费,以及客户端是否真的在轮询 tasks/get。
有些实现要求客户端带上同一个任务 ID 查询,漏传会查不到。
流式响应超时:先确认 capabilities.streaming 与服务端实现一致。
不一致时把能力声明改为 false,用轮询方式先跑通,再单独调流式链路。
跨域被浏览器拦截:A2A 客户端通常是服务端程序,不涉及浏览器同源策略;
如果确实从网页发起,需要在任务端点和代理卡路径上都返回合适的 CORS 头。
验证配置是否真的生效
配置完成后至少做三项检查:
- 用
jq解析代理卡,确认url是一个可访问的绝对地址,且与客户端实际请求的地址一致。 - 提交一个最小任务,确认返回体里有任务 ID 和状态字段。
- 用同一个任务 ID 查询一次状态,确认服务端能返回该任务,而不是 404 或空结果。
如果服务端日志里能看到任务提交记录,但客户端拿不到结果,问题多半在状态查询环节,而不是代理卡本身。
把提交和查询分开看,能少走很多弯路。
两个常见疑问
代理卡必须放在 .well-known 下吗? 协议约定使用这个路径是为了让发现过程标准化,但客户端也可以配置成从任意 URL 拉取。
如果对方代理只认标准路径,就按标准放。
A2A 和直接调 HTTP API 有什么区别? 直接调 API 需要调用方事先知道每个接口的地址和参数;
A2A 多了一层代理卡来描述能力和入口,适合代理数量多、需要跨团队复用的场景。
单个固定接口没必要为了用协议而加一层。