资讯动态

DeepAgents+MCP+A2A+Skills:多智能体集群架构设计与实操

发布时间:2026/10/3 4:34:03 来源:尧图企业网站定制
1. 从单体到集群为什么需要超级多智能体架构过去一年我一直在折腾各种 Agent 项目从最简单的单轮对话机器人到带工具调用的 ReAct 循环再到多角色协作的流水线。说实话单智能体在真实业务里很快就撞到天花板了——一个 Agent 既要理解意图、又要规划步骤、还要调用工具、最后还得校验结果上下文越堆越长稍微复杂点的任务就开始胡言乱语。这不是模型能力不够而是架构本身撑不住。DeepAgents MCP A2A Skills这套组合本质上是在回答一个问题当单个 Agent 扛不住复杂任务时怎么把能力拆开、让多个专职 Agent 协同工作同时还能让它们互相通信、动态扩展。这不是简单的多开几个 Agent而是一套完整的编排体系。先把这个标题里的四个关键词拆清楚不然后面全是空中楼阁DeepAgents深度智能体指的是具备多步推理、任务分解、自我反思能力的 Agent 主体。它和普通 Agent 的区别在于它不是一问一答而是能拿着一个目标自己拆解成子任务、自己决定下一步做什么。MCPModel Context Protocol模型上下文协议。你可以把它理解成 Agent 和外部工具/数据源之间的USB 接口标准。以前每个工具都要写一套适配代码现在只要工具实现了 MCP Server任何支持 MCP 的 Agent 都能直接接上。A2AAgent to Agent智能体之间的通信协议。MCP 解决的是 Agent 怎么用工具A2A 解决的是 Agent 怎么找别的 Agent 帮忙。一个是纵向调用一个是横向协作。Skills技能。这是 Agent 的能力封装单元一个 Skill 就是一段可复用的、带明确输入输出的能力模块比如查数据库生成图表调用某个 API。这四个东西凑在一起形成的是一个可编排、可互通、可扩展的 Agent 集群。可编排是说任务流程能灵活组合可互通是说 Agent 之间、Agent 和工具之间能顺畅对话可扩展是说加一个新能力不用动老代码。这套架构适合谁如果你正在做以下任何一件事那这篇内容对你有直接价值手上有多个 Agent 但不知道怎么让它们协作工具越接越多、适配代码越写越乱业务方要求加个新功能但你不想重构整个系统或者你单纯想搞清楚 MCP 和 A2A 到底该怎么落地而不是停留在概念层面。我下面会按整体设计思路 → 核心组件拆解 → 实操搭建 → 踩坑排查这个顺序讲每一部分都尽量给到能直接抄的配置和代码。有些细节是我自己踩过坑之后总结的文档里不会写但对实际跑通很关键。2. 整体架构设计与选型逻辑2.1 四层架构的分工与边界我最终落地的架构分成四层从上到下依次是编排层、Agent 层、协议层、能力层。这个分层不是拍脑袋定的而是根据变更频率来切的——越往上业务逻辑变得越快越往下基础设施越稳定。编排层负责接收用户目标、拆解任务、调度 Agent、汇总结果。这一层我用的是 DeepAgents 的思路核心是一个 Planner 加一个 Executor。Planner 负责把大目标拆成有依赖关系的子任务 DAGExecutor 负责按拓扑顺序触发对应的 Agent。Agent 层是各个专职智能体每个 Agent 只干一件事。比如有个数据查询 Agent专门负责把自然语言转成 SQL 并执行有个分析 Agent专门做统计和归因有个报告 Agent专门生成结构化输出。每个 Agent 内部可以有自己的推理循环但对外只暴露标准接口。协议层是 MCP 和 A2A 的落地位置。MCP 负责 Agent 到工具的调用A2A 负责 Agent 到 Agent 的调用。这一层的关键是协议统一——不管底层工具是 Python 脚本、HTTP 服务还是数据库对上层 Agent 来说都是同一个调用方式。能力层就是具体的 Skills 和工具实现。每个 Skill 是一个独立可测试的单元有自己的输入 schema、输出 schema 和错误定义。为什么这么分因为实际项目里最痛的就是改一处崩一片。分层之后加一个新 Skill 只需要在能力层注册编排层和 Agent 层完全不用动换一个 Agent 实现只要符合 A2A 接口其他 Agent 无感知。2.2 为什么选 MCP 而不是自己写工具适配在 MCP 出现之前我给 Agent 接工具的方式是每个工具写一个 wrapper 函数然后在 prompt 里描述这个函数怎么用。工具少的时候还行超过十个就开始乱函数签名不统一、错误处理各写各的、prompt 里的描述和实际实现经常对不上。MCP 的价值在于它把工具描述和工具实现标准化了。一个 MCP Server 启动后会暴露一份工具清单包含每个工具的名称、参数 schema、返回值说明。Agent 端只要连上这个 Server就能自动发现所有可用工具不需要硬编码。我实测下来MCP 带来的最大好处不是省代码而是动态发现。以前加个工具要改 Agent 的 prompt 和代码现在只要 MCP Server 那边注册好Agent 重启就能看到。对于需要频繁加能力的场景这个差别是质变。选 MCP 还有一个隐性理由生态。现在主流的工具和平台都在往 MCP 上靠你写一个 MCP Server理论上能被所有支持 MCP 的客户端复用。自己写的适配层做不到这一点。2.3 A2A 解决的是找谁帮忙的问题MCP 再强它解决的是我有个工具能用解决不了这个任务我不擅长得找个专业的来。这就是 A2A 的定位。A2A 的核心是能力注册与发现。每个 Agent 启动时向注册中心登记自己的能力描述比如我能处理 SQL 查询我能做图像识别。当某个 Agent 遇到自己搞不定的子任务时它向注册中心查询谁能干这个拿到候选 Agent 的地址后直接发起调用。这里有个设计决策我纠结了很久Agent 之间是同步调用还是异步消息最后我选了同步调用为主、异步消息为辅。原因是大部分协作场景需要即时拿到结果才能继续同步调用逻辑简单、调试方便。只有那些耗时长、不需要立即返回的任务才走异步队列。A2A 和 MCP 的关系不是替代而是互补。一个 Agent 既可以通过 MCP 调用工具也可以通过 A2A 调用其他 Agent。从调用方视角看两者都是发一个请求、等一个结果区别在于对端是工具还是智能体。2.4 Skills 的粒度怎么定Skills 的粒度是这套架构里最容易做错的地方。我一开始把 Skill 切得很细一个读文件是一个 Skill解析 JSON是一个 Skill结果 Agent 完成一个任务要调十几个 Skill编排开销比实际工作还大。后来我调整了原则一个 Skill 应该对应一个完整的业务动作而不是一个技术步骤。比如根据用户问题查询订单数据是一个 Skill它内部可能包含读配置、连数据库、执行 SQL、格式化结果这些技术步骤但对 Agent 来说它就是一个原子能力。判断粒度是否合适的标准是如果两个 Skill 总是被连续调用那它们大概率应该合并如果一个 Skill 内部有分支逻辑需要 Agent 来决定走哪条路那它可能应该拆开。3. 核心组件深度拆解与实操要点3.1 DeepAgents 的任务分解机制DeepAgents 的核心是任务分解。我用的方案是LLM 规划 结构化输出 依赖校验三步走。第一步Planner 拿到用户目标后生成一个任务列表。这里的关键是强制结构化输出不能让模型自由发挥。我用的是 JSON schema 约束每个任务必须包含task_id、description、agent_type、depends_on、expected_output这几个字段。{ goal: 分析上季度销售下滑原因并生成报告, tasks: [ { task_id: t1, description: 查询上季度各区域销售数据, agent_type: data_query, depends_on: [], expected_output: 结构化销售数据表 }, { task_id: t2, description: 对比上上季度数据计算变化率, agent_type: analysis, depends_on: [t1], expected_output: 变化率分析结果 }, { task_id: t3, description: 生成归因分析报告, agent_type: report, depends_on: [t2], expected_output: Markdown 格式报告 } ] }第二步拿到任务列表后做依赖校验。这一步很多人会跳过但它是防止死循环的关键。校验内容包括有没有循环依赖、有没有引用了不存在的 task_id、有没有孤立任务。我写了一个简单的拓扑排序检查发现环就直接打回让 Planner 重新规划。第三步按拓扑顺序执行。执行时每个任务的输出会作为下游任务的输入上下文。这里有个坑上下文不能无限传递否则后面的 Agent 会被前面所有输出淹没。我的做法是每个任务只接收直接依赖的输出加上一个全局的目标摘要。注意Planner 生成的计划不要直接执行一定要过一遍校验。我遇到过模型生成t2 depends_on t3同时t3 depends_on t2的情况不校验的话直接死锁。3.2 MCP Server 的搭建与工具注册MCP Server 我用 Python 实现核心是三个部分工具定义、请求路由、错误处理。工具定义用装饰器方式每个工具声明名称、描述、参数 schemafrom mcp.server import Server from mcp.types import Tool, TextContent import json app Server(data-tools) app.list_tools() async def list_tools(): return [ Tool( namequery_sales, description根据时间范围和区域查询销售数据, inputSchema{ type: object, properties: { start_date: {type: string, description: 开始日期 YYYY-MM-DD}, end_date: {type: string, description: 结束日期 YYYY-MM-DD}, region: {type: string, description: 区域代码可选} }, required: [start_date, end_date] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): if name query_sales: result await do_query(arguments) return [TextContent(typetext, textjson.dumps(result, ensure_asciiFalse))] raise ValueError(f未知工具: {name})这里有几个实操要点。第一inputSchema 一定要写全包括 required 字段和每个字段的类型、描述。Agent 端是靠这个 schema 来决定怎么填参数的schema 写得含糊Agent 就会瞎填。第二返回值统一用 JSON 字符串不要返回裸文本否则 Agent 解析起来很痛苦。第三错误要抛异常而不是返回错误字符串让协议层去处理这样 Agent 能区分调用失败和调用成功但结果为空。工具注册完之后Agent 端连接的方式是配置一个 MCP Server 地址。我用的配置格式大概是这样{ mcpServers: { data-tools: { command: python, args: [-m, servers.data_tools], env: {DB_URL: postgresql://...} } } }提示MCP Server 的环境变量不要硬编码在代码里通过配置注入。我吃过亏把数据库密码写死在代码里换环境的时候漏改了一处排查了半天。3.3 A2A 通信协议的设计细节A2A 我实现的是一个轻量级的 HTTP JSON 协议没有用太重的方案。核心接口就三个注册、发现、调用。注册接口Agent 启动时 POST 自己的能力描述到注册中心。{ agent_id: analysis-agent-01, endpoint: http://localhost:8001/a2a, capabilities: [ { name: statistical_analysis, description: 对结构化数据做统计分析和归因, input_schema: {type: object, properties: {data: {type: array}}}, output_schema: {type: object, properties: {result: {type: object}}} } ], health_check: http://localhost:8001/health }发现接口Agent 需要某个能力时GET 注册中心查询。GET /discover?capabilitystatistical_analysis返回所有匹配的 Agent 列表调用方自己选一个我目前用的是轮询加健康检查过滤。调用接口直接 POST 到目标 Agent 的 endpoint。{ from_agent: orchestrator, task: statistical_analysis, payload: {data: [...]}, trace_id: abc-123 }这里trace_id很重要用于全链路追踪。多 Agent 协作时出问题没有 trace_id 根本没法定位是哪个环节挂了。A2A 设计里我踩过最大的坑是超时处理。Agent 调用 Agent如果被调方卡住调用方会一直等。我的解决方案是每个 A2A 调用都带超时参数超时后调用方要么重试、要么降级、要么报错绝不无限等待。默认超时我设的是 30 秒长任务走异步。3.4 Skills 的封装规范Skills 我统一用类的方式封装每个 Skill 继承一个基类实现execute方法。基类负责参数校验、日志、异常包装这些横切逻辑。from abc import ABC, abstractmethod from pydantic import BaseModel class SkillInput(BaseModel): pass class SkillOutput(BaseModel): pass class BaseSkill(ABC): name: str description: str input_model: type[SkillInput] output_model: type[SkillOutput] abstractmethod async def execute(self, params: SkillInput) - SkillOutput: pass async def run(self, raw_params: dict) - dict: params self.input_model(**raw_params) result await self.execute(params) return result.model_dump()用 Pydantic 做输入输出模型的好处是自动校验和文档生成。Agent 端拿到的 schema 直接从模型生成不会出现文档和实现不一致的情况。Skill 的注册我用的是一个装饰器加注册表SKILL_REGISTRY {} def register_skill(skill_cls): SKILL_REGISTRY[skill_cls.name] skill_cls() return skill_cls register_skill class QuerySalesSkill(BaseSkill): name query_sales description 查询销售数据 input_model QuerySalesInput output_model QuerySalesOutput async def execute(self, params): # 实际逻辑 ...这样加一个新 Skill 只要写一个类加一个装饰器其他什么都不用改。MCP Server 启动时遍历注册表把所有 Skill 暴露成 MCP 工具。4. 完整实操从零搭一个可跑的多智能体集群4.1 环境准备与依赖安装我用的技术栈是 Python 3.11 FastAPI Pydantic MCP SDK。Python 版本建议 3.10 以上因为用到了不少新语法特性。python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install fastapi uvicorn pydantic mcp httpx目录结构我建议这样组织清晰且好扩展agent-cluster/ ├── core/ │ ├── orchestrator.py # 编排层 │ ├── planner.py # 任务规划 │ └── registry.py # A2A 注册中心 ├── agents/ │ ├── base.py # Agent 基类 │ ├── data_query.py │ ├── analysis.py │ └── report.py ├── skills/ │ ├── base.py │ ├── query_sales.py │ └── analyze_trend.py ├── mcp_servers/ │ └── data_tools.py └── config/ └── mcp_config.json这个结构的关键是core、agents、skills 三层分离。core 是框架代码agents 是业务 Agentskills 是能力实现。改业务只动 agents 和 skills框架稳定不动。4.2 编排层的实现编排层是整个集群的大脑我实现的核心逻辑是规划 → 校验 → 调度 → 汇总。class Orchestrator: def __init__(self, planner, registry): self.planner planner self.registry registry async def run(self, goal: str) - dict: # 1. 规划 plan await self.planner.plan(goal) # 2. 校验 self._validate_plan(plan) # 3. 按拓扑序执行 results {} for task in self._topological_sort(plan[tasks]): agent await self.registry.find_agent(task[agent_type]) context self._build_context(task, results, goal) result await agent.invoke(task[description], context) results[task[task_id]] result # 4. 汇总 return self._aggregate(results, goal) def _validate_plan(self, plan): task_ids {t[task_id] for t in plan[tasks]} for t in plan[tasks]: for dep in t[depends_on]: if dep not in task_ids: raise ValueError(f任务 {t[task_id]} 依赖不存在的 {dep}) # 环检测 self._topological_sort(plan[tasks])_build_context这个方法值得单独说。它决定了每个 Agent 能看到什么上下文。我的策略是直接依赖的输出 全局目标 当前任务描述。不传无关任务的输出避免上下文污染。def _build_context(self, task, results, goal): context {goal: goal, task: task[description]} for dep in task[depends_on]: context[finput_from_{dep}] results[dep] return context4.3 一个 Agent 的完整实现以数据查询 Agent 为例它的职责是接收自然语言描述转成 SQL通过 MCP 调用工具执行返回结构化结果。class DataQueryAgent: def __init__(self, llm, mcp_client): self.llm llm self.mcp_client mcp_client async def invoke(self, description: str, context: dict) - dict: # 1. 生成查询参数 params await self._generate_params(description, context) # 2. 通过 MCP 调用工具 result await self.mcp_client.call_tool(query_sales, params) # 3. 校验结果 if not result: return {status: empty, data: []} return {status: ok, data: result} async def _generate_params(self, description, context): prompt f 根据以下任务描述生成查询参数 任务{description} 上下文{json.dumps(context, ensure_asciiFalse)} 输出 JSON 格式包含 start_date, end_date, region 字段。 response await self.llm.generate(prompt) return json.loads(response)这里的关键是Agent 不直接碰数据库所有数据访问都通过 MCP 工具。这样做的好处是权限控制、审计、缓存都可以在 MCP 层统一做Agent 只管业务逻辑。4.4 启动与联调所有组件写完后启动顺序是先起 MCP Server再起各个 Agent最后起编排层。# 终端 1MCP Server python -m mcp_servers.data_tools # 终端 2注册中心 uvicorn core.registry:app --port 9000 # 终端 3各个 Agent python -m agents.data_query --port 8001 python -m agents.analysis --port 8002 python -m agents.report --port 8003 # 终端 4编排层 uvicorn core.orchestrator:app --port 8080联调时我建议先用一个简单目标测试比如查询上个月销售数据确认单链路通了再测多 Agent 协作的复杂目标。提示联调阶段把日志级别调到 DEBUG每个 Agent 的输入输出都打出来。多 Agent 系统出问题时光看最终结果根本不知道哪一步错了。5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因排查方向解决方案Agent 调用工具超时MCP Server 未启动或地址错检查 MCP 配置和进程确认 Server 端口和配置一致任务规划出现循环依赖Planner 输出未校验打印 plan 的 depends_on加拓扑排序校验失败重规划Agent 之间调用 404注册中心未注册或已下线查注册中心 Agent 列表加健康检查自动剔除死节点上下文过长导致输出质量下降传递了无关任务输出检查 _build_context只传直接依赖的输出Skill 参数校验失败schema 和实际调用不匹配对比 input_model 和传入参数统一用 Pydantic 模型结果汇总丢失数据任务失败但未中断检查每个 task 的 status失败任务要显式标记并处理5.2 三个我踩过的坑第一个坑MCP 工具描述写得太简略。我一开始给工具写的 description 就一句话查询销售数据结果 Agent 经常传错参数比如把区域代码传成区域名称。后来我把 description 写详细明确说明region 参数需要传区域代码如 NORTH、SOUTH不是区域名称准确率立刻上来了。工具描述是给模型看的不是给人看的要写得像给新人的操作手册。第二个坑A2A 调用没有幂等设计。有一次网络抖动导致调用方重试结果被调方执行了两次数据重复写入。后来我给所有 A2A 调用加了request_id被调方对相同request_id的请求直接返回缓存结果。这个改动不大但避免了生产事故。第三个坑Skills 没有版本管理。我改了一个 Skill 的输出格式结果依赖它的 Agent 全挂了。后来我给每个 Skill 加了版本号Agent 调用时指定版本新版本上线先灰度确认没问题再全量。多 Agent 系统里接口稳定性比单体能跑通重要得多。5.3 性能与并发处理多 Agent 集群的并发问题比单体复杂。我遇到的主要是两类一是多个任务同时调用同一个 Agent 导致排队二是 MCP Server 成为瓶颈。对于第一类我的做法是给每个 Agent 加一个并发上限超过就排队。同时编排层在调度时尽量把无依赖的任务并行执行。比如 t1 和 t2 没有依赖关系就同时触发而不是串行等。对于第二类MCP Server 我做了连接池和结果缓存。相同的查询参数在短时间内重复调用直接返回缓存。这个优化在批量任务场景下效果很明显实测 QPS 提升了三倍多。注意缓存一定要设过期时间而且要有手动失效机制。我吃过缓存脏数据的亏数据更新了但缓存没失效Agent 拿着旧数据做分析结论全错。5.4 安全与权限控制Agent 集群的安全边界比单体重要得多因为一个 Agent 被攻破可能影响整个集群。我做了三层防护。第一层是MCP 工具级权限。每个工具声明自己需要的权限Agent 调用时带上自己的身份MCP Server 校验权限后才执行。比如删除数据这种危险操作只有特定 Agent 能调。第二层是A2A 调用鉴权。Agent 之间的调用要带 token注册中心签发被调方校验。防止未注册的 Agent 混进来。第三层是输入输出过滤。所有进入 Agent 的用户输入和 Agent 生成的输出都过一遍敏感内容检查防止 prompt 注入和数据泄露。这三层不是一次性做完的我是先做了第一层跑通之后逐步加的。建议你也别一上来就搞全套先把主流程跑通安全加固可以迭代。6. 扩展方向与个人实践体会这套架构跑通之后扩展就变得很自然了。加一个新能力写一个 Skill 类注册进去加一个新 Agent实现 A2A 接口注册到中心换一个 LLM改配置就行。我最近在尝试的方向是把 Skills 做成可热插拔的Agent 运行时动态加载新 Skill不用重启。还有一个我觉得很有价值的方向是Agent 的自我优化。让编排层记录每次任务执行的成功率、耗时、失败原因定期分析哪些任务经常失败、哪些 Agent 表现不好然后自动调整调度策略。这个还在实验阶段但初步效果不错。我个人在实际操作中的体会是多智能体系统最难的不是技术实现而是边界划分。哪个能力该做成 Skill哪个该做成 Agent哪个该放在编排层这个决策做错了后面怎么优化都别扭。我的经验是如果一个能力需要理解和决策做成 Agent如果只是执行做成 Skill如果涉及多个 Agent 的协调放在编排层。按这个原则切基本不会错。最后分享一个小技巧调试多 Agent 系统时我会给每个 Agent 加一个单步模式可以手动触发某个 Agent 并查看它的完整输入输出。这个功能在排查复杂问题时特别有用比看日志高效得多。实现起来也简单就是加一个调试接口绕过编排层直接调用指定 Agent。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑