Agent会互相调用,这件事放在今天已经算不上新闻。翻翻开源社区,AutoGen、CrewAI、LangGraph哪个都能让几个Agent你一言我一语地跑完一个任务。但如果你真把一套多Agent系统放进生产环境,大概率会遇到比“调不通”麻烦得多的问题:Agent A到底该不该调用Agent B?调用之后谁对结果负责?B能把哪些数据传给C?调用链拉了快十层,出问题又该找谁?这些问题本质上全都指向同一个方向——协作边界。而A2A 1.0这个协议,真正解决的也正是这件事。它不是在发明“Agent之间能通信”这个事实,而是给Agent之间的协作画出一条条可定义、可校验的边界线。这篇文章我想结合自己的项目经验,把这个容易被误解的协议聊透。
1. Agent互调早已是“能做”的问题,A2A 1.0解决的是“敢做”
1.1 多Agent协作的现状:调通不难,边界不清
我自己是从LangGraph和AutoGen那个年代玩过来的。早期搭多Agent系统,大部分人的做法都很朴素:几个Agent,互相传一段prompt,主Agent负责路由。跑通demo没有任何问题,但一旦业务复杂一点,几个Agent互相调用起来,就会出现A调用B、B又调用C、C又调回A的怪圈。很多时候不是工具不行,而是压根没有一个东西能约束“谁该做什么、谁不该做什么”。
为什么说“调通不难”?因为Agent之间的调用本质上就是API调用,你给我一个接口,我就能调你。Python里requests.post一发,任务就传过去了。问题从来不是传输,而是语义和边界。A怎么知道B能处理什么任务?A把任务发给B之后,B有没有权限看A传过去的数据?B执行失败了,这个责任算谁的?这些在一个纯API层面的“调用模型”里,都是没有标准答案的。
A2A 1.0的出现,坦白说不是第一个做Agent间通信的方案,但它把这些问题放到了协议的核心位置。它的设计思路不是“让Agent更容易互相调用”,而是“让Agent之间的协作边界可以被声明、被发现、被验证”。这个定位上的差异,影响的不只是协议实现,而是整个多Agent系统的架构思路。
1.2 协作边界不清的四个典型症状
我整理了一下生产环境里常见的“边界病”,基本可以归成四类,你会发现每一类都很眼熟。
症状一:循环调用。Agent A遇到一个订单查询任务,照着自己的能力列表觉得应该交给B。B收到任务觉得太复杂,又踢回给A。两边来回推诿,提示词里明明都写着“优先协作”,结果谁也没真正干活。根因是能力边界不清,彼此不知道对方能做什么、不能做什么,只能靠“猜”和“试”。
症状二:数据越权。客服Agent为了“确认用户身份”,把用户的手机号、收货地址甚至身份证号一股脑发给物流Agent,物流Agent又把这些数据写进自己的消息记录。这本质上是信息边界没设好,协议层面没有约束,Agent都会倾向于“多要一点数据”,反正传都传了。
症状三:责任真空。一个退款链路里四个Agent参与,最后退款失败,业务方来问是谁的问题,四个Agent都不承认。职责边界不明,任务没有明确归属,出了问题只能靠人肉翻日志,效率极低。
症状四:上下文膨胀。Agent之间来回传完整对话历史,每次调用都带上整坨上下文,token成本直接起飞。边界感弱的Agent会把“自己看到的所有信息”都转发出去,而不只是转发“完成任务所需的最小字段”。
这四类症状,靠提示词工程能压一阵子,但压不住。它们需要的是协议层面的结构约束,而A2A 1.0做的事,就是把这套约束变成协议的一部分。
2. A2A 1.0把“协作边界”具象成了协议要素
2.1 先分清A2A和MCP:一个管纵向接工具,一个管横向接Agent
先解决一个高频混淆:A2A不是MCP的升级版,我见过太多人把这两个概念混在一起聊。
MCP解决的是Agent怎么接入外部工具和资源,比如Agent去调内部数据库、调用外部API、读取一个文件。它是一条“纵向”的通道,让Agent能使用更多工具。在我自己的实践里,MCP更像给每个Agent“接上手和脚”。
A2A解决的是Agent和Agent之间的沟通方式,是一条“横向”的通道。它不关心你用什么框架实现的Agent,也不关心Agent内部调用什么工具,它只管Agent之间的“发言规则”。
打个不严谨的比方:MCP是让一个员工能熟练使用打印机,A2A是让两个员工能在同一个项目里分工协作、各自清楚彼此的工作边界。前者是“人机/工具界面”,后者是“智能体之间/合作界面”。
A2A 1.0的公开规范里,整套协议最重要的资产其实就是三个:Agent Card、Task生命周期、安全上下文。下面逐个拆开讲。
2.2 Agent Card:能力边界的公开声明
Agent Card是A2A协议里用来“自我介绍”的标准化文件。每个Agent启动后,会在固定端点对外提供一份JSON描述,内容包括Agent名称、提供什么能力、输入输出的格式、要求的认证方式等等。
其他Agent要协作时,第一步不是直接发消息,而是先“看卡”。A2A鼓励消费方先获取并缓存Agent Card,判断这个Agent能不能处理当前任务,再决定要不要发任务。这个过程本质上是把人的直觉判断(“B看起来能做这事”)变成了机器可读的契约校验。
我在自己的项目里写Agent Card时,最深的一个体会是:写清楚“不能做什么”比写清楚“能做什么”更重要。很多Agent Card会把能力写得特别宽泛,比如“提供订单相关服务”,结果其他Agent什么都往这儿发。更合理的写法是加上限制条件,比如“仅提供订单状态查询,不处理退款”,这会省掉大量无效调用。
Agent Card在协议里不只是一个静态文件,它是能力边界的最小单位。每个Agent对外宣称自己的能力,消费方信任这份声明,并在边界范围内交互,这就是A2A 1.0所定义的“边界”的第一层。
2.3 Task生命周期:职责边界的运行时契约
第二个关键要素是Task。A2A 1.0把一次Agent间交互抽象成一个有生命周期的任务:创建Task、给Agent发消息、Agent执行、回报状态、最终完成或取消。
为什么要强调这个?因为职责边界的核心是“归属”。一次协作发生问题时,必须能回答:这个任务是谁发起的?当前属于谁?处于什么状态?是谁在什么阶段失手了?
在协议层面,每个Task都有唯一ID,有明确的状态流转,Agent之间的对话围绕Task展开,而不是无边界的“随便聊”。这就像工作中创建了一条工单,所有沟通都挂在工单下面,天然带着责任归属。你永远不会找不到“这个活到底谁在跟”。
从工程角度看,Task生命周期还带来一个额外收益:可观测性。每个任务都有状态、有发起方、有执行方、有时间戳,链路追踪变得简单。相比“几个Agent互相扔消息,最后不知道谁搞定了”那种黑盒,Task机制把“谁在什么边界内干了什么”变成了可审计的信息。
2.4 安全上下文:信息边界的强制护栏
最后是安全上下文。A2A 1.0把认证、授权、数据范围这些内容单独拎出来,作为交互的一部分去定义,这其实是“信息边界”的落地。
实际遇到的需求通常是这样的:企业里同时跑了客服Agent、订单Agent、财务Agent。客服Agent可以访问用户联系方式,但订单Agent不应该把内部折扣逻辑暴露出去,财务Agent只能看到和账务相关的字段。这几个Agent要协作,但又不允许“裸奔式”共享数据。
在A2A的思路里,消息会带上安全上下文,比如调用方身份、可访问的数据范围、令牌的权限级别。接收方不只是“收到消息”,还会校验“这个消息里携带的凭据,有没有资格让我提供这些数据”。没有通过校验,即使对方是内部系统,也会被拒绝。
这个设计对整个行业来说,是把“信息边界”从口头约定升级成了运行时机制。之前做多Agent系统,数据访问控制主要靠开发者在代码里自己写判断,写在哪里都行、不写也行。A2A把它作为核心要素,逼着每个Agent在协作前先回答“我有没有权限处理这个请求”。边界不再是一种自觉,而是协议约束。
3. 实操落地:用A2A思路设计你自己的Agent边界
3.1 从Agent Card开始,先把能力“说清楚”
纸上谈兵聊完协议,说说怎么落地。你手头的多Agent系统不一定需要完整实现A2A规范,但我强烈建议先从“A2A风格”的Agent Card做起,这是成本极低、收益极高的改造。
怎么定义一个合理的Agent Card?我的经验是先回答四个问题:
- 这个Agent的核心职责是什么?用一句话讲清楚。
- 它接收什么结构的输入?产出什么结构的输出?
- 它能访问哪些外部系统或数据?哪些是禁区?
- 哪些任务会被它直接拒绝?列出明确的负面清单。
把这四个问题的答案写进JSON,就是Agent Card的雏形。下面是我在一个客服项目里用过的简化版Agent Card:
{ "name": "order-agent", "version": "1.0", "description": "负责订单状态查询,不处理退款与售后", "capabilities": [ { "id": "order_status", "input": {"order_id": "string"}, "output": {"status": "string", "eta": "string"} } ], "restrictions": [ "不处理退款申请", "不返回用户隐私字段" ], "security": { "auth_type": "api_key", "allowed_caller_agents": ["customer-agent"] } }这个Card的意义在于:customer-agent拿到之后,会知道自己可以发order_status这类任务,但不能让order-agent去处理退款,也不能期望它返回隐私字段。边界在协作发生之前就已经在双方“心智”里对齐了。
如果你用的是LangGraph、CrewAI这类框架,也不用一开始就把Agent Card做成真正的HTTP端点,可以先把它当作一份设计文档,放到代码仓库里。后面要升级成真正的端点,无非是加一个路由的事。
3.2 实现一个简化版A2A端点
做完了Agent Card,下一步是把Agent服务化,对外暴露一个能接收任务的标准入口。我在实践里最常用FastAPI,因为写起来快,而且自带类型校验。下面是一个高度简化但能跑的示例:
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class TaskRequest(BaseModel): task_id: str agent_from: str payload: dict @app.get("/.well-known/agent-card") def get_agent_card(): return { "name": "order-agent", "version": "1.0", "capabilities": [ { "id": "order_status", "input": {"order_id": "string"}, "output": {"status": "string", "eta": "string"} } ], "restrictions": ["不处理退款", "不返回隐私字段"] } @app.post("/tasks") async def create_task(req: TaskRequest): # 边界校验:这个任务在不在我的能力范围内? if req.payload.get("action") != "order_status": return {"status": "rejected", "reason": "out_of_capability"} # 边界校验:调用方在不在白名单里? if req.agent_from not in ["customer-agent", "gateway"]: return {"status": "rejected", "reason": "unauthorized_caller"} # 处理任务 order_id = req.payload.get("order_id") result = query_order_status(order_id) return {"status": "completed", "result": result}这个代码虽然简陋,但它把一个核心逻辑点亮了:在任务真正进入Agent大脑之前,先用结构化校验挡住“不该我干的活”。这一步能挡掉大量循环调用和数据越权。
在真实生产里,你还需要补上身份认证、任务超时、幂等处理这些细节。但从边界设计的角度看,上面的骨架模式已经完全够用。我见过不少项目连这一步都没做,Agent之间靠提示词约束,结果模型一升级,协作就开始群魔乱舞。
3.3 任务委托的边界判断逻辑
有了Agent Card和标准入口,Agent之间该怎么决定“要不要把这个任务委托出去”?我把这个判断逻辑总结成三步。
第一步,解析当前输入,判断是不是自己能力范围内的事。如果能干,就直接干,不要甩给别的Agent。这一步最常见的错误是“谦虚过头”——Agent动不动就找兄弟帮忙,导致整条链路拉得特别长。我在项目里见过一个Agent连“查询今日天气”都要委托一圈,纯粹是浪费。
第二步,如果不在自己能力范围内,去查目标Agent的Agent Card,看它有没有能力处理。这一步的核心是“能力匹配”,不看prompt里怎么描述,看结构化声明,判断要快、要准。
第三步,在发起任务前,确认调用方权限和要传递的数据范围。只传完成任务所需的最小字段,不传完整对话历史,不传用户的敏感字段。这一步做得好,能直接避免上下文膨胀和越权。
这套逻辑写进一个统一的路由层,比让每个Agent自由发挥要稳得多。我自己是这样做的:所有Agent之间的调用不直接点对点,而是先经过一个轻量级的“协作网关”,由网关做Agent Card的查询和边界判断。这本质上是把“边界”从Agent脑中抽离出来,变成基础设施,可控性大增。
4. 一个完整案例:售后客服系统的多Agent协作边界
4.1 场景拆解与角色定义
理论讲多了,来一个完整案例。前阵子朋友的公司要做售后客服系统,最初的思路是一个超大的全能Agent,后来发现根本撑不住:要同时用到订单数据、物流数据、退款规则、客服话术,一个Agent的上下文完全装不下,而且经常答非所问。
后来建议拆成四个角色:
- gateway-agent(入口网关):负责识别用户意图,做路由分发,不碰业务逻辑。
- order-agent(订单Agent):只查订单状态与基础信息,权限限定在订单系统。
- logistics-agent(物流Agent):只跟踪物流进度,权限限定在物流系统。
- refund-agent(退款Agent):负责退款规则判断与流程发起,权限限定在财务系统。
这四个角色各管一段,边界非常清楚,谁也别想“顺便”把别人的活干了。设计这套结构的核心好处是,每一个Agent的上下文都保持精简,能力单一,出问题时定位范围也小。
4.2 边界设计四问
给每个Agent定义边界时,我用了前面提到的“边界设计四问”,这里拿refund-agent完整过一遍。
第一问:能做什么?—— 判断某个订单是否符合退款条件,如果符合,发起退款申请。 第二问:需要什么输入?—— 订单号、用户身份标识、退款原因。产出什么?—— 退款申请单号,或者拒绝原因。 第三问:能访问哪些数据?—— 财务系统的退款规则、订单系统的退款状态;不能访问物流轨迹,不能访问用户完整联系方式。 第四问:什么任务会被拒绝?—— 任何不包含订单号的退款请求,任何“查一下物流走到哪了”的请求。
这四个问题问完,Agent Card基本成型。而且这个设计过程本身就是一次受益匪浅的团队复盘。我发现一个有意思的现象:很多多Agent系统设计不清,倒不是代码难写,而是压根没有人认真画过“谁负责什么”。这一步花的时间,后面会在调试和排障上十倍地挣回来。
4.3 协作流程中的边界校验点
这个系统跑起来之后,协作流程大概是这样的:
用户说“我的订单什么时候到”,gateway-agent收到后判断这不是自己能回答的问题,于是查Agent Card,发现logistics-agent能处理物流查询。它构造一个最小任务请求,只带订单号和用户角色标识,发给logistics-agent。
logistics-agent接到请求后先做三道校验:一是检查调用方gateway-agent是否在白名单;二是检查payload里有没有多余字段,确认没有私密数据就放行;三是确认自己是transport_tracking能力的执行方。查询成功后,回传结构化结果,gateway-agent再组织话术回复用户。
如果用户改问“我这个订单能不能退款”,gateway-agent查卡发现order-agent明确声明“不处理退款”,refund-agent才是干这个的,于是请求被转发到refund-agent。这一步看似小事,但在没有边界声明的系统里,order-agent很可能先把“能不能退款”接走,然后发现规则在财务系统里、自己没权限,再转给refund-agent,白白多绕一圈,还容易出错。
这套带边界校验的流程,代码量不一定比硬编码编排少,但维护体验完全不同。新增一个Agent时,只需要写好Agent Card,在协作网关注册一下,其他Agent就能自动发现它。以前要改一堆硬编码链路,现在改的是“边界声明”,系统的扩展性是质的提升。
5. 边界不清的典型问题与排查经验
5.1 多Agent系统常见问题速查表
实战中踩的坑多了,我把最典型的场景整理成一个速查表,放在手边特别有用。
| 表现 | 根因 | 排查思路 |
|---|---|---|
| Agent A和B互相反复调用,任务长期不落地 | 能力边界声明缺失,双方都以为对方能做 | 检查双方Agent Card,把“不能做什么”写进限制列表 |
| 某个Agent把不该传的数据传出去了 | 信息边界没有约束,消息里夹带私货 | 在网关层做payload白名单,只允许特定字段通过 |
| 任务失败后没人认领责任 | 任务缺少归属和生命周期管理 | 引入TaskId与状态机,每个Agent只对名下Task负责 |
| 链路上下文越传越大,成本飙升 | 每个Agent转发完整历史,信息边界感弱 | 约束每个任务只传最小字段,禁止转发无关上下文 |
| 新增一个Agent要改一堆硬编码路由 | 没有能力发现机制,所有调用都是点对点写死 | 收敛到统一协作网关,用Agent Card做自动发现 |
5.2 我在实战中踩过的几个坑
最后分享几个真实的踩坑记录,这些在官方文档里通常看不到。
第一个坑:一开始懒得写Agent Card,觉得“反正每个Agent都是我写的,我自己心里有数”。结果三个Agent上线第二天,客服Agent就把一个“用户要开发票”的请求发给了订单Agent,订单Agent没声明这个能力,又转发给财务Agent,财务Agent拒收,最后这个请求在系统里漂流了一整天才超时。那时候才意识到,系统里可以没有“默契”,“我心里有数”不算数,机器可读的声明才算数。
第二个坑:权限设计过度。刚开始我给每个Agent都配了细粒度的数据权限,每个字段都要校验,结果开发周期拖长一倍,Agent之间的调用延迟也涨了不少。后来我悟了,边界不是越细越好,而是要刚好保护住不能出事的点。退款Agent不能看物流轨迹,比“客服Agent只能看用户姓名的前两个字”重要得多。边界设计要有主次,先守住高风险信息,再谈细粒度。
第三个坑:忘记设计“超时与拒绝路径”。协作边界里必须明确“对方不响应怎么办”“任务被拒绝怎么办”。很多Agent默认对方一定能完成,结果对方一拒收就卡死。我在协作网关里加了兜底逻辑:任务被拒收时,返回“原任务发起方重新处理”,并带上明确的拒绝原因。这样边界不仅规定“谁能做什么”,还明确了“被边界挡住之后路怎么走”。这一点做晚了,会在生产环境付出代价。
第四个坑:边界声明写了,但没有版本管理。Agent改了一次能力范围,老版本的其他Agent还在按旧的Agent Card调用,结果频繁撞上已废弃的能力。后来我把Agent Card也纳入版本管理,每次变更走一次发布流程,调用方通过版本号判断是否继续缓存。这一步让协作稳定性提升了一个档次,尤其当团队人变多、Agent变多之后,没有版本约束的边界声明只会变成新的混乱源。
这些坑让我体会最深的是:多Agent系统的复杂度根本不在单Agent的“聪明程度”,而在于怎么让一群有自主性的模块在同一个业务目标下有序协作。A2A 1.0把边界放到了协议第一公民的位置,方向是对的。即便你暂时不用完整的A2A实现,它的这套边界思维——Agent Card、任务归属、最小权限传递——也值得在每一个多Agent项目里先落地。
最后再说一点我个人的体会。我见过太多团队,demo时期几个Agent互相调用得热火朝天,一到生产就熄火。市面上讲Agent框架的教程很多,讲“怎么让Agent更聪明”的也很多,但真正讲明白“怎么让多个Agent各自安分守己、不越界、不推诿”的内容很少。如果你手头也在做一个多Agent项目,我建议先别急着上框架,花半天时间,把你系统里每个Agent的能力边界、数据边界、责任边界写下来。写完之后你会发现,很多之前困扰你的“协作问题”,其实根本不是模型能力的问题,而是边界没有定义清楚的问题。这个梳理过程,本身就是整个多Agent工程里最值得投入的一步。