
先交代一个背景我手上有三个Agent一个负责需求分析和任务拆解一个负责写代码一个负责代码审查。分开跑的时候每一个都很正常一旦想让它们协作第一个撞上的问题不是模型能力而是最基础的通信问题——A的输出怎么变成B的输入两个Agent之间到底是用自然语言对话还是直接传结构化数据调用的边界在哪里超时重试怎么处理上下文会不会互相污染这些问题我踩了一两个月试过几种方案最后被同事一句话点醒跨Agent调用根本不是某个框架的配置项而是一套需要自己设计和维护的架构决策。今天把这段实践和背后那条路线之争的脉络写清楚希望能让正在做Agent开发的同行少走点弯路。本文适合已经有一个能跑通的Agent、打算做多Agent协作的开发者也适合在设计Agent平台时纠结选型的技术负责人。1. 单Agent的边界撞墙之后跨Agent调用要解决的三个真实问题1.1 单体Agent的上下文和工具双重瓶颈一个Agent想干所有事最大的敌人是上下文窗口。我给一个Agent同时挂了代码生成、代码审查、测试执行、文档生成四组工具刚开始觉得全能跑了几轮就发现它开始犯低级错误明明只需要生成一段Python函数它却把审查规则里的限制条件也考虑进去生成结果变得特别保守或者在做测试的时候突然调用了文档生成的工具输出完全跑偏。原因在于一个模型在有限的上下文里同时装载如何写代码如何审查代码如何跑测试如何写文档这些差异巨大的指令指令之间会互相干扰。更现实的问题是工具数量膨胀之后模型在每一步都要从十几个工具里选一个工具选择错误的概率会直线上升。把一个大Agent拆成多个专注的Agent不是锦上添花而是单体Agent在上下文和工具选择两个维度上撞墙之后的必然结果。1.2 三种主流协作模式流水线、编排、竞速评审拆完之后怎么协作实践中我发现主流需求可以归纳成三种模式。第一种是流水线模式Pipeline最直观需求Agent拆解任务代码Agent写代码审查Agent给出修改意见每个Agent负责一段输出成为下一个的输入。这种模式逻辑简单链路清晰但每一环的延迟是累加的如果中间某个Agent输出格式不对后面全线崩盘。第二种是编排模式Orchestrator有一个主控Agent负责动态决策它读任务判断这一步该交给哪个子Agent汇总结果后再决定下一步。这种模式灵活但主控Agent本身会成为瓶颈而且主控的决策质量完全取决于它对子Agent能力的理解程度经常出现派错活的情况。第三种是竞速/评审模式多个Agent做同一件事比如让三个不同风格的Agent分别生成方案再由一个评审Agent投票或打分选出最优。这种模式结果质量高但成本是成倍增长的适合高价值决策场景。这几种模式不是互斥的实际项目中经常混合使用。但无论哪种模式落到实现层面都需要回答三个问题Agent之间怎么通信、传什么格式的数据、失败时怎么处理。1.3 通信协议、数据契约、执行语义跨Agent调用的三要素我习惯把跨Agent调用拆成三个要素任何一个设计不到位整个系统都会埋雷。第一个是通信协议。两个Agent在同一个进程里可以直接函数调用在不同服务里可以走HTTP、消息队列或者用MCP这类标准化协议。协议的本质是传输通道它决定了调用的同步异步、可靠性和延迟特征。第二个是数据契约。A传给B的是什么是纯文本描述还是带有固定字段的JSON契约不清晰Agent之间就会像两个语言不通的人互相比划表面上在对话实际上各说各的。我见过最典型的翻车现场是需求Agent输出的任务描述在结果里代码Agent却从对话历史里找任务结果反复找不到白白烧了三次token。第三个是执行语义。一次跨Agent调用是同步等结果还是异步拿回执超时设多少失败重试最多几次重试会不会导致下游重复执行这些问题没有提前定义后面线上出故障时根本无从下手。2. 四条技术路线工具调用、消息队列、MCP、语义路由的全面对比2.1 工具调用路线把Agent硬包装成函数简单但不适合复杂协作最早的跨Agent调用方案就是把AgentB封装成AgentA的一个工具。AgentA在推理时发现任务需要B的能力就发起一次工具调用背后的执行器收到请求后运行B然后把B的结果作为工具返回值交还给AgentA。这个路线最大的优势是简单完全符合LLM工具调用的既有范式AgentA不需要知道B的存在细节只需要知道有这个工具、传什么参数、拿什么结果。但它有硬伤。首先工具调用天然是同步阻塞的A调用B之后必须等B返回如果B内部还要调用C、D整条链路就会变成一个长同步调用任何一个环节超时A都要跟着超时。其次工具调用的错误处理很粗糙B返回一个报错字符串A能不能理解并自行修复大多数情况下A只会把这个错误原封不动地夹在对话里继续执行根本不会做真正的容错。这个路线适合的子Agent是执行时间短、逻辑稳定、不需要复杂状态同步的场景。2.2 消息队列路线用异步消息化解耦跨进程协作第二个路线是把跨Agent调用改成消息传递。A把任务封装成一条消息发到队列里B监听队列消费消息后处理再把结果发回结果队列A通过异步方式接收。这个路线的核心价值是解耦和削峰。A不需要等B处理完再继续中间可以穿插其他任务B实例也可以水平扩展多个B同时消费队列吞吐量大幅提升。消息队列本身自带的ack、重试、死信机制让失败处理有了制度保障。缺点是延迟和复杂度。一个消息从A发到队列再到B处理完回到A链路比同步调用长很多不适合对时延敏感的场景。另外消息契约一旦定义就难以变更字段改名要经过严格的版本兼容流程否则新旧实例同时跑的时候会互相踩踏。消息队列适合长流程、跨进程、需要高可靠性的任务流但要求团队有比较强的中间件运维能力。2.3 MCP路线把Agent能力标准化成服务接口MCPModel Context Protocol这两年是Agent工具化方向最被看好的协议之一。它的思路是把Agent的能力封装为标准化的服务端接口其他Agent通过MCP客户端像调用工具一样调用这些接口。这个路线的意义在于标准化和生态。一旦Agent能力以MCP形式暴露任何支持MCP协议的Agent客户端都能直接对接不需要为每个Agent定制通信代码。服务发现、鉴权、参数校验这些能力也随协议一起逐步完善相当于把跨Agent调用的方言统一成了普通话。要泼一盆冷水MCP本质上更适合工具型交互也就是请求-响应模式对于需要多轮对话、需要共享上下文、需要记忆的协作场景MCP目前的表达能力还不太够。我试过把一个需要和用户来回确认需求的Agent包成MCP服务调用方传进来的参数只有两三个固定字段需求确认的过程根本没法展开最后只能放弃。MCP适合能力开放和工具集成不适合承载复杂的对话式协作。2.4 语义路由/Agent网关路线调用方只面对一个入口第四个路线是Agent网关。调用方不直接接触任何具体Agent而是面对一个统一入口入口内部根据任务语义由LLM或规则引擎判断该把请求路由给哪一个Agent。这个路线的核心收益是调用方与Agent实例完全解耦。上层业务不需要维护什么任务找谁的映射关系新增一个Agent或替换一个Agent网关侧配置一下就行对调用方透明。网关还可以统一做鉴权、限流、日志和链路追踪跨Agent调用的治理能力一下子上来了。代价同样明显网关本身会成为新的单点和瓶颈。路由判断依赖LLM推理的话一次路由额外引入几百毫秒延迟和一次模型调用成本路由判断一旦出错请求被送到错误的Agent下游会连锁出错而且排查起来比直连难得多。我觉得这个路线不是开局就能用的而是Agent数量多到一定程度、调用关系乱到无法维护之后的治理选择。2.5 路线对比小结用一张表把这四条路线的核心差异列出来方便直接对照选型。路线同步/异步耦合度可靠性适用场景主要代价工具调用同步高弱同进程轻量子Agent超时链长、容错差消息队列异步低强跨进程长流程延迟高、运维复杂MCP同步为主中中能力标准化开放不适合对话式协作Agent网关同步/异步极低中多Agent治理与入口统一路由延迟、单点风险选型口诀基本是进程内、轻量、要即时反馈用工具调用跨服务、长任务、要高可靠用消息队列对外标准化能力用MCPAgent数量失控了再考虑上网关。大多数人一上来就想上网关或者想拿MCP解决一切协作问题其实都是没想清楚自己缺的到底是传输通道还是标准化协议。3. 实战拆解从零实现一套跨Agent调用的可运行方案3.1 场景定义与数据契约先行我自己搭的演示系统是一个简化版的开发协作流水线需求Agent负责把用户描述转化成可执行需求文档代码Agent根据需求文档生成代码审查Agent检查代码并输出修改意见。三个Agent独立部署跑在同一个Python进程里方便演示但调用的边界完全按跨服务的方式来设计。动手写代码之前我做的第一件事是定义数据契约。三个Agent之间的数据流有三段需求Agent到代码Agent的需求文档代码Agent到审查Agent的待审查代码审查Agent到代码Agent的审查意见。对应设计了三个JSON Schema每个Schema都带版本号字段。以需求文档为例字段包括需求ID、原始描述摘要、功能点列表、约束条件列表、验收标准、创建时间。代码Agent消费的时候只需要关注这几个字段不会被需求Agent的对话历史里那些分析过程干扰。这个设计在后面帮了大忙——审查Agent改字段的时候通过版本号能立刻定位是哪个环节出了问题而不是靠猜。3.2 混合架构工具调用做同步应答队列做异步流水我的最终架构是一个混合方案把三种Agent的执行器注册到一个AgentRegistry里Agent之间的调用分两种形态。一种是AgentA需要AgentB立即返回结果的场景比如代码Agent向审查Agent发起审查请求代码Agent必须拿到审查意见才能做修改。这种我用工具调用强同步但封装了超时和重试。另一种是流水线场景需求Agent处理完任务后把需求文档投递到任务队列代码Agent从队列里拿任务开始写代码。这种我不让需求Agent同步等代码Agent的结果而是把队列当拼接缝需求Agent完成自己的职责就结束后面的环节独立跑。这么设计的理由是代码生成可能花几十秒甚至几分钟让需求Agent同步等一个几分钟的任务既浪费它的时间也会让它带着上一个任务还没结束的心理负担处理下一个任务影响质量。队列隔开之后每个Agent的任务边界变得非常干净。3.3 核心代码骨架Agent注册表与调用链下面给出一份可以跑通的最小骨架语言用PythonAgent本身用OpenAI格式的接口但跨Agent调用逻辑与模型无关。import json import time from typing import Any, Callable from dataclasses import dataclass, field from collections import deque dataclass class Agent: name: str version: str executor: Callable[[dict], dict] # 每个Agent声明自己能处理的消息类型用于路由和校验 accepts: list[str] field(default_factorylist) class AgentRegistry: def __init__(self): self._agents: dict[str, Agent] {} def register(self, agent: Agent): self._agents[agent.name] agent print(f[registry] {agent.name}{agent.version} 已注册) def resolve(self, name: str) - Agent: return self._agents[name] class CallChain: 同步工具调用的外层封装统一处理超时和重试 def __init__(self, registry: AgentRegistry, timeout: float 30.0, max_retry: int 2): self.registry registry self.timeout timeout self.max_retry max_retry def call(self, target_agent: str, payload: dict) - dict: agent self.registry.resolve(target_agent) last_err None for attempt in range(1, self.max_retry 2): try: start time.time() # 执行器内部自行做LLM调用这里只关心结果 result agent.executor(payload) if not isinstance(result, dict): raise ValueError(执行器必须返回dict) result.setdefault(_trace, { target: target_agent, attempt: attempt, duration_ms: int((time.time() - start) * 1000), }) return result except Exception as e: last_err e print(f[callchain] 调用 {target_agent} 第{attempt}次失败: {e}) time.sleep(2 ** attempt) # 指数退避 raise RuntimeError(f调用 {target_agent} 超过重试上限: {last_err}) _task_queue: deque[dict] deque() def enqueue_task(task: dict): 异步投递任务返回任务ID调用方不用等结果 task_id ftask-{int(time.time() * 1000)} _task_queue.append({task_id: task_id, **task}) return task_id def worker_loop(registry: AgentRegistry, agent_name: str): 模拟队列消费者持续处理发往指定Agent的任务 while True: if not _task_queue: time.sleep(0.5) continue task _task_queue.popleft() if task.get(target) ! agent_name: # 不是本Agent的任务放回队尾 _task_queue.append(task) time.sleep(0.1) continue agent registry.resolve(agent_name) result agent.executor(task) print(f[queue] {agent_name} 完成任务 {task[task_id]}结果摘要: {json.dumps(result)[:200]})实际执行时每个Agent的executor内部调用LLM并把模型输出解析成契约里的JSON格式。关键点是注册表让按名字找Agent变得统一调用链封装了超时重试队列实现了异步解耦三个组件合起来就是一个最简可用的跨Agent调用基础设施。3.4 上下文隔离只传契约结果不传完整对话这里必须单独强调上下文隔离这是我踩坑最深的地方。最早我图省事把AgentA的完整对话历史直接拼进AgentB的system prompt结果AgentB把AgentA的思考过程当成了自己的分析依据输出了一系列项目里根本不存在的问题编造得有理有据差点让人以为审查Agent真的发现了严重bug。正确做法是跨Agent调用传递的永远是执行结果不是对话过程。A的完整思维链是A的私有信息B只需要看到A产出的结构化契约结果。这就像团队协作你交给下游的应该是交付物而不是把你在工位上说的每句话都录下来发给他。上下文隔离能显著降低下游Agent的上下文占用也能避免它被上游的心理活动带偏。另外要维护好调用链的追踪ID。每个跨Agent调用都带一个上游task_id日志里通过task_id可以把整条流水线串起来。没有这个ID线上排查的时候得靠肉眼猜哪条消息属于哪次调用那种痛苦经历过一次就不想经历第二次。4. 路线之争的本质控制权、上下文与工作流原子性4.1 中心化编排与去中心化自组织之争跨Agent调用的路线之争表面上是技术选型差异本质上是控制权之争。中心化编排方案里一个主控Agent或一个代码层面的Orchestrator掌握全局每一步让谁执行、按什么顺序执行全部由中心节点决定。去中心化自组织方案里Agent之间通过消息互相协商谁有能力谁接活没有全局控制节点。中心化最大的优点是可预期执行顺序、资源分配、失败处理都有明确归属特别适合流程固定的业务场景。缺点上面也说过主控Agent是全系统的认知瓶颈它如果判断错了子Agent的能力边界整个任务链都会偏。去中心化在系统弹性和故障隔离上更有优势但没有全局视角这个特点让它很难保证复杂流程的正确性很容易出现Agent之间互相等消息造成活锁。我的实际体会是拿中心化还是去中心化当口号争没有意义关键看工作流的原子性。如果你的业务天然是固定流程中心化编排能用代码写清楚就别硬掰成自组织如果业务流程本身高度动态每条任务路径都不同才需要借助去中心化的灵活性。大多数项目属于前者所以中心化编排依然是当前最务实的起步方案。4.2 对话式协作与结构化数据之争这是我在团队里吵得最凶的一条路线之争。支持对话式的人认为Agent天然以自然语言为核心Agent之间用自然语言对话最符合模型的理解方式AgentA把任务用一句话发给AgentBAgentB自己解析。支持结构化数据的人认为Agent之间传递的应该是严格Schema的JSON字段明确、语义清晰模型不需要臆测。两种方案都有过失败案例。对话式协作的问题是稳定性差模型会用各种不同的措辞表达同一个意思下游Agent的理解也会跟着波动而且对话历史越长语义漂移越严重录人类对话的Agent经常自己加戏。结构化数据的问题是灵活性差超出Schema范围的信息不知道怎么传偶尔出现一个不在预设字段里的需求整个链路就要改。我现在的倾向是结构化为主对话为辅主体信息走JSON契约模型的自由解释放在一个专门的reasoning字段里。这样既保证了主链路的确定性也给模型留下了表达空间。纯粹的自然语言Agent间对话目前更适合语义高度灵活但错误容忍度高的场景比如头脑风暴、方案构思不适合工程化的协作链路。4.3 共享上下文、黑板架构与持久记忆第三种路线之争围绕上下文展开。一种做法是把所有Agent的上下文放进一个共享空间每个Agent都能看到全局信息这就是黑板架构Blackboard。另一种做法是每个Agent只维护自己的上下文通过专门设计的消息传递交换信息。黑板架构在视觉上很优雅所有Agent都在同一块黑板上写字、读字协作看上去非常直观。但工程上它有个致命问题黑板内容迅速膨胀每个Agent读取时都要从大量无关信息中筛选自己的部分token消耗巨大而且信息间的相互引用容易产生隐性耦合。我在一个Demo项目里试过黑板跑了几轮后每个Agent的prompt都膨胀到近万token响应时间肉眼可见地变慢。持久记忆是另一个被频繁讨论的方向。跨Agent调用时与其每次把上下文传过去不如让Agent们共享一个外部记忆库需要什么信息按需检索。这个思路我很认可但要注意记忆库不是垃圾桶写入记忆的信息需要经过筛选和结构化否则Agent从记忆库里检索出来的全是噪音效果比不检索还差。我在实践中更倾向按任务维度建独立记忆空间任务结束就清理而不是做一个全局大水缸。4.4 Harness、Skills与Agent编排的关系再梳理很多同行问过Harness和Agent到底什么区别Skills和Agent的区别跟跨Agent调用有什么关系。我的理解是Harness是承载Agent运行的执行环境它负责加载模型配置、维护上下文、处理工具调用循环、管理生命周期。跨Agent调用发生的时候真正做调度、路由、消息投递的往往是Harness层面的事情而不是Agent内部逻辑。Agent只关心我收到了什么输入、该产出什么输出至于消息是从哪个Agent来的、要不要重试这些交给Harness处理。Skills是Agent拥有的可复用技能模块本质上是预定义的调用模板和行为规范。跨Agent调用可以把一个Agent的Skill暴露给另一个Agent使用这时候Skill就扮演了可复用单元的角色。但Skill和Agent的边界要清楚Skill是静态的能力描述Agent是动态的决策主体。试图用一堆Skill拼成一个Agent简单但要在Agent之间动态传递Skill执行权复杂度就完全不一样了。把这两个概念分开设计和讨论的时候会清爽很多。5. 线上实测跨Agent调用最常见的五个坑与排查链路5.1 死循环调用A调B、B调A如何快速打断我遇到过最尴尬的线上事故是Agent A调用Agent B而B在处理过程中又发起调用Agent A的意图不明请求两个Agent互相调用直到把token预算烧穿。根因是编排逻辑里缺少调用深度限制和环路检测。排查链路是这样的先看链路追踪日志发现同一个task_id在两分钟内反复出现A→B、B→A的调用记录基本可以确定是死循环。定位到代码后发现是A的一个工具描述里写了如果处理过程中需要额外信息可以调用需求澄清助手而B恰好也注册了需求澄清助手这个名字B为了完成任务又去调用它恰好这个助手在实现上回指了A。修复方案有两层。第一层是硬性限制在CallChain里加最大调用深度超过就抛异常并终止链路同时加环路检测记录本次调用链上的Agent名集合重复出现就立刻熔断。第二层是软性治理工具描述要写清楚只做xx不要调用其他Agent给模型明确边界。这两层缺一不可硬限制保命软治理保质量。5.2 上下文污染B拿到了A的思考过程给出错误结论上下文污染是跨Agent调用里最隐蔽的问题。症状是下游Agent产出结果听起来很有道理但完全不着边际而且这类错误很难通过单元测试发现因为它不是逻辑错误是信息源错误。我的案例是审查Agent引用了代码生成Agent思考过程中的一个假设——这段代码目前没有性能瓶颈——然后顺着这个假设写出了无需优化的审查结论。但实际情况是代码生成Agent在思考过程中提到这个假设时本来就带了一个暂不考虑性能的限定审查Agent把这个上下文中的限定丢掉了。排查思路先对比上下游Agent的输入输出确认B的输入里包含A的原始对话内容然后查A的Executor是不是把完整消息历史传给了B。修复方法很直接就是前面说的上下文隔离——B的prompt里只允许出现契约定义的输入字段。我在系统里加了一条硬编码规则跨Agent调用时除reasoning字段外任何来自上游的非结构化工文本都会被拦截并告警。5.3 超时重试导致重复执行幂等性设计异步消息队列方案里最常见的事故是重复执行。B执行任务花了40秒而A设置的是30秒超时A判定失败并重试但第一次执行其实已经成功并把结果写入了结果队列。B最终被触发了两次生成了两份结果下游拿到两份一样的数据轻则去重麻烦重则重复扣费或执行两次有副作用的外部调用。排查链路是看结果队列里的task_id有没有重复。修复的关键是给任务加幂等键B在处理前先查这个任务是否已经被处理过处理过就直接返回已有结果。这个逻辑要在B的业务逻辑之前做不能在LLM调用之后做因为LLM调用本身就有副作用。另一个实用技巧是超时时间要参考任务耗时的分布来设置。先跑一段时间的基线数据看90分位耗时超时时间设为90分位耗时的两倍以上而不是随便拍一个数。我见过太多系统把超时设成5秒而下游Agent光生成本文就要15秒这种超时形同虚设只会制造无谓的重试。5.4 数据契约不一致字段变更引发连锁故障跨Agent调用的数据契约一旦上线每个字段就都承担了跨系统的语义责任。我遇到过一次事故需求Agent更新了Schema把验收标准的字段名从acceptance_criteria改成了acceptance_checklist但代码Agent没有同步更新。代码Agent收到了新字段按旧字段处理结果验收标准在代码生成阶段直接丢失生成的代码完全没有按验收标准来做。这种问题的排查链路通常很长因为报错发生在下游的下游而且报错信息往往只是缺少必要的字段这种模糊描述。解决思路有三个层面契约校验前置调用链入口做JSON Schema校验字段不符合直接拒绝而不是带病向下传版本号协商每次Schema变更要带版本号下游Agent发现版本不匹配时不是硬解析而是明确报错线上加契约监控用CI任务定期比对所有Agent的Schema定义不一致就报警。5.5 安全边界与权限收敛最小化Agent的可见范围跨Agent调用还有一个绕不开的安全问题Agent可以被其他Agent调用那它能不能随便调用别人如果A被提示词注入攻击者能不能通过A去调用B再通过B调用C形成一条绕过原本身份鉴权的攻击链我的处理原则是默认最小化可见性。每个Agent注册到注册表时都要声明自己的可见范围哪些Agent可以调用它、它自己允许调用哪些Agent。调用链在发起调用前先查一下双方的可调关系不合规直接拒绝。这类似于微服务架构里的调用白名单Agent同样需要。另一条是鉴权信息不能随着上下文传递。A在调用B时绝不能把自己的API Key或身份凭证夹在payload里传过去。B需要什么凭证由B自己的环境变量或密钥托管服务来提供A不需要也不应该知道。这个原则在单Agent时代容易被忽略多Agent协作时代就成了底线级别的要求。单独再补一句给Agent系统做安全测试一定要专门测跨Agent注入链路。方法是构造一个恶意输入喂给流程入口Agent看它会不会把恶意指令传播到下游并在某个下游Agent处执行。我测出来的结果是如果不做上下文隔离这种跨Agent注入的成功率高得吓人做完隔离之后基本被阻断在入口Agent那一层。写到这里我想把最后一次项目复盘时跟同事说的那段话再拿出来跨Agent调用不是选一个框架就能躺平的事它本质上是架构设计通信协议、数据契约、执行语义、上下文边界、安全访问哪一个环节偷懒后面都会在线上用故障来提醒你。路线之争争到最后一地鸡毛的时候不妨回到最原始的问题上去——你的业务到底是固定流程多一点还是动态决策多一点你的Agent是更多依赖自然语言协作还是更依赖确定性数据。把这两个问题想清楚很多架根本不用吵。几个具体的建议亲测有效。第一项目第一天就把契约文件建出来哪怕你还没想好用什么协议Schema先定好后面改动成本最低。第二跨Agent调用日志一定要带链路ID这个投入产出比极高。第三不要一上来就搞去中心化自组织先跑通一条中心化流水线把问题和边界摸清再逐步放开灵活性。我没见过哪个项目是因为开始方案太简单而失败的倒是见过不少因为一上来就上了复杂的多Agent协作框架而拖垮进度的。