1. 从单兵作战到团队协作多智能体系统到底在解决什么问题如果你已经跟着这个系列一路看下来应该对 MCP 和 A2A 这两个概念有了基本的认识。但说实话前几篇更多是在拆解单个协议本身——MCP 怎么让模型调用外部工具A2A 怎么让智能体之间互相通信。到了这一篇我想把视角拉高一层当你手里同时握着 MCP 和 A2A 这两把工具怎么真正搭出一个能跑、能扩展、能维护的多智能体 AI 系统这个问题听起来很宏大但落到实操层面其实非常具体。我见过太多团队在搭多智能体系统时第一步就走偏了——要么把所有逻辑塞进一个超级 Agent 里用一堆 if-else 硬编码路由要么一上来就搞十几个 Agent 互相调用结果调试的时候连日志都理不清。这两种极端我都踩过所以这篇想聊的是中间那条路用 MCP 解决单个 Agent 怎么和外部世界打交道用 A2A 解决多个 Agent 之间怎么协作两者各司其职边界清晰。先说说为什么需要多智能体而不是一个更强的单体 Agent。核心原因有三个。第一是上下文窗口的物理限制——一个 Agent 要同时处理代码审查、数据库查询、文档检索、邮件发送它的系统提示词会膨胀到难以维护而且不同任务之间会互相干扰。第二是工具权限的隔离需求——财务 Agent 不应该有删除生产数据库的权限客服 Agent 不应该能访问内部 HR 系统这种隔离在单体架构里很难做干净。第三是独立演进和复用——搜索 Agent 做好了可以同时被客服流程和研发流程复用不需要复制粘贴代码。而 MCP 和 A2A 的分工用一句话概括就是MCP 是 Agent 的手A2A 是 Agent 的嘴和耳朵。MCP 让 Agent 能够操作外部工具和数据源A2A 让 Agent 能够和其他 Agent 对话、委派任务、汇总结果。这个类比我在团队内部讲了很多次新人一听就懂。提示不要试图用 A2A 去替代 MCP 的工具调用能力也不要用 MCP 去硬凑 Agent 之间的通信。两者的抽象层级不同混用会让架构变得极其混乱。2. 架构分层把 MCP 层和 A2A 层彻底分开2.1 三层架构的具体划分我在实际项目中总结出来的分层方式是三层工具层、智能体层、编排层。工具层由 MCP Server 组成每个 Server 封装一组相关的工具能力比如文件操作、数据库查询、HTTP 请求、代码执行。智能体层由若干 Agent 组成每个 Agent 通过 MCP Client 连接到自己需要的 MCP Server同时通过 A2A 接口暴露自己的能力。编排层负责决定什么任务交给哪个 Agent它本身也可以是一个 Agent也可以是一个确定性的工作流引擎。这个分层的价值在于每一层都可以独立测试。工具层可以用 MCP Inspector 单独验证工具是否正常工作智能体层可以用固定的输入测试单个 Agent 的行为编排层可以用 mock Agent 测试路由逻辑。如果三层混在一起任何一个环节出问题你都得从头查起。2.2 为什么不让 Agent 直接互相调用 MCP这是我在早期设计中犯过的一个错误。当时图省事让编排 Agent 直接持有所有 MCP Server 的连接然后根据任务类型调用不同的工具。结果就是编排 Agent 的系统提示词里塞了几十个工具定义模型经常选错工具而且每次新增工具都要改编排 Agent 的配置。正确的做法是每个 Agent 只持有自己职责范围内的 MCP 连接。搜索 Agent 只连搜索相关的 MCP Server代码 Agent 只连代码仓库和沙箱执行的 MCP Server。编排 Agent 不直接调用任何 MCP 工具它只通过 A2A 向其他 Agent 发任务。这样一来编排 Agent 的提示词非常干净只需要描述每个 Agent 的能力边界即可。2.3 A2A 的 Agent Card 设计要点A2A 协议里有个很重要的概念叫 Agent Card相当于每个 Agent 的名片描述了它的名称、能力、输入输出格式、认证方式等。我在设计 Agent Card 时有几个心得。能力描述要面向任务而非面向工具。不要写本 Agent 可以调用 grep 和 ripgrep而要写本 Agent 可以在代码库中搜索特定模式并返回匹配位置和上下文。因为编排 Agent 是根据任务语义来路由的不是根据工具名。输入输出格式要尽量结构化。我一般用 JSON Schema 定义这样编排 Agent 可以程序化地校验参数而不是靠自然语言猜测。比如搜索 Agent 的输入 schema 里query 是必填字符串file_pattern 是可选字符串max_results 是可选整数默认 20。认证方式要在 Card 里明确声明。A2A 支持多种认证机制我通常用简单的 Bearer Token 做内部 Agent 之间的认证因为都是内网通信不需要太复杂。但如果你的 Agent 要暴露给外部系统就得考虑 OAuth 之类的方案。3. 用 MCP 给每个 Agent 装配专属工具箱3.1 MCP Server 的粒度怎么把握一个常见的困惑是应该做一个大而全的 MCP Server还是拆成很多小的我的经验是按数据源或按权限边界来拆。比如文件系统操作是一个 Server数据库访问是另一个 Server外部 API 调用又是另一个。这样拆的好处是权限控制清晰——你可以给代码 Agent 挂载文件系统 Server 和代码执行 Server但不给它挂数据库 Server。粒度太细也有问题。我曾经把每个工具都做成独立的 MCP Server结果一个 Agent 要连十几个 Server启动慢、配置复杂、日志分散。后来合并成按领域划分一个领域一个 Server里面包含该领域的多个工具这样平衡得比较好。3.2 工具描述的写法直接影响模型选择准确率MCP 工具的 description 字段是给模型看的写法直接决定了模型能不能选对工具。我踩过的坑是描述写得太简短比如搜索文件模型根本不知道是搜文件名还是搜内容也不知道支持什么参数。好的描述应该包含三部分这个工具做什么、什么时候用它、关键参数的含义。举个例子{ name: search_code, description: 在代码库中搜索匹配指定正则表达式的内容。适用于查找函数定义、变量引用、TODO 注释等。返回匹配的文件路径、行号和上下文。不适用于搜索文件名如需按文件名搜索请使用 find_files 工具。, inputSchema: { type: object, properties: { pattern: { type: string, description: 正则表达式模式例如 def\\s\\w 匹配 Python 函数定义 }, path: { type: string, description: 搜索的起始目录默认为项目根目录 }, max_results: { type: integer, description: 最大返回结果数默认 50超过可能导致上下文过长 } }, required: [pattern] } }注意最后那句不适用于搜索文件名这种负向说明非常有用能显著减少模型选错工具的概率。3.3 MCP 连接的生命周期管理在多智能体系统里MCP 连接的管理是个容易被忽视的工程问题。每个 Agent 启动时建立 MCP 连接如果连接失败要有重试机制如果 Server 中途挂了要有降级策略。我的做法是在 Agent 初始化时做一次健康检查把所有 MCP Server 的 tools/list 调一遍确认能拿到工具列表。运行过程中如果某个工具调用连续失败三次就把这个 Server 标记为不可用后续任务路由时跳过依赖它的 Agent。同时后台有个定时任务每隔 30 秒重试一次恢复了就重新标记为可用。注意MCP 连接是有状态的不要在多线程环境下共享同一个连接对象。每个 Agent 实例应该持有自己的连接或者用连接池管理。4. A2A 通信的实战细节从任务委派到结果汇总4.1 任务委派的消息格式设计A2A 的核心交互模式是任务委派编排 Agent 发一个任务给执行 Agent执行 Agent 完成后返回结果。消息格式我一般用这样的结构{ task_id: uuid-v4, from_agent: orchestrator, to_agent: code_search_agent, intent: search_code, parameters: { pattern: def\\shandle_, path: ./src, max_results: 30 }, context: { parent_task_id: uuid-of-parent, deadline_seconds: 60 } }task_id用于追踪和去重context里的parent_task_id用于构建任务树方便调试时看到完整的调用链路。deadline_seconds是超时控制执行 Agent 如果预计无法在时限内完成应该返回一个部分结果 需要更多时间的响应而不是硬撑到超时。4.2 同步调用还是异步回调A2A 支持同步和异步两种模式。同步模式下编排 Agent 发完任务后阻塞等待结果异步模式下执行 Agent 先返回一个已接受的确认完成后通过回调通知。我的选择是短任务用同步长任务用异步。判断标准是任务预计耗时——如果超过 10 秒就走异步。因为同步阻塞会占用编排 Agent 的上下文而且如果执行 Agent 挂了编排 Agent 会一直等下去。异步模式的实现要点是回调地址的管理。执行 Agent 需要知道完成后往哪里发结果这个信息放在任务的callback_url字段里。回调时要带上task_id和结果编排 Agent 根据task_id找到对应的等待中的任务唤醒它继续执行。4.3 多 Agent 结果汇总的冲突处理当一个任务被拆给多个 Agent 并行执行时结果汇总会遇到冲突。比如两个 Agent 都返回了当前代码库中 handle_ 开头的函数有 15 个但具体列表不一样。这时候怎么办我的策略是按置信度和时效性排序。每个 Agent 返回结果时可以带一个confidence字段0 到 1以及timestamp。汇总时优先采用置信度高的如果置信度相同则采用时间戳新的。如果差异很大且无法自动裁决就把多个结果都呈现给上层由编排 Agent 或最终用户决定。还有一种冲突是资源竞争。两个 Agent 同时想修改同一个文件这时候需要加锁。我在 A2A 层实现了一个简单的分布式锁基于task_id和资源标识做互斥拿不到锁的 Agent 进入等待队列。5. 编排逻辑让正确的 Agent 做正确的事5.1 路由策略规则优先还是模型优先编排 Agent 的核心工作是路由——根据任务描述决定交给哪个执行 Agent。路由策略有两种基于规则的关键词匹配、正则和基于模型的让 LLM 判断。我的实践是混合使用。对于高频、明确的任务类型用规则路由快且稳定。比如任务描述里包含搜索代码就走代码搜索 Agent包含查询数据库就走数据库 Agent。对于模糊的、复合的任务才交给模型判断。规则路由的实现很简单维护一个意图到 Agent 的映射表用正则或关键词匹配。模型路由则需要给编排 Agent 一个清晰的提示词列出所有可用 Agent 及其能力让模型输出目标 Agent 名称和参数。5.2 任务拆解什么该拆什么不该拆不是所有任务都值得拆解。我见过有人把读取一个文件并返回内容也拆成定位文件和读取内容两个子任务这纯属过度设计。判断标准是如果子任务可以并行执行或者需要不同 Agent 的专业能力就拆否则不拆。比如分析这个项目的代码质量并生成报告就值得拆——代码搜索、静态分析、报告生成可以分给不同 Agent而且搜索和分析可以并行。而把这个字符串转成大写就没必要拆。拆解时要注意依赖关系。有些子任务有先后顺序比如必须先搜索到文件才能分析内容。这种依赖要在任务图里明确标注编排 Agent 按拓扑序执行。5.3 失败重试与降级策略多智能体系统里失败是常态。网络抖动、模型超时、工具报错都会导致任务失败。我的重试策略是指数退避 最大次数限制。第一次失败等 1 秒重试第二次等 2 秒第三次等 4 秒最多重试 3 次。如果重试仍然失败就触发降级。降级策略取决于任务类型。对于搜索类任务降级为返回缓存结果或提示用户手动搜索。对于生成类任务降级为返回一个简化版本或模板。对于关键任务降级为通知人工介入。提示重试时要确保任务是幂等的。如果任务有副作用比如写文件、发邮件重试前要检查是否已经执行过避免重复操作。6. 可观测性多智能体系统调试的命脉6.1 分布式追踪的落地方式多智能体系统的调试难度是指数级上升的因为一个请求可能经过五六个 Agent每个 Agent 又调用若干 MCP 工具。没有好的追踪手段出了问题根本无从下手。我的做法是全链路 Trace ID 贯穿。编排 Agent 生成一个根 Trace ID每次 A2A 调用和 MCP 工具调用都带上这个 ID所有日志都输出这个 ID。这样出问题时用 Trace ID 一搜就能看到完整的调用链路。日志格式我用结构化 JSON每条日志包含trace_id、span_id、parent_span_id、agent_name、event_type、duration_ms、status等字段。这样可以直接导入到日志分析系统里做可视化和告警。6.2 关键指标监控除了日志还需要监控一些关键指标。我关注的指标包括每个 Agent 的调用次数和成功率、每个 MCP 工具的调用次数和平均耗时、A2A 消息的端到端延迟、任务队列的长度、失败任务的重试次数。这些指标用 Prometheus 格式暴露配合 Grafana 做面板。我一般会设置几个告警规则某个 Agent 成功率低于 90% 持续 5 分钟、任务队列长度超过 100、端到端延迟 P99 超过 30 秒。6.3 回放与复现调试多智能体系统最痛苦的是问题难以复现。我的解决方案是记录完整的输入输出。每个 Agent 的每次调用都把输入消息、输出结果、调用的工具和参数、耗时都记录下来。这样出问题时可以拿历史记录回放逐步定位是哪一步出了偏差。回放时要注意有些工具调用有副作用不能直接重放。我的做法是给工具调用加一个dry_run模式回放时用 dry_run 模式执行只验证逻辑不产生实际影响。7. 实战中踩过的坑与应对经验7.1 上下文爆炸Agent 之间传递的信息太多早期设计时我让每个 Agent 把完整的上下文都传给下一个 Agent结果上下文迅速膨胀模型开始丢失关键信息而且 token 成本飙升。后来改成只传必要信息。每个 Agent 的输出经过一个摘要步骤提取关键结论和必要数据丢弃中间过程。比如代码搜索 Agent 返回的不是所有匹配行的完整内容而是文件路径、行号和匹配行的简短摘要。7.2 循环调用Agent A 调用 BB 又调用 A这是多智能体系统里很危险的一种情况。我遇到过搜索 Agent 找不到结果时调用编排 Agent 请求澄清编排 Agent 又调用搜索 Agent 重新搜索形成死循环。解决办法是在任务上下文里记录调用链每次 A2A 调用前检查目标 Agent 是否已经在当前调用链里出现过。如果出现过就拒绝调用并返回错误。同时设置最大调用深度超过就强制终止。7.3 工具权限越界Agent 做了不该做的事有一次测试时客服 Agent 意外调用了一个删除文件的工具原因是它的 MCP 连接里包含了文件系统 Server而模型在某种提示下选择了删除操作。这个坑让我意识到权限隔离必须在架构层面做不能靠提示词约束。后来我给每个 Agent 配置了独立的 MCP Server 白名单客服 Agent 只能连只读的文件 Server根本没有删除工具可用。这样即使模型选错了也造不成实际损害。7.4 模型选择困难工具太多导致选择准确率下降当一个 Agent 挂载了 20 多个工具时模型选择正确工具的概率会明显下降。我实测下来工具数量超过 15 个后选择准确率开始显著降低。应对策略是按任务阶段动态加载工具。比如代码审查 Agent 在搜索阶段只加载搜索相关工具进入分析阶段再加载分析工具。这样每个阶段模型面对的工具数量控制在 10 个以内选择准确率明显提升。8. 从原型到生产还需要补哪些工程能力8.1 配置管理让 Agent 和工具可插拔原型阶段硬编码没问题但生产环境必须做到配置驱动。我把 Agent 的定义、MCP Server 的连接信息、路由规则都放在配置文件里启动时加载。这样新增一个 Agent 或调整路由规则不需要改代码改配置重启即可。配置格式我用 YAML结构清晰易读。每个 Agent 的配置包含名称、A2A 端点、MCP Server 列表、系统提示词、超时设置等。路由规则单独一个文件用意图到 Agent 的映射表。8.2 版本管理Agent 和工具的兼容性Agent 和 MCP 工具都会迭代版本不兼容会导致运行时错误。我的做法是在 Agent Card 里声明依赖的 MCP 工具版本范围启动时校验实际连接的 Server 版本是否满足要求。不满足就拒绝启动避免运行到一半才报错。A2A 消息格式也要版本化。我在消息里加一个protocol_version字段接收方根据版本决定如何解析。这样新旧版本可以共存一段时间平滑升级。8.3 安全边界认证、授权、审计生产环境的安全不能马虎。A2A 通信我用 mTLS 双向认证确保只有合法的 Agent 能互相调用。MCP 连接用 Token 认证每个 Agent 有独立的 Token权限最小化。审计日志记录所有敏感操作包括谁在什么时候调用了什么工具、传了什么参数、返回了什么结果。审计日志单独存储不可篡改保留至少 90 天。8.4 成本控制Token 消耗和工具调用次数多智能体系统的成本主要来自两块LLM 的 token 消耗和外部工具的调用费用。我见过一个团队因为没做成本控制一个月烧掉了几万块。控制手段包括给每个 Agent 设置 token 预算超了就降级到更便宜的模型或返回缓存结果给工具调用设置频率限制防止某个 Agent 疯狂调用定期分析日志找出 token 消耗大户做针对性优化。9. 一个完整的落地案例代码审查多智能体系统9.1 系统组成与职责划分为了把前面的内容串起来我拿一个实际做过的项目举例——代码审查多智能体系统。这个系统包含四个 Agent编排 Agent、搜索 Agent、分析 Agent、报告 Agent。编排 Agent 接收审查请求拆解为搜索相关代码、分析代码质量、生成审查报告三个子任务按顺序委派给其他 Agent。搜索 Agent 连接代码仓库的 MCP Server根据关键词和文件模式搜索相关代码片段。分析 Agent 连接静态分析工具的 MCP Server对搜索到的代码做质量检查。报告 Agent 汇总分析结果生成结构化的审查报告。9.2 关键流程的代码骨架编排 Agent 的核心逻辑大概长这样async def review_code(repo_path, review_request): trace_id generate_trace_id() # 第一步搜索相关代码 search_result await a2a_call( targetsearch_agent, intentsearch_code, parameters{query: review_request.keywords, path: repo_path}, trace_idtrace_id ) if not search_result.success: return handle_failure(search_result, trace_id) # 第二步分析代码质量 analysis_result await a2a_call( targetanalysis_agent, intentanalyze_quality, parameters{files: search_result.files}, trace_idtrace_id ) # 第三步生成报告 report await a2a_call( targetreport_agent, intentgenerate_report, parameters{ search_result: search_result.summary, analysis_result: analysis_result.findings }, trace_idtrace_id ) return report每个a2a_call内部处理了序列化、认证、超时、重试、日志记录等通用逻辑业务代码只需要关注任务本身。9.3 实测效果与优化过程这个系统上线后代码审查的平均耗时从人工的 2 小时缩短到 8 分钟覆盖率从抽查 30% 提升到全量审查。但也遇到了一些问题。最初搜索 Agent 返回的结果太多导致分析 Agent 的上下文超限。后来在搜索 Agent 里加了结果数量限制和相关性排序只返回最相关的 50 个片段。分析 Agent 的准确率也从最初的 70% 提升到 92%主要优化是给分析工具的描述加了更多示例让模型更清楚什么时候该用哪个检查规则。报告 Agent 最初生成的报告太啰嗦后来调整了提示词要求每个问题不超过三句话包含位置、严重程度、修复建议报告的可读性明显提升。10. 后续可以继续深挖的方向这套架构跑通之后还有几个方向值得继续探索。一是动态 Agent 编排根据任务复杂度自动决定拆几个子任务、用哪些 Agent而不是固定流程。二是Agent 能力学习记录每个 Agent 在不同任务上的表现动态调整路由权重让表现好的 Agent 承担更多任务。三是跨组织 A2A当 Agent 需要调用外部组织的 Agent 时如何做认证、计费、隐私保护这是更复杂的工程问题。我在实际项目中的体会是多智能体系统的复杂度不在于单个 Agent 有多强而在于 Agent 之间的协作是否顺畅、边界是否清晰、失败是否可控。MCP 和 A2A 提供了很好的基础设施但真正决定系统好不好用的还是那些工程细节——日志、监控、重试、降级、权限、成本。把这些做扎实了系统才能从 demo 走向生产。