
前阵子帮一个团队搭内部的多智能体平台聊到一半我发现他们最缺的不是模型而是一套“互认”的规矩。里面的 Agent 有七八个各写各的工具一个用 function calling一个直接发 HTTP 请求还有一个人干脆把数据导成 CSV 再人工喂给下一个节点。每次加一个新 Agent都得把人叫齐重新对接口、对字段、对调用方式。后来我们统一用 DeepAgents 做编排、MCP 接工具、A2A 做 Agent 之间的通信、Skills 沉淀能力三天把原来那套混乱的脚本重构成一个可以横向加节点的集群。这篇文章就把这套组合怎么落地讲清楚适合正在搭多智能体、或者刚被各种协议名词绕晕的开发者。1. 为什么单个 Agent 够用、多个 Agent 就“乱”四个摩擦点拆开看1.1 单体 Agent 的天花板不只是上下文很多人一开始做 Agent都是把“所有能力”塞进一个模型里。要写代码就配写代码的提示词要查资料就配搜索工具要读文件就配文件解析器。前期确实能跑到了后面你会发现上下文越塞越满模型调用越来越慢工具定义越来越多真正需要它专注做某件事的时候反而容易东拉西扯。这不是模型的错而是架构已经不适合了。一个 Agent 的核心价值在于“聚焦”聚焦一个职责、一份上下文、一组工具。一旦职责边界不清晰你根本没法判断某次错误回答到底是提示词写得不好还是工具调用返回的数据污染了推理过程。这也是多智能体架构出现的最根本动机——把大问题拆成多个小问题每个小问题由一个专门的 Agent 处理。1.2 摩擦点一工具接入各写各的无法互相复用拆成多个 Agent 之后第一个暴露出来的问题就是工具接入方式不统一。Agent A 用函数调用直接调 Python 方法Agent B 用 REST APIAgent C 干脆把逻辑写死在代码里。表面上看它们都能“用工具”实际上这些能力完全是孤岛A 能用数据库查询B 想用的时候拿不到只能自己再写一遍。更麻烦的是工具描述和参数格式也可能不一样。同一个“查库存”能力在 A 那边是get_stock(item_id)在 B 那边是POST /api/stock/query字段名还对不上。多智能体一旦多起来这种重复建设造成的维护成本是指数级上升的因为每个工具都有多套实现、多个“半对不对”的版本。1.3 摩擦点二、三、四互发现、技能复用、编排拓扑第二类摩擦是“互相发现”的困难。A 能做什么B 能做什么没有任何人知道。你想把任务从 A 转给 B只能靠硬编码在 A 的代码里写死“如果遇到某类任务就调用 B 的地址”。这种硬编码在节点少的时候勉强可控节点一多谁负责什么、当前是否可用、负载怎么样全凭猜。第三类摩擦是“技能难沉淀”。你花了很长时间调出来的提示词、少样本示例、校验规则只活在某一个 Agent 的配置里。换个新 Agent又得重新写一遍。更别提线上出问题时你根本说不清某个行为到底是哪份配置导致的。第四类摩擦是编排拓扑僵化。任务链路一旦写死改一个节点就要改整条链路。项目初期可能只有三个 Agent直接 A→B→C 没问题到了二十个 Agent 的时候这种写死的方式就是灾难任何 A→D→F→B 的新链路都要重新开发。1.4 四个组件正好各管一段我们在实践里把这些问题对应到四层设施上分工非常清楚摩擦点解决层落地形态工具接入不统一MCP一套协议统一工具调用Agent 只认工具名和参数Agent 互发现与互操作A2AAgentCard 声明能力任务消息互通技能难沉淀Skills标准化的“能力包”安装即用编排拓扑僵化DeepAgents注册中心、路由策略、生命周期管理这几个组件不是“谁替代谁”的关系。DeepAgents 是框架层负责把前面的东西组装起来MCP、A2A、Skills 更像是“连接件”和“内容件”一个解决工具怎么用一个解决 Agent 怎么聊一个解决能力怎么打包。2. DeepAgents 编排模型注册、路由与拓扑怎么设计才不僵化2.1 Agent 启动后先“报到”注册中心是编排的地基按 DeepAgents 这类框架的通用思路第一步不是写业务代码而是让每个 Agent 启动后先向编排器“报到”。报到的内容一般包括Agent 名称、能力标签、服务地址、当前状态、可处理的负载等级。有点像微服务里的注册中心但多了一层“能力语义”不只是知道服务在哪更要知道它能干什么。这个注册动作非常重要。它把“有哪些 Agent”这件事从代码里抽了出来变成了一张动态可查询的表。你可以在运行时查询“当前谁能做法律文档审核”然后拿到一串候选 Agent再结合负载决定把任务给谁。没有这一层多智能体本质上还是手工连线。2.2 路由别硬编码显式指定、能力标签、层级委派三种都行编排器拿到任务后路由策略一般有三种。第一种是显式指定你明确说“这个任务交给 Agent B”适合固定流程第二种是能力标签匹配任务描述里带#code_review这类标签编排器根据注册表自动路由适合能力动态变化的场景第三种是层级委派编排器先把任务交给一个“主管 Agent”由它拆解成子任务再分发给其他 Agent。我们实际项目里用得最多的是标签路由加层级委派组合。比如客服工单进来编排器先按#triage找到分类 Agent分类 Agent 再根据结果把任务派给退款组、技术组或投诉组。新增加一个支持渠道时只要新 Agent 注册时打上对应标签路由不用改任何代码这才能真正叫“可编排”。2.3 生命周期与状态机超时、重试、幂等一个都不能少多 Agent 系统看起来是“对话”本质上其实是“分布式任务调度”。每个任务都应该有明确的状态入队、运行、成功、失败、取消。DeepAgents 这类编排器一般把任务状态机管在框架层Agent 自己不需要操心“我这个任务超时了怎么办”只需要上报进度超时和重试由编排器统一处理。这里我想特别强调幂等设计。任务重试可不是简单“再跑一次”就行如果一个写数据库的任务在超时后又被重试两次可能出现重复写入。我们的做法是每个任务带上一个全局唯一requestId所有 Agent 和工具都把它当作幂等键透传。数据写入前先查一下这个requestId是否处理过处理过就直接返回上一次的结果。这个习惯越早养成越省事否则后面每加一个会写外部系统的 Agent都在给系统埋雷。2.4 拓扑别一上来就整网格三种基本形态怎么选多 Agent 系统的拓扑主要有三种星型、网格、层级。星型是指所有 Agent 都连到一个编排器简单、好排查但编排器压力大网格是每个 Agent 都能直接和其他 Agent 通信灵活但排错靠日志非常痛苦层级则是把 Agent 分成多个域每个域里有自己的主管域之间再通过顶层编排器互通适合规模较大的系统。我给团队的建议一直都是初始项目用星型节点超过二三十个后再拆层级域。网络拓扑图越复杂线上定位问题的成本就越高。可扩展不等于一开始就铺全连接能加节点、能平滑升级这才是扩展性的本质。3. MCP 是把工具变成“即插即用”的那层皮肤协议与安全边界3.1 MCP 的核心逻辑Agent 不再关心工具“长什么样”MCP 出现之前接入工具的姿势五花八门。MCP 的做法是定义一层标准接口工具提供方只要实现一个 MCP Server工具消费方也就是 Agent 侧通过 MCP Client 去发现和调用工具。Agent 看到的永远是“工具列表、工具描述、输入输出格式”不用关心工具背后是本地脚本、远程服务还是数据库连接。这套设计很像 USB——外设厂商不用关心电脑是哪家生产的电脑也不用关心鼠标内部怎么实现。协议一旦统一工具接入成本就从“写一次性代码”变成了“写一个符合规范的适配层”。我们团队后来新接内部系统基本都是先问“有没有 MCP Server”没有就顺手写一个写完所有 Agent 都能用不再是某个 Agent 的私有资产。3.2 写 MCP Server 的几个关键决策按顺序来很多人第一次写 MCP Server上来就写业务逻辑容易漏掉几个前置决策。我的建议是先定传输方式本机进程内用 stdio简单可靠跨机器、跨服务用 Streamable HTTP能支持流式输出和长任务。不要一上来就追求全协议支持能满足当前场景就好。第二步是定义工具描述。注意工具描述不是写给人看的注释是写给模型看的说明书。一个工具的 name、description、inputSchema 写得好不好直接决定模型什么时候会调用它、调用时会不会传错参数。这里有来自实测的经验description 里一定要写“什么时候不要用它”模型误调用的概率会明显下降。比如“查询订单”工具我会在描述里加一句“只能查已登录用户自己的订单不要接受任意用户ID作为参数”——这既是调用约束也是安全边界的一部分。第三步才是实现逻辑和鉴权。MCP 工具对外暴露的是能力内部一定要做权限校验不能因为 Agent 能调用就认为调用者是可信的。{ name: query_order, description: 按订单号查询当前用户的订单详情。只能查询当前登录用户自己的订单不能接受任意 userId 参数。, inputSchema: { type: object, properties: { order_id: { type: string, description: 订单号 } }, required: [order_id] } }3.3 流式输出和分页别让工具变成“返回大 JSON”的怪物工具调用最容易被忽略的问题是大结果集。有些工具一查就是几百上千行数据全塞给模型既费 token 又容易超时。我们的处理方式是普通结果直接返回大结果用分页参数返回一批摘要加一个next_page_token若工具本身是长时间执行的异步任务则通过 MCP 的流式机制把中间进度推给 Agent而不是让 Agent 傻等完成才返回。我在实际项目里还踩过一个很现实的坑MCP Server 在开发环境能访问所有数据库上线后没做最小权限收窄就有 Agent 在误操作时读到不该读的数据。安全边界必须和功能一起设计功能代码写完之后回头补权限大概率会漏。上生产前把每个工具实际需要的权限列一张表然后用最小权限账号跑一轮回归测试这个步骤不能省。3.4 MCP 常见使用场景从 IDE 到调试器关键在“把能力标准化”现在 MCP 生态已经很广了常见的有 IDE 补全、浏览器操作、数据库查询、设计稿标注、反汇编工具这类。比如调试器插件接 MCP 后Agent 可以直接在断点位置读寄存器、看堆栈不需要自己解析命令行输出设计工具接 MCP 后Agent 可以直接拿到图层尺码和标注信息。本质都一样把原来只有人能看懂的界面或命令变成 Agent 能调用的结构化接口。我比较推荐在团队内部先挑一个高频工具做 MCP 化试点比如数据库查询跑通之后再复制到更多工具上。不要一上来就把所有系统都 MCP 化协议的价值在于统一但前提是业务节奏能跟上。4. A2A 让 Agent 之间说同一种语言从 AgentCard 到任务生命周期4.1 A2A 解决的是“Agent 和 Agent 怎么协作”的问题MCP 解决的是 Agent 用工具A2A 解决的是 Agent 之间互相派活、互相查进度、互相取消任务。A2A 协议本身基于 JSON-RPC over HTTP但它真正重要的两个设计是AgentCard 和任务生命周期。AgentCard 相当于每个 Agent 的名片放在一个固定路径比如/.well-known/agent-card.json。上面写清楚这个 Agent 叫什么、能做什么、有哪些技能、接收什么类型的任务、用什么端点和协议通信。别的 Agent 或编排器想看“谁负责什么”拉一圈 AgentCard 就知道了。这直接解决了我们前面讲的“互发现”问题。任务生命周期则是把“Agent 间对话”规范成“任务状态管理”。你给另一个 Agent 派一个任务它接不接、跑到哪一步、结果有没有产出都有明确状态。A2A 定义了一套消息和任务操作包括提交任务、查询任务、发送消息、取消任务、订阅状态变更避免双方靠“来回发消息猜进度”。4.2 把现有 Agent 暴露成 A2A 端点四步走我自己把一个只靠内部函数调用的老 Agent 改成 A2A 可访问大概花了一个下午。核心思路不是重写业务逻辑而是加一个适配层。第一步给 Agent 增加一个 HTTP 服务把协议端点暴露出来第二步写好 AgentCard 文件声明它的名称、能力、技能和任务处理 URL第三步实现几个核心协议方法主要是message/send、tasks/send、tasks/get、tasks/cancel、tasks/resubscribe第四步把内部处理逻辑包成一个统一入口外面收到的 A2A 消息转成内部指令处理完再把结果转成 A2A 的 message 和 artifact 返回。这一步的关键是别把 A2A 状态机和业务逻辑耦合在一起。A2A 状态是协议层的事业务逻辑完全可以继续用原有代码只是外层套了一层“协议翻译”。这样即使以后换了不同的 Agent 框架改动也只在适配层。4.3 AgentCard 里到底写什么一个精简例子很多人在第一步就卡住不知道 AgentCard 该放什么字段。下面是一个精简但完整的例子字段顺序可以按照自己的需要调整但核心信息不能少身份、能力描述、端点、技能列表。{ name: weekly-report-agent, description: 负责从数据库读取项目数据并生成周报, url: https://agent.internal.example.com/report, version: 1.2.0, capabilities: { task: true, streaming: true }, skills: [sql-query, markdown-formatter], endpoints: [ { protocol: a2a, url: https://agent.internal.example.com/a2a } ] }需要注意AgentCard 里的description和skills会被其他 Agent 或编排器用来做路由。写的时候要多站在“调用方”角度让别的 Agent 一眼看出这个 Agent 适合处理哪类任务而不是写一堆泛泛而谈的话。4.4 A2A 和 MCP 千万别混用一个是协作一个是调用新手最容易犯的错是把 A2A 当成“远程函数调用”来用让一个 Agent 直接通过 A2A 调另一个 Agent 的某个工具方法。实际上A2A 设计的单位是“任务”而不是“函数”。你给另一个 Agent 发的是“帮我处理这件事”而不是“帮我执行这个方法”。两者的语义差别很大函数调用要求确定的输入输出任务协作允许对方自主规划、异步执行、中途反馈。如果只是要复用工具能力正确姿势是走 MCP如果是要把一个子任务整体委托给另一个 Agent才走 A2A。有时候一个 Agent 的服务也会被包装成 MCP 工具供外部 Agent 调用但这是一种“工具化封装”并不改变 A2A 作为 Agent 协作通道的本质。5. Skills 的本质是“能力包”构建、分发与权限审查5.1 Skills 和 MCP 的分工“能连什么”和“会做什么”Skills 这个概念这些年被反复提及核心就是一句话把 Agent 的做事方法打包成一个可安装、可卸载、可版本化的能力单元。MCP 管的是“能连什么”Skills 管的是“会做什么”。举个例子你希望 Agent 会写符合团队规范的前端代码。MCP 只能让它访问设计稿、访问代码库但“怎么写才符合规范”这件事需要描述、示例、检查清单这就是 Skill 的范畴。一个 Skill 内部可以引用多个 MCP 工具也可以完全没有工具只是纯提示词和决策规则。两者互补不是替代。5.2 一个标准 Skill 的目录结构和清单文件我在团队里推行 Skills 时用的目录结构很轻。每个 Skill 是一个独立目录里面有一个SKILL.md作为入口必要时放scripts目录存代码references目录存参考资料。SKILL.md的内容建议包括技能名称、一句描述、什么时候启用、什么时候禁用、具体执行步骤、输入要求的格式、输出结果的格式、验收清单。描述部分一定要写“禁用场景”这跟 MCP 工具描述是同一个逻辑模型不知道边界就容易乱用。一个写代码审查的 Skill 如果没写“不要用于重构大型遗留模块”它可能会在完全不适合的场景里强行套规则。5.3 分发方式从 Git 仓库到内部市场Skills 落地以后分发问题就来了。我们最开始就是每个 Agent 一台机器手动拷目录后来发现新版 Skill 发布之后线上 Agent 还在用旧版特别的坑。于是改成统一从内部 Git 仓库拉取每条 Skill 有独立的版本号Agent 启动时按锁定的版本安装。再后来多一点团队参与就搭了一个极简的内部市场上传 Skill 包、填写清单、管理员审核、自动分发。审核环节很重要尤其是包含代码的 Skill。我们要求任何 Skill 里的脚本必须经过代码评审不能只是看看提示词就放行。因为 Skill 是能影响 Agent 行为的一条写着“读取项目根目录所有文件”的 Skill授权范围如果没写清楚可能在多个 Agent 上造成信息泄露。5.4 实际收益新 Agent 上线时间从半天缩短到十分钟我们做了一次对比之前新加一个 Agent要人工配置提示词、工具、输出规范差不多半天后来把能沉淀的流程全部写成 Skill新 Agent 安装三五个 Skill十分钟就具备同样的基础能力。这对“可扩展”这三个字是实打实的贡献。我还想提醒一句不是所有能力都值得做成 Skill。如果一个技能只在一个 Agent 上用一次塞进 Skill 反而增加维护负担。我的判断标准是至少三个 Agent 会复用或者这个技能本身需要严格流程管控才值得打包成 Skill。6. 最小可运行集群搭一套 DeepAgents MCP A2A Skills 的落地步骤6.1 先明确目标架构一个编排器、两个 Worker、一个工具服务第一次搭这套体系不要追求“全网互联”。我建议的最小可运行架构是一个 DeepAgents 编排器、两个带 A2A 出口的 Worker Agent、一个 MCP Server、一个 Skills 目录。组件少问题定位就快链路跑通后再加组件也只是重复同样的注册动作。组件职责关键配置编排器接收任务、路由、状态管理注册表、路由规则、超时策略Worker A处理数据查询并生成摘要注册能力标签、A2A endpointWorker B负责格式化输出与发送通知注册能力标签、A2A endpointMCP Server提供数据库/文件等工具工具 schema、鉴权配置Skills 目录存放 Agent 可安装技能包SKILL.md 清单6.2 落地五步从启动编排器到跑通链路第一步先起编排器。不需要复杂界面先把控制台和健康检查跑起来确认核心服务在线。第二步挂载 MCP Server。把数据库查询工具通过 MCP 暴露出来然后在编排器里配置 MCP Client 地址。这一步先不连别的直接用 curl 验证 MCP 工具能被正常发现和调用。第三步让 Worker 暴露 A2A 端点。两个 Worker 各写各的业务逻辑但对外统一通过 A2A 端点接收任务并各自写好 AgentCard。用编排器拉取两个 AgentCard确认它们被正确识别。第四步安装 Skills。把“数据摘要生成”和“通知文案规范”各自做成 Skill挂到对应 Worker 上。第五步提交一个完整任务比如“统计本周订单数据并生成周报发送到企业微信”。看它能否被编排器路由到 Worker A再通过 A2A 把格式化任务转给 Worker B并且全程日志里能追踪到同一个 requestId。下面是一个简化的编排配置示例agent-cluster: orchestrator: port: 8080 task-timeout: 120s workers: - name:>