
这几年我搭过不少 Agent 应用一开始大家觉得“接个大模型就完事了”做到后面才发现真正麻烦的从来不是模型而是系统问题。最典型的状态就是工具散落、调用混乱、接口各自为政——每个 Agent 都在用自己的方式连工具换一个 Agent 就得重写一遍Agent 之间不能互通想做个协同任务只能写一堆临时脚本。我参与的这个 DeepAgents 多智能体项目核心思路是把 MCP、A2A、Skills 三个东西组合成一套标准化的 Agent 集群底座目标是让每个 Agent 都能编排、能互通、能扩展。这篇文章适合正在用 LLM 搭建实际应用、或者在做多 Agent 框架选型的工程师我会把协议分层、框架演进、落地细节和踩坑记录都摊开讲。1. 先拆清楚四个关键词DeepAgents、MCP、A2A、Skills1.1 DeepAgents 不是再造一个框架而是一种组织方式先说 DeepAgents。它不是某个必须安装的运行时而是一种集群组织思路。“Deep”强调的不是单个 Agent 的深度而是任务分解的深度和系统层级的深度。传统单体 Agent 的问题很典型一个 Agent 里塞了上下文管理、工具调用、任务规划、结果生成上下文窗口是有限的工具一多就开始互相干扰一次任务跑挂了整个链路都断掉。把任务拆给多个角色、多个流程去处理就像公司里不会让一个员工包揽研发、测试、运维一样分工能提高单位效率也能让每个角色保持独立的上下文状态。多 Agent 的常见形态有三种编排式、协作式、混合式。编排式由一个主控 Agent 负责任务分发子 Agent 干完活把结果交回协作式没有中心节点Agent 之间相互确认和传递混合式则结合两者适合既有固定流程、又有自由协同的业务。项目取名 DeepAgents还有另一层意图——把编排逻辑做成深度分层的状态机而不是扁平的一轮轮 prompt 互叫。每个 Agent 在链路中有明确的角色和状态主控可以随时查询进度、中断重跑比较接近真实工程里的流程编排系统。这样的设计可以在后续接监控、审计、权限控制时不至于推倒重来。1.2 MCP把工具接入变成“插 USB”MCP 全称是 Model Context Protocol它解决的问题其实非常具体Agent 该怎么标准化地使用外部工具。以前一个模型要调一个工具往往靠 function calling也就是在 prompt 里塞一段函数定义然后让模型生成一个匹配的 JSON 参数。这个方案单看没问题但每个工具都要针对当前模型调一次换一个模型、换一个 Agent 就要重新对齐一次。MCP 的思路是把这个过程抽成 Client-Server 结构。模型运行在 Host 里Host 通过 MCP Client 连接各个 MCP Server每个 Server 暴露 Tool、Resource、Prompt 三类能力。模型不需要知道工具背后的实现细节它只知道“这里有这么个工具、参数是什么、返回值是什么”调用请求统一走 MCP 协议。这个体验和 USB 差不多——设备不需要关心驱动细节插上就能被识别协议对接口做了统一。在整个 DeepAgents 集群里MCP Server 是工具接入层的最小单元。我们团队的成员做过不少定制化 Server有的连数据库、有的封装内部 API、有的读文件系统、有的操作 Redis 缓存。统一用 MCP 之后任何 Agent 只要拿到同一个 Server 地址就能复用里面全部工具不用每个 Agent 单独写适配代码。这也是整个集群能“可扩展”的物理基础。1.3 A2AAgent 之间的普通话如果说 MCP 解决的是 Agent 调工具的问题那 A2A 解决的就是 Agent 调 Agent 的问题。A2A 全称是 Agent-to-Agent它定义了一套对话协议让不同团队、不同技术栈实现的 Agent 能统一注册、发现、通信。A2A 有几个基础概念要理解清楚。AgentCard 是每个 Agent 的“身份证”里边包含名称、能力描述、端点地址、认证方式等。外部 Agent 通过读 AgentCard 就知道这个 Agent 能干什么、怎么调用。Task 是核心任务单元一次请求就是一个 Task状态可以是 submitted、working、completed、failed支持长任务轮询也支持回调。Message 和 Part 则是内容的载体Part 可以是文本、文件引用、结构化数据跑一次任务会产出一个或多个 Artifact。传输层 A2A 用的是 JSON-RPC 2.0这意味着它是语言中立的。我们团队后端是 Python也有同事用 Node 起了服务两边通过 AgentCard 注册完互相调用完全不用关心对方是哪个框架。社区里那些把 A2A 移植到 Spring 或者 C 的项目本质也是在做同一个协议的适配器。你在实现时只需要保证消息是标准 JSON-RPC 格式就能和任何 A2A 兼容的 Agent 对上话。1.4 Skills能力包不是一段 promptSkills 这个概念最近讨论得很多但不少人把它理解成“一段写得更长的 prompt”这就太小看它了。Skills 是一套完整的可复用能力包它同时包含能力描述、参数定义、执行指令、示例数据以及可能引用的脚本和资源文件。按需加载是它最大的好处——没有谁会让 Agent 启动时把所有能力全部灌进上下文那样上下文很快就爆了Skills 让系统在特定任务到来时才把相关指令加载进去。举个例子一个文本摘要 Skill它不只是写“请帮我总结一下这段文字”而是会告诉模型输入字段有哪些输出格式要包含摘要和关键点输出长度上限是多少遇到多语言文本怎么处理以及给一个 few-shot 示例。这套东西内部其实是 Skill 描述和模型指令的组合。那 Skills 和 MCP 有重叠吗重叠很小。可以把 MCP 看成传输管道把 Skills 看成管道里运的可执行“知识包”。MCP Server 提供的是工具Skills 提供的是执行规范。两者经常配合出现一个 Skill 在运行过程中会调用 MCP Tool 去拿数据再按照 Skill 定义的规则加工最后按约定格式抛出结果。市面上越来越多的 Skills 市场、仓库出现的也顺理成章——能力包本身就是可以跨场景复用的。1.5 四个概念的关系模块职责生活化类比典型场景DeepAgents集群组织与编排设计公司的项目管理部任务拆解、状态流转、故障重试MCP统一工具接入协议万能插线板Agent 调用数据库、文件、APIA2AAgent 间互操作协议公司间的合同范本多 Agent 协作、跨团队服务调用Skills可复用能力包员工岗位手册文本摘要、代码检查、数据分析要注意的是这四个词并不属于同一个层级。MCP 和 A2A 是通信协议Skills 是能力载体DeepAgents 则更像这些能力之上的设计范式。做集群的时候不要把它们看成并列选项而是要理解它们如何上下配合再动工。2. 架构设计可编排、可互通、可扩展的落地思路2.1 四层架构我习惯把整个集群拆成四层编排层、能力层、通信层、工具层。编排层是大脑负责任务拆解、状态管理和结果聚合能力层是各种 Skills 的集合决定 Agent 会做什么通信层跑 MCP 和 A2A 两类协议分别是“Agent 到工具”和“Agent 到 Agent”的桥梁工具层是底层依赖数据库、文件系统、外部 API 都在这一层。分层带来的收益是“各管各的”。工具层新增一个内部 API只需要包装成 MCP Server上层完全不用改能力层假如要增强某个 Agent 的本领只需要替换对应的 Skill 包编排层调整流程时也不影响底层已经在跑的服务。这样每一层都可以独立演进。这里的层级关系还有一层深意把 SDK、协议、业务逻辑分开。比如同一个 MCP Server 可以在 A2A 场景里被多个 Agent 共享同一套 Skills 也可以在单 Agent 系统和多 Agent 系统中通用。不是“因为用了多 Agent所以要重新发明一遍工具调用”而是“工具和能力本身就该标准化多 Agent 只是多了编排和通信两个维度”。2.2 可编排任务怎么拆、怎么调度可编排的关键点是任务拆解和状态管理。我们在项目里给每个任务定义了一个最小状态机pending → running → succeeded / failed / retrying。主控 Agent 收到一个复杂任务后先根据 Skills 的注册信息判断哪个环节谁能做然后并行或串行地发请求最后把每个环节的输出按规则聚合。状态管理要落地不能只靠内存变量。我们项目早期就吃过亏内存里存任务状态主控一重启所有任务全丢失。后来统一引入了队列和持久化存储每个任务的状态推进都记录到数据库。复杂任务还支持事件总线订阅Agent 完成一个子任务就发一个事件编排层收到事件后决定下一步。刚开始做编排别一上来就上复杂规则先把简单的“拆解-分发-聚合”跑通。聚合这一步通常是隐形难点因为不同子任务的输出格式未必一致。一个治标又治本的做法是在子任务下发的 prompt 里就要求输出带 schema 的 JSON 字段这样聚合层只用做字段映射不用做自然语言理解。很多所谓编排不稳定的问题根因不是模型太笨而是你从来没对子任务的输出约定过格式。2.3 可互通异构 Agent 怎么才能对上话团队项目里经常出现这个问题A 团队用 LangChain 做了个 AgentB 团队自己用纯 Python 写了个服务还带异步任务两边各聊各的。没有统一协议时只能靠 HTTP 接口临时刻接口签名、数据结构、错误语义全都依赖口头对齐需求一换就崩。A2A 为这个场景提供了标准答案。每个 Agent 只需暴露端点并提供 AgentCard其他 Agent 就能实时了解它的能力。而且 A2A 的 Task 语义天然支持长任务对于需要耗时几十分钟的异步工作不必让调用方僵在同步等待里。互通还需要考虑另一个问题消息路由。我们项目在通信层前面加了一个轻量级路由服务它维护一份 AgentCard 索引请求进来时根据能力和负载情况路由到合适的目标 Agent。说白了 A2A 只解决“按照协议对话”在一个大集群里还需要“按照规则找到谁该执行”可以帮助你做灰度、做负载均衡。2.4 可扩展加能力不加成本扩展性可以从两个维度看加节点和加能力。加节点是横向扩展同一个 Agent 可以水平多实例部署前面用负载均衡分发请求这是常规思路不需要多讲真正有意思的是加能力。有了 MCP往集群里加一种新工具只需要部署一个新 Server 并注册已有的 Agent 就能立刻使用它。有了 Skills给某个 Agent 增加专业知识只需要上传新的技能包并按需触发加载。扩展成本从原来的“改代码、改提示词、改调用逻辑”降到了“注册和部署”。对我们这种长期迭代的项目来说这个信息比任何框架选型都值钱。可扩展性还体现在标准协议的演进空间上。项目后期我们想让外部系统接入集群比如把逆向调试工具 IDA 用 MCP 包一层暴露出来或者让游戏引擎 Unreal 里跑一个 MCP Server这些都没改集群主干只是新增一个 Server。集群主干早已标准化新增能力就变成了纯粹的插件工作。3. 环境准备与选型3.1 运行时选型Python 优先Rust 可以补充整个集群我们大部分代码用的是 Python。原因是这个生态里对 MCP、A2A 的支持最成熟官方 SDK 和社区示例都齐全团队成员可以更快把精力放在业务上。Rust 并不是不能用它在性能、内存占用上有优势适合做网关或高并发节点我们只在少数关键链路上用 Rust 写过组件但日常 Agent 开发我还是推荐 Python。另一个需要考虑的是模型入口。项目没有绑定某个特定模型而是统一走 OpenAI 兼容的接口这样方便切换不同部署方式也方便做多模型路由。当然如果你们的推理服务支持 Anthropic 的 API需要单独适配模型接入层但协议层不受影响。3.2 安装核心依赖基础依赖建议准备这些pip install mcp httpx fastapi uvicornmcp提供 MCP Server 开发所需的 SDKhttpx用来发 A2A 请求FastAPI和uvicorn用来起 Agent 的 HTTP 服务。如果还想在浏览器里做可视化调试可以再安装 MCP Inspector 相关的调试模块不过那不是必须品。如果你要对接一些现成的 MCP Server 仓库则需要先看清楚它们各自的依赖环境。社区里有不少针对设计工具、EDA、逆向分析等领域的 MCP Server它们很多都能独立跑成一个进程接入集群时只需要把地址填进 Agent 的 MCP Client 配置里即可不一定要和主服务同进程部署。3.3 项目目录结构一个合理目录可以让多团队协作轻松很多。我们项目的主干结构参考如下agent-cluster/ orchestrator/ # 编排层主服务 agents/ # 各业务 Agent team-a/ # 每个团队独立目录 team-b/ mcp-servers/ # 各类 MCP Server file-server/ db-server/ skills/ # Skills 包目录 summary/ code-review/ shared/ # 公共路由、鉴权、工具 deployment/ # 部署脚本与配置这个结构最重要的是“技能包目录”和“MCP Server 目录”分开。很多人喜欢把 Skills 塞进 Agent 代码里结果每个 Agent 都膨胀升级困难。独立目录之后Skills 可以被打包、版本化管理发布时通过注册中心同步整个集群就像一个组件仓库。4. 实操从零搭一套最小可运行的 Agent 集群4.1 先写一个 MCP Server文件读取工具大家最容易理解 MCP 的方式就是直接写一个 Server。我用官方的 FastMCP 做示例实现一个文件读取工具from mcp.server.fastmcp import FastMCP mcp FastMCP(file-tools) mcp.tool() def read_text_file(path: str) - str: 读取文本文件内容 with open(path, r, encodingutf-8) as f: return f.read() if __name__ __main__: mcp.run()这个 Server 跑起来后任何配置了该 Server 地址的 Agent 都能调用read_text_file。注意我在函数里写了 docstring这非常重要——在 MCP 协议里docstring 会被当作工具描述注入给模型模型靠它决定“在什么场景用什么工具”。很多人只写函数名不写描述导致模型在复杂的任务里压根不知道什么时候该调用这个工具。接下来你可以用 MCP Inspector 类的工具进行测试传一个文件路径看返回内容是否正常。测通之后再跑下一步。4.2 定义一套本地 Skills结构化摘要Skills 的定义方式是理解它的核心。以摘要 Skill 为例我会这样组织一个目录skills/summary/ SKILL.mdSKILL.md内容大致长这样--- name: structured_summary description: 对长文本做结构化摘要支持中英文混合输入 version: 1.0.0 inputs: - name: text type: string required: true - name: max_length type: integer required: false default: 300 outputs: - name: summary type: string - name: key_points type: array --- 你是文本摘要助手。收到 text 后按以下规则处理 1. 先快速识别文本类型是新闻、论文、聊天记录还是代码文档。 2. 摘要输出必须包含一段 summary字数控制在 max_length 以内。 3. key_points 需列出 3 至 5 条核心结论每条不超过 20 字。 4. 原文若出现技术术语保留英文原名不要强行翻译。 5. 保持客观中立不得添加原文没有的观点。为什么要写成这样而不是直接写“帮我总结”因为有了明确的输入输出 schemaAgent 才知道调用这个 Skill 时要传什么参数、结果怎么接回来有了规则约束输出才稳定。我们当时接了一个多语言会议纪要的项目所有 Agent 都用同一个摘要 Skill但各自的处理流程不一样这套定义方式让结果质量非常可控。4.3 快速实现一个带 A2A 接口的 Agent我习惯用 FastAPI 暴露 Agent 的 HTTP 服务同时实现 A2A 的 AgentCard 和消息处理端点。以下是核心代码骨架from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI(titlesummary-agent) agent_card { name: summary-agent, description: 提供结构化摘要能力的 Agent可用于会议纪要、文档压缩等场景, url: http://127.0.0.1:8100, capabilities: { skills: [structured_summary] } } app.get(/.well-known/agent.json) def get_agent_card(): return agent_card class Message(BaseModel): role: str parts: list class A2ARequest(BaseModel): jsonrpc: str id: str method: str params: dict app.post(/a2a) async def a2a_endpoint(req: A2ARequest): if req.method message/send: message req.params.get(message, {}) text_parts [p[text] for p in message.get(parts, []) if text in p] text \n.join(text_parts) # 这里调用你定义的 Skills 处理逻辑 result run_structured_summary(text) return { jsonrpc: 2.0, id: req.id, result: { taskId: task-001, status: completed, artifacts: [ {parts: [{text: result}]} ] } } raise HTTPException(status_code400, detailunsupported method)这段代码里AgentCard 声明了自身能力和端点。编排层只要发现这个 Agent 的地址就能判断“摘要任务可以交给这个服务”。注意/.well-known/agent.json这个路径是 A2A 协议约定的标准注册点外部 Agent 也会优先从这个路径读取信息。这种设计让 Agent 可以完全独立部署独立扩缩容。团队里有人用纯 Rust 实现了另一个 Agent只要它遵守同样的 JSON-RPC 语义主集群里的 Python Agent 调用它毫无障碍。这也是我坚持把所有 Agent 都包一层 A2A 接口的原因——语言隔离被协议消解掉了。4.4 编排器的实现思路编排层不需要很复杂但一定要有状态意识。我的最小实现可以写成from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass, field dataclass class Task: task_id: str status: str pending result: str def dispatch_agent(step: str): # 通过 A2A 协议向某个 Agent 发请求 return call_a2a_agent(step) def orchestrate(root_task: str): steps decompose_with_skills(root_task) task_records [] with ThreadPoolExecutor(max_workers4) as pool: futures {pool.submit(dispatch_agent, s): s for s in steps} for fut in as_completed(futures): step futures[fut] result fut.result() task_records.append({step: step, result: result}) return aggregate(task_records)这个实现已经能跑通基本编排但为了生产可用我建议至少补三块失败重试、限流和超时控制。A2A 调用和本地函数调用不一样它跨进程甚至跨机器网络抖动、目标服务过载都可能发生。没有超时控制上游任务会一直挂死没有重试策略一次偶发错误就中断整体流程没有限流下游 Agent 容易被突发流量打垮。编排器里还必须记录每个 Agent 调用的入参和出参。这个数据对排查“为什么这次 Agent 输出的结果是错的”特别关键。如果以后要做回放测试这些日志就是最宝贵的素材。5. 联调、监控与安全5.1 联调检查点从单点测试到全链路多 Agent 集群联调时大多数问题都出在“每个服务单独测都是好的一连起来就崩”。我整理了一个逐项排查顺序检查项操作预期结果单个 MCP Server用调试工具直接调用返回正常数据Skills 加载在单 Agent 环境试跑一次输出符合 schemaAgent A2A 接口用 curl 发一个 JSON-RPC 请求任务进入对应状态AgentCard 发现从另一节点请求 agent.json返回完整卡片编排层分发给编排器发一个复合任务子任务能被分发到正确 Agent结果聚合查看最终输出与各子任务日志聚合结果字段对齐异常链路强制某个子节点失败任务进入重试或失败状态每一层都验证通过后再跑端到端测试。很多团队忽略 AgentCard 发现环节结果项目上线后新增 Agent 一直不能被调用最后发现是注册中心没同步低级错误但排查成本极高。5.2 监控与链路追踪多智能体集群比单体系统更需要监控原因很简单一次业务请求背后可能有多个 Agent 参与每个 Agent 又有内部工具调用任何一个环节慢都会拖垮整体。我们项目在重要请求里给每个 Task 绑定全局 traceId调用链路上所有日志都带上这个 ID排查问题时直接按 ID 聚合查阅。指标方面重点看三个任务成功率、平均完成耗时、Active 状态任务数。这三个指标能快速反映集群整体健康度。另外建议给每个 Agent 单独设置指标识别“哪个 Agent 成了瓶颈”。我们在实际运行中发现过某个 Agent 因为底层 API 限流导致任务成功率骤降靠单 Agent 指标一眼定位。还有一个小技巧在编排层把“已分发但长时间没完成”的任务主动给上游发一次心跳超时就中断重试。很多任务失败不是异常而是“卡住”卡住比异常更坑因为你不知道什么时候该等、什么时候该放弃。心跳机制能把这部分等待变成确定性逻辑。5.3 Agent 安全有哪些容易忽略的坑Agent 集群的安全和传统 Web 服务不同它更多了一层“无意的误操作”风险。比如一个 Agent 拥有文件删除工具理论上模型只要在 prompt 里被诱导就可能发起破坏。我们采取的底线措施是所有危险工具在 MCP Server 层都加了 ACL只有指定角色的 Agent 才能调用同时在编排层做二次审批涉及高危行为的任务必须显式确认。敏感信息管理同样是重头戏。密钥、口令不能出现在 Skill 的固定提示词里也不能随 A2A 消息明文传输。我们要求所有密钥统一存放到独立配置中心运行时通过环境变量注入到各服务日志里强制脱敏。代码里禁止出现“跨多个 Agent 共享密钥”的做法权限的边界必须和 Agent 的业务边界保持一致。最后是 AgentCard 的暴露范围。A2A 协议让服务容易被发现但反向也意味着“该被发现的暴露了不该被发现的也危险”。我们内部会把 AgentCard 放在内网网关后公网只开放必要的入口。多智能体集群的安全不是单一环节加固而是每一层都要默认收紧。6. 常见问题与排查实录6.1 问题速查表现象可能原因排查思路MCP 工具调用返回空Server 没用 docstring 描述工具检查函数描述重新加载 Server工具调用参数一直报错参数名与 schema 不对齐查看 MCP Server 的调用日志A2A 请求超时目标 Agent 同步处理长任务改用长任务轮询或回调AgentCard 无法读取路径里没开/.well-known/agent.json检查路由注册子任务结果格式混乱没有对子任务输出约定 schema统一 prompt 返回 JSON 字段整体任务失败但无异常某个子节点卡死加心跳、超时、中断重试Skill 效果不稳定描述和指令不明确增加示例和边界条件说明编排任务恢复丢失状态存在内存里任务状态持久化到数据库密钥硬编码在 Skills 里权限没有抽离迁移到配置中心并脱敏6.2 避坑清单先让一个 MCP Server 跑通再接多个。一次接太多工具模型会选错工具排查还特别费劲。Skills 的示例少给不如多给尤其是边界情况。没有示例模型就只能猜。A2A 和内部 HTTP 接口不要混在一起。内部接口改动频率高一旦暴露成 A2A 能力就要按协议兼容性管理。不要迷信“一个 Agent 全知全能”它解决不了 Agent 集群的可用性问题反而容易把上下文塞爆。每次改 Agent 的 AgentCard 后都要重新验证发现链路很多“新增 Agent 找不到”的问题都出在这里。对子任务输出加 schema 校验不要在聚合阶段做自然语言解析。编排器不要写过长的等待逻辑能用轮询就用轮询轮询间隔控制在可接受的延迟范围内。日志是所有排查的基础尤其要记录“谁调的谁、什么时候调的、返回了什么”。对高风险工具做危险操作前再加一层审批不要认为模型不会犯错。6.3 我经历过的两个真实故障第一个故障是集群上线初期一个摘要 Agent 经常“答非所问”。排查后发现它加载的摘要 Skill 里没有说明输入文本语言导致中英混排的文档被模型随意翻译。这个案例给了我一个教训Skills 的约束条件必须写清楚边界不要指望模型“能猜对”。第二个故障更有意思某个 MCP Server 正常调用了一段时间后开始超时。我们排查到原因不是工具本身慢而是它的客户端连接池配置不够高并发时连接被占满。后来给该 Server 接上了连接池复用和超时回收问题立刻消失。MCP 是跨进程通信客户端自身的资源管理比 Server 内部逻辑更容易成为瓶颈。最后这套组合彻底改变了我做 Agent 项目的习惯。现在每次开工第一件事是搞清楚底层的工具应该用什么 MCP Server 暴露业务能力包用哪个 Skills 封装Agent 之间通过 A2A 协议怎么互通最后再用编排器把流程串起来。看似多了点前期设计工作但后续每加一个新 Agent、新工具都不需要推翻重来。多智能体集群真正的价值不在于“又一个高端概念”而在于它把本来混乱的系统塞进了清晰的协议与层级里让团队可以像搭积木一样持续迭代。