
如果你最近在做Agent开发大概率已经意识到一个问题单个Agent再强也扛不住所有事。我自己在搭Agent项目的时候第一个撞上的墙不是模型能力不够而是怎么让两个Agent互相配合。你说让一个Agent既懂日志排查、又会网络诊断、还要能写回复工单模型上下文塞满不说工具列表乱到连LLM自己都开始瞎选。跨Agent调用就是在这种背景下被反复讨论的解法同时伴随着的还有各种技术路线之间的争论。这篇内容我会把跨Agent调用的几种主流实现方式、实操代码、以及目前路线之争的核心分歧一次聊透适合正在做Agent开发、或者在团队里负责评估多Agent架构的同学。多说一句这不是纯理论文章。下面所有代码和结论都来自我最近在真实项目里反复试错后的沉淀。你照着抄能跑踩坑点也会给你标出来。1. 为什么说跨Agent调用是Agent开发绕不开的坎1.1 单体Agent的失控工具、上下文、权限三座大山先聊一个我实际遇到的场景。早前我图省事把IT运维助手做成了一个单体Agent工具列表里挂了日志查询、指标分析、告警推送、网络检测、工单创建等十几个工具。刚开始模型还能勉强分辨该调哪个但随着工具描述越来越长问题开始冒头。首先是上下文爆炸。每轮对话都要把工具描述塞给模型十几个工具的描述加起来上千token真正的用户诉求反而被挤到后面。其次是工具误选率上升。当两个工具的功能边界模糊比如“日志查询”和“指标分析”模型经常选错。最麻烦的是权限一个Agent同时持有读日志、写工单、发通知的权限一旦被prompt注入或者误操作影响面非常大。拆成多个Agent之后上面三个问题会自然缓解。每个Agent只负责一块小领域工具描述短、上下文清爽、权限可以按Agent细粒度划分。但拆开之后就出现新问题A Agent需要B Agent的产出才能继续干活这时候跨Agent调用就不可避免了。1.2 什么是跨Agent调用一句话版本跨Agent调用就是允许一个Agent去请求另一个Agent的计算能力或结论。整体逻辑跟微服务很相似服务A调用服务B的接口拿数据只不过这里“服务”变成了“Agent”参数和返回值从结构化JSON变成了自然语言、半结构化结果、甚至是一段执行计划。拿做饭类比单Agent就像一个人又要洗菜又要切菜又要炒菜还要摆盘跨Agent调用就像后厨分工洗菜Agent把净菜交给切菜Agent切好的食材再传给炒菜Agent每个环节只认自己负责的那一小块标准接口。不过后厨传菜靠的是盘子Agent之间传的是什么、怎么传、谁来定这个“盘子”的标准正是当下主线之争的核心。1.3 谁最需要关心跨Agent调用如果你只是写个单轮对话机器人跨Agent调用确实跟你没关系。但只要你开始做复杂任务拆解、或者想构建一个可靠的自动化工作流这个问题就躲不掉。具体分几类做Agent开发的需要把复杂任务拆成多个子Agent协作比如客服系统、数据分析平台。做Agent框架与编排的需要选择合适的调用协议和调度方式比如在LangGraph、自研Workflow之间做选型。做Agent安全的跨Agent调用意味着信任边界从单进程扩展到网络一旦调用链路被攻击影响范围是面状的。使用Agent平台的也需要理解平台底层的Agent互调逻辑便于排查任务执行中断类问题。我甚至看到不少人面试Agent相关的岗位最后都会被问到“多个Agent之间怎么通信”。这个问题背后考察的就是你对跨Agent调用技术路线的理解深度。2. 跨Agent调用的四种技术路线与选型思路先说结论现在行业里所谓的路线之争核心分歧在于“Agent之间的协作到底应该像函数调用一样严格还是像人类团队一样松散”。围绕这个分歧我把它拆成四条路线每条都有自己的道理也各有明显的短板。2.1 路线一把子Agent封装成工具函数主Agent统一调度这是目前最简单、也最流行的一条路线。思路很直白子Agent对外只暴露一个类似函数的接口包含名称、描述、入参Schema、返回值格式主Agent通过Function Calling机制去调用它。具体执行的时候主Agent先规划要不要调用这个“函数”LLM返回一个结构化调用请求然后运行时把请求转发给子Agent得到结果后塞回主模型的上下文里继续下一步推理。我为什么首先推荐这条路线因为它是四条路线里最可控的。调用链是显式的父子关系主Agent拥有绝对决策权子Agent的“主动性”被压到最低。对于客服工单、数据处理这类确定性强的场景这种控制模式非常适合出了问题也容易定位。但它的局限同样明显。子Agent永远处于被动响应状态无法主动汇报、无法跨级协作。你很难用它搭建对等的多Agent协作网络一旦需要多级嵌套主Agent的上下文会被反复撑大整个系统可能退化成一个超级Agent套壳。2.2 路线二通过MCP和A2A协议实现Agent互调协议派的人在回答跨Agent调用时会直接给你看两套东西MCP和A2A。MCP全称是Model Context Protocol它解决的原始问题是让Agent更方便地访问外部数据源和工具而不是Agent与Agent之间对话。但在实践中很多人会把子Agent的能力包成一个MCP Server主Agent作为MCP Client去发现和调用这些能力。这种方式的好处是工具接入标准化了换掉底层模型也不影响工具侧。A2A则是Agent到Agent之间的通信协议思路更接近“对等协作”。A2A引入了AgentCard的概念每个Agent公布自己的能力描述和接入地址其他Agent通过标准HTTP端点发现能力并提交任务。它支持流式事件、任务状态回传、人机协作确认等机制比单纯把Agent当函数调用更进一步。这两套协议前者偏工具标准化后者偏Agent间任务编排。目前生态还在早期A2A更是刚起步很多工具库还不完善我建议先观察再投入但它们的思路一定会影响后续Agent互调的方向。2.3 路线三用编排引擎把Agent编成流程图如果你对可靠性要求极高那么编排引擎路线更值得考虑。这派思路是把Agent当成工作流里的一个节点节点之间的调用关系由引擎在代码层面显式定义。LangGraph就是这类框架的代表它还支持子图嵌套本质上就是一种跨Agent调用的结构化管理方式。在这个模式下跨Agent调用不再依赖大模型临场“灵机一动”决定要不要调而是预先设计好拓扑第一步调AA的输出满足条件就走B不满足就走C。这样做的好处是执行路径可预测、可单测、可回放适合金融风控、运维巡检这类要求结果稳定的场景。代价就是灵活度相对低。工作流画得越细越像以前的BPM流程引擎Agent的“智能感”会被流程逻辑稀释。我在项目中遇到的情况是一旦任务形态频繁变化维护那张流程图的成本会比维护Agent本身还高。2.4 路线四事件驱动架构让Agent异步协作还有一类团队尤其是处理高并发或长耗时任务的会把Agent之间解耦成事件流。子Agent订阅消息队列里的任务处理完发一个完成事件下游Agent收到事件后再继续。中间可以接RabbitMQ、Kafka、Redis Stream这类中间件。这条路线最大的优点是完全松耦合Agent不需要知道谁在上游、谁在下游新增一个Agent只需订阅对应事件。适合批量数据处理、异步审批流、大规模任务分发等场景。它的弱点也相当突出链路难追踪调试靠补日志事件丢失或重复消费会把状态搞乱Agent之间的即时交互体验做不出来。更关键的是事件消息里塞自然语言结果还是结构化结果经常成为团队吵架的导火索。2.5 四条路线怎么选看这张对比表实现路线典型技术优点缺点适用场景工具函数化Function Calling实现简单、可控性强子Agent被动、多级嵌套易上下文爆炸客服、报表、小规模任务处理标准化协议MCP、A2A通用性好、生态潜力大协议尚未完全成熟、调试成本高跨团队共享工具、开放Agent市场编排引擎LangGraph、Dify Workflow路径稳定可审计、易测试灵活性低、流程图维护成本高风控、运维、流程固定业务事件驱动消息队列、流平台高度解耦、支持大规模异步难追踪、难调试、实时性弱批量任务、异步审批、数据流水线现实项目里这四种不是非此即彼我自己会混合使用外层采用编排引擎保证主干稳定内部节点用Function Calling调用具体Agent变化较快的调研类任务则走事件异步。3. 实操落地我手写三种方式实现跨Agent调用光说路线容易飘下面我拿一个具体场景把三种主流方式都实操一遍。我的项目背景是一个IT工单智能处理系统用户报障后主Agent要先调用日志分析子Agent看异常、再调用网络诊断子Agent排查链路、最后调用工单回复子Agent生成答复整个流程模拟跨Agent调用。代码做了简化但关键细节都保留。3.1 场景设定一个IT工单处理的多Agent系统先定义子Agent返回的数据结构。因为后面所有调用方式都依赖这个契约最好从一开始就把字段定清楚dataclass class AgentResult: agent_name: str status: str # success / failed / need_more_info summary: str details: dict confidence: float我强烈建议所有子Agent返回的summary控制在200个字符以内details里放结构化的机器可读信息。原因后面说这里先记住summary是给主Agent的LLM看的关键内容details是给程序逻辑用的。3.2 方式一Function Calling 封装子Agent10分钟搞定这是最快的方案。把子Agent注册成主模型的一个tool模型根据对话内容决定何时触发。以OpenAI风格的Function Calling为例先定义工具Schema{ type: function, function: { name: log_analysis_agent, description: 分析服务器日志并返回异常摘要适用于排障场景, parameters: { type: object, properties: { service_name: {type: string, description: 服务名}, time_range: {type: string, description: 时间范围如 2024-01-01T00:00:0008:00/2024-01-01T00:30:0008:00} }, required: [service_name] } } }然后主Agent侧通过一个简单的循环来执行调用import json from openai import OpenAI client OpenAI(api_keysk-xxx, base_urlhttp://localhost:8000/v1) messages [{role: user, content: 帮我查一下订单服务最近30分钟有没有异常}] tools [get_log_analysis_tool_schema()] for step in range(5): # 安全迭代上限 resp client.chat.completions.create( modelqwen-max, messagesmessages, toolstools, tool_choiceauto ) msg resp.choices[0].message messages.append(msg) if msg.tool_calls: for tc in msg.tool_calls: args json.loads(tc.function.arguments) tool_result run_sub_agent(tc.function.name, args) # 这里触发子Agent messages.append({ role: tool, tool_call_id: tc.id, content: json.dumps(tool_result, ensure_asciiFalse) }) else: print(最终回复, msg.content) break跑起来你会发现几个坑。第一run_sub_agent这一步必须设置超时和错误捕获否则子Agent内部报错会让整个主流程直接挂掉。第二工具描述里的参数说明非常关键写“service_name”而不是“服务名”会让模型误填更多噪声。第三我在循环里加了5次上限防止模型陷入工具调用死循环。3.3 方式二把子Agent独立成HTTP服务如果子Agent要独立部署或者需要被多个上游调用把它变成HTTP服务更合适。我这边用的是FastAPI加SSE流式返回子Agent一边执行一边吐结果主Agent实时就能看到进度比干等更友好。子Agent侧的核心接口可以这样写from fastapi import FastAPI from fastapi.responses import StreamingResponse from pydantic import BaseModel app FastAPI() class TaskRequest(BaseModel): task_id: str input_text: str trace_id: str app.post(/agent/log_analysis) async def run_log_analysis(req: TaskRequest): async def gen(): yield fdata: {json.dumps({type: status, message: started})}\n\n result await analyze_log(req.input_text) yield fdata: {json.dumps({type: result, result: result})}\n\n return StreamingResponse(gen(), media_typetext/event-stream)主Agent侧我用httpx做异步调用然后按SSE格式解析流式数据import httpx async def call_sub_agent(url: str, payload: dict, timeout: float 30.0): async with httpx.AsyncClient(timeouttimeout) as client: async with client.stream(POST, url, jsonpayload) as resp: result None async for line in resp.aiter_lines(): if line.startswith(data:): event json.loads(line[5:]) if event[type] result: result event[result] break return result这个方案最大的价值是部署解耦子Agent可以由不同团队维护技术栈也不要求完全一致。只要HTTP契约稳定谁调用谁都行。我实际踩过的坑是SSE连接容易因为代理超时断开所以在生产环境我会在前置网关把这类路径的超时时间调到60秒以上并且主Agent侧必须有超时重试。3.4 方式三把子Agent包装成MCP Server如果你更看重标准协议MCP是当前最值得投入的方向。用Python的mcp库把子Agent能力暴露为标准tool并不复杂。下面是经过我简化的关键代码from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app Server(log-analysis-agent) app.list_tools() async def list_tools(): return [ Tool( nameanalyze_service_logs, description分析指定服务在指定时间窗口内的日志返回异常片段和可能的根因, inputSchema{ type: object, properties: { service_name: {type: string}, time_range: {type: string} }, required: [service_name] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): result await analyze_main(arguments[service_name], arguments.get(time_range)) return [TextContent(typetext, textresult.summary)]在主Agent侧用MCP Client连接这个Server后就能像调用普通MCP工具一样调用子Agent能力。整个过程里你需要额外处理stdin/stdout的传输层我在小项目里直接用了官方的stdio_server部署上比较轻量。MCP的好处在于生态兼容性。同一个子Agent能力封装成MCP Server后理论上任何支持MCP的Agent客户端都可以接入不用为每个上游单独定制协议。不过MCP目前对长任务、流式输出的支持还在演进如果你的子Agent执行时间动辄几分钟用MCP做同步调用会等得很痛苦建议配合回调或任务ID轮询。3.5 上下文怎么传给子Agent这里有个容易翻车的细节跨Agent调用和普通API调用最大的区别在于入参和出参都是自然语言而自然语言的可压缩性非常差。把主Agent的整段历史对话全量传给子Agent看起来信息最完整实际会导致两个问题。第一是token成本爆炸。调用链路每深一层上下文就会指数级放大。第二是噪声干扰子Agent并不需要知道用户当初是怎么抱怨的它只需要知道服务名、时间范围和“帮我查异常”这个意图。我的做法是维护一个“上下文契约”每次跨Agent调用前由主Agent把会话压缩成一个结构化摘要只保留任务目标、关键参数、已完成步骤。这个摘要我控制在300~500字之间。如果你发现自己摘要完了子Agent还是答非所问往往是摘要时丢失了关键上下文这时候要回头检查摘要模板而不是简单加长摘要。另一个必须加的东西是trace_id。无论走HTTP、MCP还是Function Calling我要求所有子Agent在入参里带上trace_id在日志和返回结果里带上trace_id。没有链路ID多Agent系统的排障会变成灾难后面我会专门讲这个问题。4. 路线之争工具派、协议派、编排派谁会成为主流技术从来不单纯靠“谁更好”来胜出。跨Agent调用这轮路线之争本质上三方对Agent的定位、对智能的理解、对商业形态的押注都不同。4.1 工具派的核心逻辑Agent就是高级函数工具派认为Agent没必要拥有对等的“人格化协作”所有Agent本质上就是被更高层智能调用的工具函数。你给子Agent配了模型、记忆、工具但它依然是个黑盒服务调用方只需要定义输入输出。这个逻辑的好处是全局可控而且工程上完全复用现有微服务体系团队上手成本低。很多企业内部的Agent平台都在走这条路因为老板关心的是流程能不能被审计而不是Agent之间聊得开不开心。但这一派有个根本短板跨Agent能力的组合缺少涌现性。当主Agent把一切都当成函数调用时它很难容忍子Agent“临时冒出个新需求”。Agent之间无法形成真正的动态协商系统只能解决预定义好的问题。4.2 协议派的核心逻辑标准化才能规模化协议派说的是单点方案做得再好没有标准就没法规模化。只有MCP、A2A这类协议真正成熟Agent之间才能像今天的Web服务一样即插即用。到时候一个Agent可以动态发现另一个Agent的能力跨组织协作就像现在的API市场一样简单。我认可这条逻辑因为互联网行业已经用历史证明了标准协议对产业生态的巨大推动力。但协议派面前有座大山现在的Agent协议标准远不止一个A2A、MCP、各家私有协议都在抢地盘连Agent服务发现、任务描述格式这些基础问题都还没有统一。协议迭代速度能不能跟上模型能力迭代是很大的未知数。4.3 编排派的核心逻辑可靠性优先于智能编排派更接近传统企业软件工程师的思维方式Agent再智能也只是流程里的执行节点流程的正确性必须先被证明然后才谈智能优化。用LangGraph这类工具把调用关系显式画出来配上状态管理和人机确认节点系统是可靠可控的。这个路线的劣势是一旦任务场景变化快流程维护成本会反噬前面的“可靠性”。我自己在项目中就被流程版本迭代折腾过需求一变流程图改一版状态定义改一版回归测试还要再跑一遍整体节奏远没有纯动态调用灵活。4.4 我的判断这场路线之争短期内不会收敛原因有三个。第一Agent还远没到技术收敛期模型能力每隔几个月就会变化一次好比地基还没打好标准协议很难定下来。第二不同场景的需求差异太大金融风控要的是可控和审计创意工作流要的是灵活和探索工具派和编排派各占一头谁也无法覆盖全部。第三商业利益牵扯太重每个平台都想把自己定义为“其他Agent的调度中枢”都希望自己是规则的制定者。我给你的建议是不要赌未来哪个路线一定赢而是先选一个当前场景里最合适、并且留有抽象边界的方案落地。今天我可以在Function Calling之上加一层编排引擎明天也可以把它换成A2A客户端关键在调用层要隔离好。5. 跨Agent调用高频问题排查与避坑记录跨Agent调用方的好处之一是“什么都是玄学”坏处也一样。因为中间隔了模型推理排障思路和传统API完全不一样。下面这些问题都是我从真实项目中积累的照着排查效率会高很多。5.1 子Agent执行中途报错的排查步骤你在Agent开发里一定见过这类错误类似“agent execution terminated due to error.”出现在子Agent执行中但没有任何堆栈信息。我第一次遇到时也懵了半小时后来总结出标准排查顺序排查点操作方式检查trace_id日志确认传入子Agent的trace_id是否贯通日志里是否记录入参和出参复现单次调用用记录的入参在测试环境单独跑子Agent看能否稳定复现检查子Agent内部工具调用看子Agent在报错前调用了哪些工具是不是工具侧超时或鉴权失败检查上下文长度往往是在子Agent内部又塞了大量上下文触发上下文窗口限制检查下游依赖确认子Agent调用的模型API、数据库、外部系统在这个时间点是否正常有些框架会在错误信息里带内部error code先检索这个code的官方文档大概率能直接命中问题。如果是自定义Agent建议在子Agent外层捕获所有例外把标准错误映射成一个统一格式的错误结构返回给上游避免LLM自由发挥把错误描述得五花八门。5.2 调用链上的记忆丢失和上下文爆炸跨Agent调用的记忆问题非常陈旧但至今没有银弹。记忆分两块参数记忆和结论记忆。参数记忆指的是任务执行到一半时主Agent需要的中间参数必须显式保存不能只靠LLM自己记得。结论记忆则是指子Agent产出的结论要不要缓存在专门的记忆层避免下次再执行一遍。我踩过的坑是子Agent执行完返回summary时因为summary写得不够结构化主Agent后续引用时出现幻觉把结果记错了。所以前面我才强调summary要控制在200字内、details要结构化。这不仅是节省token更是为了保证后续链路引用时的精确度。如果你用的是LangGraph这类框架可以在节点级别定义状态schema把跨Agent传递的数据类型固定下来这样每个节点都能从state里读取必要字段记忆丢失问题会好很多。5.3 两个Agent互相调用导致死循环这个问题的触发方式比想象中容易Agent A在遇到不确定信息时会调用Agent B去确认Agent B发现自己也缺信息又回过头来问Agent A。两边模型一旦进入这种循环资源就被吃光日志里全是你调用我、我调用你的记录。我的处理手段有三种通常会一起用每个调用请求带上max_depth子Agent转发调用时深度1超过阈值直接拒绝并返回“请人工介入”。全局维护一个调用痕集合如果同一个trace_id下出现了重复的caller, callee对立即中断并告警。在提示词里写明“如果你没有足够信息直接说明缺少什么不要调用其他Agent来追问”。这三种手段可以覆盖大部分死循环场景。剩下的一小部分靠超时机制保底。5.4 跨Agent调用的权限边界怎么划单体Agent时代权限模型围绕用户设计。跨Agent调用之后问题变得微妙主Agent拥有用户授权它调用子Agent时子Agent是否天然继承这个授权我见过很多项目直接让子Agent用同一个服务账号结果任何一个子Agent被注入恶意指令都能拿到整个系统的读写权限。我的建议是采用最小权限继承。主Agent调用子Agent时在请求里显式声明授权范围比如“只读日志不写工单”子Agent执行前先校验权限声明不允许执行超出范围的工具。同时把敏感操作单独拆成“需要人工确认”的能力跨Agent调用触发敏感操作时返还给主Agent走人机确认节点。5.5 大家都在做链式追踪我先落地了一版简易方案正式的Agent可观测性平台可能需要不少成本但如果只是自用你可以用日志加trace_id快速搭一版。规则很简单每个跨Agent调用入口生成trace_id以日志形式记录每个Agent的入参、出参、耗时、错误码集中采集后按trace_id串联起来。我之前用纯文本日志都能排查80%的调用异常。记录格式长这样2025-06-01 10:22:33.123 [TRACE7ab01] [AGENTcoordinator] [CALLlog_analysis] inputserviceorder-service timelast30min 2025-06-01 10:22:34.001 [TRACE7ab01] [AGENTlog_analysis] [CALLquery_logs] ok latency230ms 2025-06-01 10:22:34.876 [TRACE7ab01] [AGENTlog_analysis] resultstatussuccess summaryfound 3 error entries后期如果规模上来了再去接专门的trace系统协议侧的trace_id可以无缝衔接。记住跨Agent调用排查第一原则没有链路ID就不要谈排障。6. 关于路线之争我目前的取舍与个人体会在跨Agent调用这个问题上我目前的做法是项目主干用编排引擎兜底保证关键业务路径的可控和可测试内部探索性环节用Function Calling做动态调用让模型有自由发挥的空间同时对MCP、A2A保持跟进但不会把核心链路押在尚未成熟的协议上。这个组合不一定最优但对我这种既要稳定又要灵活的小团队来说是试错成本最低的状态。我个人还有个小体会跨Agent调用能不能做好技术选型只占三成剩下七成都在约定规范。包括每个Agent的输入输出契约、错误码体系、上下文摘要格式、trace_id强制使用规则。这些规范看起来很土但它们才是多Agent系统能不能长期演进的基础。你先跟团队把Agent之间的契约文档写清楚再去纠结用MCP还是A2A会顺手很多。