Appearance
Agent 间协作 A2A
A2A 用于一个数字员工在处理当前用户消息时, 通过知办AI Bus 受控调用另一个数字员工完成子任务。它属于 自定义 Agent Runtime 接入能力, 不是普通租户服务端 API。
典型链路:
text
用户消息
-> Agent A Runtime
-> Agent A 的 LLM 判断需要协作, 并生成给 Agent B 的干净任务指令
-> SDK A2A discover / invoke / get_edge
-> 知办AI Bus 做权限、白名单、深度、防环、审计和转发
-> Agent B Runtime 执行 request_payload.user_text
-> Agent A 汇总 Agent B 结果后回复用户端到端流程图
每个环节的输入输出示例
下面示例用“行政专员找法务顾问审阅供应商服务合同”说明完整链路。UUID 均为示例值; st_...、endpoint、内部 numeric ID 不应出现在业务 payload 里。
1. 用户消息进入 Agent A
输入: 客户端只发送给当前会话的普通用户消息, 不指定 A2A 目标。
json
{
"conversation_id": "conv-admin-20260710-001",
"agent_id": "agent-admin-uuid",
"role": "user",
"text": "帮我找法务顾问看一下这份供应商服务合同的付款节点、违约责任和保密条款风险, 然后汇总成审批前确认清单。",
"blocks": []
}输出: Bus 把当前轮消息派发给 Agent A Runtime。Agent A 此时还没有调用其它 Agent。
json
{
"session": {
"session_kind": "single",
"tenant_id": "tenant-uuid-001",
"user_id": "account-uuid-15011553109",
"conversation_id": "conv-admin-20260710-001",
"agent_id": "agent-admin-uuid"
},
"message": {
"role": "user",
"text": "帮我找法务顾问看一下这份供应商服务合同的付款节点、违约责任和保密条款风险, 然后汇总成审批前确认清单。",
"blocks": []
}
}2. Agent A 生成被调方任务
输入: Agent A 的 LLM 可以读取当前用户消息、当前会话上下文、可协作员工摘要。这里是 Agent A 内部决策, 不是 Bus 接口。
json
{
"current_user_message": "帮我找法务顾问看一下这份供应商服务合同的付款节点、违约责任和保密条款风险, 然后汇总成审批前确认清单。",
"caller_agent": {
"agent_id": "agent-admin-uuid",
"name": "行政专员",
"role": "行政协同与流程支持"
},
"available_agent_summaries": [
{
"agent_id": "agent-legal-uuid",
"name": "法务顾问",
"summary": "审阅合同条款、付款节点、违约责任、保密与合规风险。"
}
],
"conversation_context": [
{
"type": "plain",
"text": "用户正在准备供应商服务合同审批。"
}
]
}输出: Agent A LLM 选择目标, 并生成“给 Agent B 执行”的干净任务。这个输出会进入后续 request_payload.user_text。
json
{
"target_agent_id": "agent-legal-id",
"target_agent_name": "法务顾问",
"request_payload": {
"user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{
"type": "plain",
"text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
}
]
}
}注意: user_text 里不应再出现“帮我找法务顾问”“联系某某”“你就是被联系的人”等路由话术。
3. discover: 查询当前可协作候选
输入: Agent A 通过 SDK 或 runtime tool 发起 discover。实际 HTTP 鉴权由 SDK 使用 agent-bound runtime access token 完成。
http
POST /zap/v1/internal/a2a/discover
Authorization: Bearer st_agent_bound_runtime_access
Content-Type: application/jsonjson
{
"query": "供应商合同付款节点、违约责任、保密条款风险审阅",
"tags": ["legal"],
"mode": "sync",
"limit": 20,
"cursor": ""
}输出: Bus 返回可发现候选。discover 只是候选提示, 不是最终授权; invoke 还会重新校验。
json
{
"agents": [
{
"agent_id": "agent-legal-uuid",
"name": "法务顾问",
"summary": "合同、条款、合规和法律风险审阅。",
"when_to_use": ["合同付款节点审阅", "违约责任确认", "保密条款风险评估"],
"when_not_to_use": ["普通会议室预订", "报销单录入"],
"examples": [
{
"input": "审阅供应商合同里的付款节点和保密条款",
"expect": "输出法律风险点和审批前确认清单"
}
],
"tags": ["legal", "contract"],
"input_schema": {
"type": "object",
"properties": {
"user_text": {"type": "string"},
"context_blocks": {"type": "array"}
},
"required": ["user_text"]
},
"supported_modes": ["sync", "async"],
"risk_level": "medium",
"requires_approval": false,
"match_score": 0.91
}
],
"next_cursor": "",
"confidence": 0.91,
"should_clarify": false,
"clarify_question": ""
}4. invoke: 创建 Agent A 到 Agent B 的任务 edge
输入: Agent A 指定目标 Agent B, 并把 LLM 生成的干净任务放入 request_payload.user_text。
http
POST /zap/v1/internal/a2a/invoke
Authorization: Bearer st_agent_bound_runtime_access
Content-Type: application/jsonjson
{
"to_agent_id": "agent-legal-uuid",
"invoke_kind": "sync",
"idempotency_key": "conv-admin-20260710-001:msg-0007:tool-a2a-legal-001",
"request_payload": {
"user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{
"type": "plain",
"text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
}
]
}
}链式调用时, 被调方如果确实需要继续调用第三个 Agent, 还要带上父 edge:
json
{
"to_agent_id": "agent-finance-uuid",
"invoke_kind": "sync",
"idempotency_key": "edge-legal-uuid:tool-a2a-finance-001",
"parent_edge_id": "edge-legal-uuid",
"parent_edge_created_at": "2026-07-10T02:31:20.123456Z",
"request_payload": {
"user_text": "核对该供应商合同的付款周期和发票要求是否符合财务审批口径。",
"context_blocks": [
{
"type": "plain",
"text": "这是法务顾问在审阅合同风险时发起的财务协作。"
}
]
}
}输出: Bus 创建 edge 并返回回执。调用方后续必须用 edge_id + edge_created_at 查询状态。
json
{
"edge_id": "edge-legal-uuid",
"edge_created_at": "2026-07-10T02:31:20.123456Z",
"status": "pending",
"approval_request_id": ""
}approval_request_id 仅在本次调用进入人工审批时返回;普通调用为空并省略。
5. Bus 治理与 edge 记录
输入: Bus 使用 invoke 请求、agent-bound token、租户上下文和协作配置做校验。
json
{
"tenant_id": "tenant-uuid-001",
"from_agent_id": "agent-admin-uuid",
"to_agent_id": "agent-legal-uuid",
"user_id": "account-uuid-15011553109",
"checks": {
"from_agent_has_a2a_invoke": true,
"to_agent_callable_by_from_agent": true,
"same_tenant": true,
"depth_within_limit": true,
"chain_budget_available": true,
"loop_guard_passed": true,
"runtime_instance_ready": true
},
"request_payload": {
"user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{
"type": "plain",
"text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
}
]
}
}输出: Bus 持久化 edge, 并只把脱敏副本用于审计和管理页展示。
json
{
"edge_id": "edge-legal-uuid",
"status": "running",
"invoke_kind": "sync",
"from_agent_id": "agent-admin-uuid",
"to_agent_id": "agent-legal-uuid",
"root_edge_id": "edge-legal-id",
"parent_edge_id": "",
"depth": 1,
"origin_conversation_id": "conv-admin-20260710-001",
"request_payload_redacted": {
"user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{
"type": "plain",
"text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
}
]
}
}6. Bus 派发给 Agent B Runtime
输入: 对 external ZAP runtime, Bus 创建 A2A session, 再发送一条 user message。bus_backchannel_token 只给 Agent B runtime 使用, 不得进日志和回复。
http
POST /zap/v1/sessions
Authorization: Bearer runtime_dispatch_token
Content-Type: application/jsonjson
{
"agent_key": "main",
"session_kind": "a2a",
"user": {
"tenant_id": "tenant-uuid-001",
"user_id": "account-uuid-15011553109"
},
"conversation_context_hint": {
"tenant_id": "tenant-uuid-001",
"account_id": "account-uuid-15011553109",
"purpose": "a2a",
"a2a_edge_id": "edge-legal-uuid"
},
"initiator": {
"kind": "agent",
"agent_id": "agent-admin-uuid",
"tenant_id": "tenant-uuid-001"
},
"bus_backchannel_token": "st_child_secret_never_log"
}http
POST /zap/v1/sessions/sess-a2a-legal-001/messages
Authorization: Bearer runtime_dispatch_token
Content-Type: application/jsonjson
{
"role": "user",
"text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"blocks": [
{
"type": "plain",
"text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
}
]
}输出: Agent B handler 看到的是 A2A 上下文和当前任务。它不需要知道用户原始路由话术。
json
{
"ctx": {
"is_a2a": true,
"a2a_edge_id": "edge-legal-uuid",
"initiator": {
"kind": "agent",
"agent_id": "agent-admin-uuid",
"tenant_id": "tenant-uuid-001"
}
},
"inbound": {
"role": "user",
"text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"blocks": [
{
"type": "plain",
"text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
}
]
}
}OpenClaw / Hermes 被调方也是同一个语义: Bus 把 request_payload.user_text 作为当前任务文本派发给对应托管 runtime; runtime 不需要原生理解 Bus A2A 协议。
7. Agent B 返回子任务结果
输入: Agent B 执行收到的任务。
json
{
"task": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{
"type": "plain",
"text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
}
]
}输出: Agent B 返回当前子任务结果。Bus 会把结果写回 edge; 入库副本会做结构化脱敏。
json
{
"status": "done",
"response_payload": {
"text": "法务审阅意见:\n1. 付款节点应与明确交付物、验收标准和验收时间绑定, 避免仅按自然日期付款。\n2. 违约责任需覆盖延期交付、质量不达标、提前终止和未配合整改等场景, 并约定可执行的违约金或赔偿上限。\n3. 保密条款应明确保密信息范围、接触人员范围、保存期限、返还/销毁义务和违约责任。"
}
}8. Agent A 查询 edge 并汇总最终回复
输入: Agent A 用 invoke 回执里的 edge_id 和 edge_created_at 查询结果。
http
GET /zap/v1/internal/a2a/edges/edge-legal-uuid?created_at=2026-07-10T02%3A31%3A20.123456Z
Authorization: Bearer st_agent_bound_runtime_access输出: Bus 返回 edge 终态和脱敏后的被调方结果。
json
{
"edge_id": "edge-legal-uuid",
"status": "done",
"invoke_kind": "sync",
"from_agent_id": "agent-admin-uuid",
"to_agent_id": "agent-legal-uuid",
"created_at": "2026-07-10T02:31:20.123456Z",
"updated_at": "2026-07-10T02:31:28.654321Z",
"request_payload": {
"user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{
"type": "plain",
"text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
}
]
},
"response_payload": {
"text": "法务审阅意见:\n1. 付款节点应与明确交付物、验收标准和验收时间绑定, 避免仅按自然日期付款。\n2. 违约责任需覆盖延期交付、质量不达标、提前终止和未配合整改等场景, 并约定可执行的违约金或赔偿上限。\n3. 保密条款应明确保密信息范围、接触人员范围、保存期限、返还/销毁义务和违约责任。"
},
"error_message": ""
}Agent A 最终给用户的回复必须由 Agent A 自己生成, 不能由 Bus 拼接, 也不能把 Agent B 原文无上下文地直接当作 Agent A 正文。
json
{
"role": "assistant",
"text": "我已请法务顾问审阅这份供应商服务合同, 审批前建议重点确认三项:\n\n1. 付款节点要绑定明确交付物、验收标准和验收时间, 不建议只按自然日期付款。\n2. 违约责任要覆盖延期交付、质量不达标、提前终止和未配合整改等场景, 并明确违约金或赔偿上限。\n3. 保密条款要补齐保密信息范围、接触人员范围、保存期限、返还/销毁义务和违约责任。\n\n建议你在提交正式审批前, 先让采购/业务负责人补齐合同正文、报价单、验收标准和预算依据, 再发起用印流程。",
"blocks": [
{
"type": "markdown",
"text": "我已请法务顾问审阅这份供应商服务合同..."
}
]
}放在哪一层理解
| 层级 | 是否负责 A2A | 说明 |
|---|---|---|
| 客户端 | 否 | 客户端只发送用户消息, 不直接发起 A2A。 |
| 业务服务端 API | 否 | /api/external/v1/* 用于知识库、记忆、组织、通知等能力, 不用于 Agent 间调度。 |
| 自定义 Agent Runtime | 是 | Agent A 在运行时决定是否调用 Agent B, 并通过 SDK 的 A2A helper 调 Bus。 |
| Bus | 治理与转发 | Bus 不生成任务, 不替 Agent A 拼最终回复; 只做鉴权、配额、防环、审计和派发。 |
| 被调 Agent Runtime | 是 | Agent B 收到当前轮干净任务并执行。 |
调用时机
适合 A2A 的场景:
- 行政专员需要法务顾问审阅合同风险后再汇总审批清单。
- 客户跟进助手需要财税助手核对续费发票口径。
- 研发工程师需要行政协同专员补会议、通知、排期准备。
- 一个自定义 Agent 需要另一个岗位 Agent 输出专业意见, 再由自己形成最终答复。
不适合 A2A 的场景:
- 只是查知识库、记忆、组织或发送通知, 直接用对应服务端 API。
- 用户只是问当前 Agent 自己能做什么, 不应为了回答能力介绍而调用其它 Agent。
- 客户端想指定两个 Agent 对话, 客户端不直接调 A2A; 应通过正常消息交给当前 Agent Runtime 判断。
核心契约
所有 A2A invoke 统一为:
- Agent A 的 LLM 根据当前用户消息、被调方画像和当前任务生成给 Agent B 的任务指令。
- 任务指令放到
request_payload.user_text。 - 可选低敏背景放到
request_payload.context_blocks。 - Bus 只做治理和仅泄露项清洗, 不生成任务、不清路由词、不改写业务语义。
- Agent B 只把
request_payload.user_text当作当前轮要执行的任务。
推荐 payload:
json
{
"user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{
"type": "plain",
"text": "来自行政专员的合同协作请求。"
}
]
}不要这样传:
json
{
"instruction": "帮我找法务顾问看一下合同",
"expected_output": "给我结果"
}也不要把下面内容放进 payload:
session_key- bearer token / API key / client secret
- runtime endpoint
- Bus 内部 id、候选列表、权限细节
- “你就是被联系的人”“不要再找你自己”这类元指令
Python SDK 示例
python
from zhiban_agent_sdk import ZhibanClient
with ZhibanClient.for_runtime_access(
ZHIBAN_BASE_URL,
ZHIBAN_CLIENT_ID,
ZHIBAN_CLIENT_SECRET,
tenant_id=ZHIBAN_TENANT_ID,
agent_id=AGENT_A_UUID,
account_id=ACCOUNT_UUID, # 调平台托管 OpenClaw / Hermes 时用于选择当前用户实例
) as client:
candidates = client.a2a.discover(
limit=20,
query="需要法务审阅供应商合同风险",
).agents
legal = next(a for a in candidates if "法务" in a.name)
ack = client.a2a.invoke(
to_agent_id=legal.agent_id,
invoke_kind="sync",
idempotency_key="message-uuid:tool-call-1",
request_payload={
"user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{"type": "plain", "text": "来自行政专员的合同协作请求。"}
],
},
)
edge = client.a2a.get_edge(ack.edge_id, created_at=ack.edge_created_at)
print(edge["status"], edge.get("response_payload"))Go SDK 示例
go
payload := json.RawMessage(`{
"user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
"context_blocks": [
{"type": "plain", "text": "来自行政专员的合同协作请求。"}
]
}`)
ack, err := client.A2A().Invoke(ctx, zhibansdk.InvokeRequest{
ToAgentUUID: legalAgentUUID,
InvokeKind: "sync",
IdempotencyKey: "message-uuid:tool-call-1",
RequestPayload: payload,
})
if err != nil {
return err
}
edge, err := client.A2A().GetEdge(ctx, ack.EdgeUUID, ack.EdgeCreatedAt)
if err != nil {
return err
}
fmt.Println(edge.Status, string(edge.ResponsePayload))discover 与 invoke 的关系
discover 只返回当前上下文中可被发现的候选 Agent, 供 Agent A 的 LLM 或规则选择目标。它不是授权结果。
invoke 会重新校验:
- Agent A 是否有
a2a_invoke发起能力。 - Agent B 是否允许 Agent A 调用。
- 双方是否在同一租户授权范围内。
- 当前调用链是否超过深度、预算或防环限制。
- 被调 Agent Runtime 是否有可用实例。
因此, 不要把 discover 结果缓存成永久可调用清单。可以在一次对话或短时间内缓存候选列表, 但每次 invoke 仍以 Bus 的校验为准。
错误处理
| 错误码 | 含义 | 建议处理 |
|---|---|---|
a2a.no_candidate | 没有可发现的目标 Agent | 向用户说明暂时找不到合适协作对象, 或让用户指定更明确岗位。 |
a2a.runtime_unavailable | 被调方 runtime 实例不可用 | 提示稍后重试, 并让管理员检查对应托管环境实例。 |
a2a.auth_or_scope | 凭证或 scope 不满足 | 检查 Agent SDK 凭证、runtime access token 和 agent_id 绑定。 |
a2a.chain_timeout | 等待被调方结果超时 | 返回可理解的暂时失败, 不编造被调方结论。 |
a2a.awaiting_approval | 本次调用需要人工审批 | 告知用户已进入审批等待。 |
a2a.dropped_budget / a2a.dropped_loop | 超预算或检测到循环 | 停止继续调用, 汇总已有结果。 |
实现建议
- Agent A 应在调用前用 LLM 生成
user_text, 规则只做兜底和敏感字段清洗。 idempotency_key用当前消息和当前 tool call 生成; 同一次重试复用, 新用户消息必须换新 key。- 被调方如果收到 A2A 入站请求, 默认不要再自动触发泛化 A2A; 只有任务内容明确要求继续协作时才考虑链式调用。
- 真实业务回复由 Agent A 生成: Agent B 只提供子任务结果, Bus 不替 Agent A 总结。
- 日志里可以记录 edge、阶段、目标 Agent 名称和脱敏后的任务摘要; 不记录 token、runtime key 或完整敏感 payload。