ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多Agent协作实战:从Codex内部机制到A2A协议与Java SDK落地

多Agent协作实战:从Codex内部机制到A2A协议与Java SDK落地 1. 多 Agent 协作的真实痛点不是模型不够强而是协议没对齐很多人第一次接触多 Agent 协作脑子里浮现的画面是这样的几个 AI 各司其职一个负责拆解任务一个负责写代码一个负责审查最后一个负责汇总输出整个流程像流水线一样丝滑。但真正动手搭过的人都知道现实往往是一地鸡毛——Agent A 输出的格式 Agent B 解析不了Agent C 中途超时没人管最后汇总的那个 Agent 拿到的是一堆半成品还得靠人肉兜底。这个问题的根源其实不在模型能力上。你把 GPT-4、Claude、DeepSeek 这些模型单独拎出来每一个都能干活。但一旦让它们协作就变成了三个和尚没水喝。为什么因为多 Agent 协作的本质不是智能问题而是通信问题。每个 Agent 都是一个独立的进程、独立的上下文、独立的工具集它们之间怎么传消息、怎么约定格式、怎么处理失败、怎么保证顺序这些才是决定协作成败的关键。Codex 这个项目之所以值得拿出来聊就是因为它在内部机制上把Agent 之间怎么对话这件事想得比较清楚。而 A2AAgent-to-Agent这个概念则是把这套思路从单个产品内部抽象出来变成一套可以跨服务、跨语言、跨团队复用的企业级协议。关键词里出现的 SDK、Java其实指向的就是落地层面——你不可能永远用 Python 写 Agent企业里大量存量系统是 Java 的怎么让 Java 服务也能接入这套协作体系这才是真问题。这篇文章我打算分几层来拆先讲 Codex 内部多 Agent 协作到底靠什么机制在跑再讲 A2A 协议是怎么把这套机制标准化的然后落到 SDK 和 Java 生态的具体接入方式最后聊聊企业级场景下那些文档里不会写的坑。适合已经动手写过 Agent、或者正准备把多 Agent 方案往生产环境推的人看。如果你还停留在调个 API 让模型回句话的阶段建议先补一下工具调用和上下文管理的基础不然中间有些设计取舍会看不明白。2. Codex 内部的多 Agent 协作机制拆解2.1 为什么 Codex 不把所有活交给一个 Agent先问一个反直觉的问题既然单个模型能力已经够强Codex 为什么还要搞多 Agent答案藏在上下文窗口的物理限制和任务类型的异质性里。一个真实的编码任务往往包含这些环节理解需求、检索代码库、定位相关文件、生成补丁、跑测试、根据报错修正、最后生成说明。如果全塞给一个 Agent会发生什么首先代码库检索的结果可能几万 token直接把上下文撑爆其次生成补丁需要的是发散思维跑测试修正需要的是收敛思维两种模式在同一个上下文里会互相干扰最后一旦中间某步出错整个上下文都被污染回滚成本极高。Codex 的做法是按职责切分 Agent每个 Agent 只持有自己那部分上下文。检索 Agent 只关心哪些文件相关它的输出是一份文件路径列表而不是完整代码生成 Agent 拿到路径列表后自己去读文件它的上下文里只有真正要改的那几个文件测试 Agent 只关心跑出来的结果对不对它不需要知道补丁是怎么生成的。这种切分带来的直接好处是每个 Agent 的上下文都很干净token 消耗可控出错时影响面也被限制在单个环节内。这里有个容易踩的坑很多人切分 Agent 时按模型切比如这个用 GPT-4那个用 Claude。这是错的。切分维度应该是职责和上下文边界模型选择是切分完之后才考虑的事。同一个职责用哪个模型取决于这个环节对推理深度、速度、成本的要求而不是反过来。2.2 Agent 之间到底传了什么消息契约的设计多 Agent 协作最容易翻车的地方就是消息格式。A 输出一段自然语言B 期望的是 JSON中间就得加一层解析解析一失败整个链路就断了。Codex 内部的做法是强制结构化消息契约每个 Agent 的输入输出都必须是预定义 schema 的数据结构。具体来说消息契约包含几个必备字段字段作用常见错误task_id全局唯一任务标识用于串联整条链路用自增 ID分布式环境下会冲突from_agent发送方标识只写 Agent 名不写版本号升级后无法追溯to_agent接收方标识支持广播时格式不统一payload_type载荷类型决定接收方怎么解析用 MIME type 但没约定具体 schemapayload实际数据直接塞自然语言下游无法程序化处理trace_context链路追踪上下文缺失导致出问题后无法定位是哪一跳这张表里最容易被忽视的是trace_context。我见过太多团队Agent 跑得好好的一出问题就抓瞎——不知道是哪个 Agent 输出的、不知道经过了哪些跳、不知道每跳耗时多少。等到线上出故障只能靠加日志重新跑一遍复现效率极低。Codex 在这一点上做得很扎实每个消息都带完整的 trace 上下文任何一个环节出问题都能顺着链路倒查。payload_type的设计也有讲究。不要用text/json这种粗粒度类型而要定义到业务语义层面比如code_patch、test_result、file_list。这样接收方拿到消息后能直接根据类型选择对应的解析器和处理器而不是先尝试 JSON 解析、失败了再试别的。这种类型先行的设计在 Agent 数量超过三个之后收益会非常明显。2.3 失败处理多 Agent 协作里最被低估的环节单 Agent 场景下失败就是失败重试或者报错就完了。但多 Agent 场景下失败是有传播性的。检索 Agent 超时生成 Agent 拿不到文件列表要么空转要么报错生成 Agent 输出格式错误测试 Agent 解析失败整个链路卡死。如果不做处理一个环节的小问题会放大成整个任务的失败。Codex 内部对失败的处理分了三层第一层是Agent 内部重试。网络抖动、模型偶发输出异常这类问题在 Agent 内部自己重试不往上抛。重试策略通常是指数退避第一次等 1 秒第二次 2 秒第三次 4 秒最多三次。这里的关键是重试必须幂等也就是说同一个请求重试多次结果应该一致。如果 Agent 的操作有副作用比如写文件、调外部接口重试前必须先检查上一次是否已经成功。第二层是链路级降级。如果某个 Agent 重试后仍然失败不是直接让整个任务挂掉而是走降级路径。比如检索 Agent 挂了生成 Agent 可以退化成基于已有上下文直接生成虽然质量下降但任务能继续。降级策略需要在编排层预先定义好不能等出问题了临时想。第三层是熔断与隔离。如果某个 Agent 连续失败说明它可能本身出了问题比如依赖的服务挂了这时候要把它熔断掉避免拖垮整个链路。同时不同任务之间要隔离一个任务的失败不能影响其他任务。这一点在并发场景下尤其重要。实操心得失败处理逻辑一定要在本地能复现。我的做法是给每个 Agent 加一个故障注入开关可以人为让它超时、返回错误格式、直接抛异常然后在本地把各种失败路径都跑一遍。很多团队上线后才发现降级路径根本没走通就是因为从来没测过。2.4 上下文传递全量传还是增量传这是多 Agent 协作里一个经典的设计选择题。Agent A 处理完要把结果传给 Agent B是传完整的上下文还是只传增量全量传的好处是 B 拿到的是完整信息不依赖 A 的处理逻辑A 改了也不影响 B。坏处是 token 消耗大而且随着链路变长上下文会像滚雪球一样越来越大。增量传的好处是轻量坏处是 B 强依赖 A 的输出格式A 一改 B 就崩。Codex 的选择是混合模式核心的、稳定的信息全量传比如任务描述、原始需求中间过程的、易变的信息增量传比如检索结果、临时补丁。判断标准是这个信息如果丢了下游能不能自己重新获取能就增量传不能就全量传。举个具体例子。任务描述给用户模块加一个手机号登录功能这个信息全量传因为下游 Agent 都需要知道最终目标是什么。检索 Agent 找到的user_service.java 第 120 行有登录逻辑这个信息增量传因为生成 Agent 拿到路径后可以自己去读文件不需要把整个文件内容塞进消息里。这种设计还有个隐藏好处它天然支持 Agent 的独立演进。检索 Agent 换了实现只要输出的还是文件路径列表下游完全无感。如果全量传上下文检索 Agent 一改所有下游都得跟着改维护成本会指数级上升。3. A2A 协议把 Codex 的内部经验变成企业标准3.1 A2A 要解决的核心问题跨边界协作Codex 内部的多 Agent 协作跑得再好那也是一个产品内部的事。企业里的真实情况是用户服务团队用 Java 写了一套 Agent风控团队用 Python 写了一套数据团队用 Go 写了一套这三套东西要协作怎么办如果没有统一协议结果就是点对点集成。用户服务和风控集成一次用户服务和数据集成一次风控和数据再集成一次。三个团队两两集成就是三条链路四个团队就是六条五个团队就是十条。这是组合爆炸维护成本根本扛不住。A2A 协议的价值就在这里它定义了一套所有 Agent 都必须遵守的通信规范任何两个 Agent 之间都能直接对话不需要为每一对组合单独开发适配层。这跟微服务里服务发现和 RPC 协议的作用是一样的——把 N×N 的集成问题变成 N 个接入协议的问题。A2A 的核心抽象是Agent Card。每个 Agent 对外暴露一张卡片描述自己的能力、输入输出格式、调用方式、认证要求。其他 Agent 要调用它先读卡片然后按卡片描述的方式发请求。这跟 OpenAPI/Swagger 的思路很像区别在于 A2A 是专门为 Agent 场景设计的考虑了流式输出、长任务、多轮交互这些 Agent 特有的需求。3.2 Agent Card 里到底该写什么Agent Card 是 A2A 的入口写得好不好直接决定了这个 Agent 能不能被别的 Agent 正确调用。我见过很多团队把 Agent Card 写成了一段自然语言描述比如这个 Agent 可以帮你处理订单相关的问题。这种卡片人看着还行机器根本没法用。一张合格的 Agent Card 至少包含这几部分能力声明部分要明确列出这个 Agent 能处理哪些任务类型每种任务类型的输入 schema 和输出 schema 是什么。schema 要用标准的 JSON Schema 描述不要用自然语言。比如输入是一个订单 ID 字符串这种描述机器解析不了要写成{type: object, properties: {order_id: {type: string, pattern: ^ORD[0-9]{10}$}}}。调用约束部分要说明这个 Agent 的调用频率限制、超时时间、是否支持流式返回、是否支持取消。这些信息下游 Agent 在编排时要用到。比如一个 Agent 声明超时是 30 秒编排层就知道不能给它派超过 30 秒的任务或者要给它配异步回调。认证方式部分要说明调用这个 Agent 需要什么凭证。A2A 支持多种认证方式常见的有 API Key、OAuth2、mTLS。企业环境下通常用 OAuth2因为可以细粒度控制权限而且 token 可以过期安全性更好。版本信息部分要写清楚当前版本号和兼容性策略。Agent 升级是常态如果下游不知道版本变化很容易出问题。建议采用语义化版本主版本号变化表示不兼容次版本号变化表示新增功能修订号变化表示 bug 修复。一个实战经验Agent Card 最好支持动态获取而不是写死在调用方代码里。我见过团队把下游 Agent 的地址和 schema 硬编码在自己代码里结果下游一升级上游全挂。正确做法是每次调用前先拉一次卡片或者用带缓存的卡片注册中心缓存过期时间设短一点比如 5 分钟。3.3 同步、异步、流式三种交互模式怎么选A2A 协议支持三种交互模式选错了会直接影响用户体验和系统稳定性。同步模式最简单调用方发请求等着接收方处理完返回结果。适合处理时间短秒级、结果确定的场景比如查一下这个订单的状态。但同步模式有个致命问题如果处理时间超过 HTTP 超时通常 30 秒到 60 秒连接就断了调用方拿不到结果接收方还在傻傻地处理。异步模式是调用方发请求接收方立刻返回一个 task_id然后调用方拿着 task_id 去轮询或者等回调。适合处理时间长分钟级甚至小时级的场景比如生成一份完整的代码补丁并跑完测试。异步模式的关键是状态管理接收方要维护每个 task 的状态pending、running、success、failed调用方要能查到这些状态。流式模式是接收方一边处理一边往回推结果适合需要实时反馈的场景比如边生成代码边展示。流式模式实现复杂度最高因为要处理连接中断、部分结果、顺序保证这些问题。但如果你的场景是用户等着看结果流式模式的体验是最好的。模式适用场景超时风险实现复杂度同步秒级、结果确定高低异步分钟级以上低中流式需要实时反馈中高选择原则很简单先看处理时间再看用户体验要求。处理时间超过 10 秒的一律走异步需要实时展示的走流式剩下的走同步。不要为了省事全用同步线上超时会教你做人。3.4 A2A 和 MCP 的关系别搞混了这里必须澄清一个常见的混淆。A2A 和 MCPModel Context Protocol经常被放在一起提但它们解决的是不同层面的问题。MCP 解决的是Agent 和工具之间的通信。一个 Agent 要用某个工具比如查数据库、调 API通过 MCP 协议来调用。MCP 的抽象是资源和工具Agent 是主动方工具是被动方。A2A 解决的是Agent 和 Agent 之间的通信。两个 Agent 是对等关系可以互相调用、互相协作。A2A 的抽象是Agent Card和任务双方都可以是主动方。打个比方MCP 像是人用工具A2A 像是人跟人协作。一个 Agent 内部可以用 MCP 调各种工具对外用 A2A 跟其他 Agent 协作。两者是互补的不是替代的。实际项目里经常是一个 Agent 同时用 MCP 和 A2A内部用 MCP 调数据库、调外部 API对外用 A2A 暴露自己的能力给其他 Agent 调用。理解这个分层架构设计时就不会乱。4. SDK 与 Java 生态的接入实践4.1 为什么 Java 接入是块硬骨头关键词里出现 Java 和 SDK不是偶然。企业里大量存量系统是 Java 写的这些系统要接入多 Agent 协作体系绕不开 Java SDK。但 Java 接入 A2A 有几个天然的难点。第一是异步模型不匹配。A2A 大量使用异步和流式交互而 Java 传统的 Servlet 模型是同步阻塞的。虽然现在有 WebFlux、CompletableFuture 这些异步工具但存量代码大量是同步的改造起来工作量大。第二是序列化生态差异。A2A 的消息格式基于 JSON而 Java 生态里大量用 POJO Jackson。JSON Schema 到 Java 类的映射虽然有工具但复杂 schema比如嵌套的 oneOf、anyOf映射起来很麻烦容易出错。第三是依赖管理复杂。Java 项目依赖多SDK 引入后可能跟现有依赖冲突尤其是 Jackson、Netty 这些基础库的版本冲突排查起来很痛苦。4.2 Java SDK 接入的最小可用路径如果你现在就要在 Java 项目里接入 A2A我建议按这个路径走不要一上来就追求完整功能。第一步引入 SDK 并跑通 Agent Card 解析。先把 SDK 依赖加进去写一个最简单的程序拉取一个远端 Agent 的卡片并打印出来。这一步的目的是验证网络通、依赖没冲突、序列化正常。很多问题在这一步就能暴露出来比如 Jackson 版本冲突导致反序列化失败。dependency groupIdcom.example.a2a/groupId artifactIda2a-java-sdk/artifactId version1.0.0/version /dependency第二步实现一个最简单的同步调用。找一个支持同步模式的 Agent发一个请求拿到结果。这一步验证的是消息契约能不能对上——你发的 payload 格式对不对对方返回的格式你能不能解析。A2AClient client A2AClient.builder() .agentCardUrl(https://agent.example.com/.well-known/agent-card) .build(); TaskRequest request TaskRequest.builder() .taskId(UUID.randomUUID().toString()) .payloadType(order_query) .payload(Map.of(order_id, ORD1234567890)) .build(); TaskResponse response client.invoke(request); System.out.println(response.getPayload());第三步接入异步模式。同步跑通后改成异步。异步模式的关键是状态查询和回调处理。SDK 通常会提供一个 TaskHandle你可以用它查状态、取消任务、注册回调。TaskHandle handle client.invokeAsync(request); handle.onComplete(resp - { // 处理完成 }); handle.onError(err - { // 处理失败 });第四步接入流式模式。流式模式用 Reactor 或者 RxJava 的流式 APISDK 通常会返回一个 Flux 或者 Observable你订阅它就能拿到流式结果。client.invokeStreaming(request) .subscribe( chunk - System.out.println(收到片段: chunk), error - System.err.println(出错: error), () - System.out.println(流结束) );这四步走完基本接入就完成了。不要跳过任何一步每一步都在验证不同的东西跳步会导致问题堆积到最后一起爆发排查困难。4.3 存量 Java 系统改造的三种策略存量系统接入 A2A不可能推倒重来。根据改造范围有三种策略可选。旁路策略不动存量代码在旁边新起一个 A2A 适配层适配层负责跟 Agent 通信存量系统通过内部接口调适配层。这种策略改造量最小风险最低适合存量系统稳定、不想大动的情况。缺点是适配层和存量系统之间多了一次调用有性能损耗。嵌入策略在存量系统内部引入 SDK直接在业务代码里调 Agent。这种策略性能最好没有额外跳转。缺点是改造侵入性强SDK 的依赖可能跟存量依赖冲突而且业务代码里混入 Agent 调用逻辑可维护性下降。网关策略在系统入口处加一个 A2A 网关所有 Agent 调用都经过网关。网关负责协议转换、认证、限流、监控。这种策略适合 Agent 调用量大、需要统一治理的场景。缺点是网关本身成为单点需要高可用设计。策略改造量性能侵入性适用场景旁路小中低存量稳定、不想大动嵌入中高高新系统、调用频繁网关大中低调用量大、需统一治理我的建议是新系统用嵌入老系统用旁路调用量上来之后再考虑网关。不要一上来就搞网关网关的复杂度很高没有足够的调用量支撑投入产出比不划算。4.4 Java 侧的几个具体坑坑一Jackson 版本冲突。A2A SDK 通常依赖特定版本的 Jackson如果你的项目里已经有别的 Jackson 版本很容易冲突。表现是反序列化时字段丢失或者报错。解决办法是用 Maven 的 dependencyManagement 统一 Jackson 版本或者用 shade 插件把 SDK 的依赖打包进去隔离。坑二超时配置不生效。Java 的 HTTP 客户端超时配置有好几层连接超时、读超时、写超时还有连接池的超时。A2A 调用超时往往是某一层没配到。建议把每一层都显式配置并且配置值要跟 A2A 协议里声明的超时对齐。坑三线程池打满。异步调用如果用默认的 ForkJoinPool高并发下容易打满。建议给 A2A 调用单独配一个线程池核心线程数和最大线程数根据实际 QPS 和单次调用耗时来算。公式是线程数 QPS × 平均耗时秒。比如 QPS 是 100平均耗时 0.5 秒那线程数至少要 50。坑四日志里泄露敏感信息。A2A 消息里可能包含业务数据如果日志把完整 payload 打出来可能泄露敏感信息。建议在日志里对 payload 做脱敏只记录类型和大小不记录具体内容。需要排查问题时再通过 trace_id 去专门的链路追踪系统里查。实操心得Java 接入 A2A最花时间的不是写代码而是排查依赖冲突和配置问题。我的做法是先在一个干净的 Spring Boot 空项目里把 SDK 跑通确认没问题后再往存量项目里迁。这样能把 SDK 本身的问题和存量项目的问题分开排查效率高很多。5. 企业级 A2A 服务的落地要点5.1 服务发现Agent 多了之后怎么找单机或者小规模场景下Agent 地址可以写死在配置里。但企业级场景下Agent 数量可能几十上百个而且经常上下线写死配置根本维护不过来。这时候就需要服务发现。A2A 的服务发现有两种模式。一种是中心化注册所有 Agent 启动时向注册中心注册自己的卡片调用方从注册中心查。这种模式简单直接但注册中心是单点需要高可用。另一种是去中心化发现通过 DNS 或者约定好的路径比如/.well-known/agent-card直接发现。这种模式没有单点但发现效率低而且不好做权限控制。企业环境下我推荐中心化注册但注册中心要做成集群并且要有健康检查机制——Agent 下线后要能及时从注册中心摘除不然调用方会一直往挂掉的 Agent 发请求。注册中心里存的卡片要做版本管理。Agent 升级后新卡片要能平滑替换旧卡片同时保留旧版本一段时间让还在用旧版本的调用方有时间迁移。这个双版本共存的窗口期通常设一到两周。5.2 可观测性出问题了怎么定位多 Agent 协作的可观测性比单服务复杂得多。一个用户请求可能经过五六个 Agent每个 Agent 又调了若干工具任何一环出问题都会导致最终失败。没有完善的可观测性排查问题就是大海捞针。可观测性要抓三个东西日志、指标、链路。日志方面每个 Agent 的每条消息都要带 trace_id这样能把一次请求在所有 Agent 上的日志串起来。日志级别要分级正常流程用 INFO异常用 WARN 或 ERROR调试信息用 DEBUG。生产环境默认 INFO需要排查时动态调成 DEBUG不要一直开着 DEBUG日志量太大会拖垮系统。指标方面每个 Agent 要暴露几个核心指标调用次数、成功率、平均耗时、P99 耗时、错误分类。这些指标要能按 Agent、按任务类型、按调用方维度聚合。指标异常时要有告警比如成功率低于 99% 持续 5 分钟就告警。链路方面用 OpenTelemetry 这类标准做分布式追踪。每个 Agent 的每次调用都是一个 spanspan 之间通过 trace_id 和 parent_span_id 关联。这样在追踪系统里能看到完整的调用链路哪一跳慢、哪一跳错一目了然。一个容易被忽视的点Agent 的输入输出要采样记录。全量记录存储成本太高但完全不记录出问题时又没法复现。建议按 1% 到 5% 的比例采样同时对所有失败的请求 100% 记录。这样既能控制成本又能保证问题可追溯。5.3 安全边界Agent 之间的信任怎么建多 Agent 协作引入了一个新的安全问题Agent 之间的信任。Agent A 调用 Agent BB 怎么知道 A 是真的 A而不是冒充的A 怎么知道 B 返回的结果没被篡改认证方面A2A 支持多种机制。企业内部 Agent 之间通常用 mTLS双向 TLS每个 Agent 持有自己的证书通信时互相验证。跨企业场景用 OAuth2 更合适通过授权服务器颁发 tokentoken 里带权限范围。授权方面要遵循最小权限原则。Agent A 只需要调用 Agent B 的某个能力就只给它这个能力的权限不要给全量权限。权限的粒度要细到任务类型级别比如允许调用订单查询不允许调用订单修改。数据安全方面Agent 之间传递的数据要分类。公开数据可以直接传敏感数据要脱敏或者加密。特别是涉及用户隐私的数据传递前必须脱敏而且要在 Agent Card 里声明这个 Agent 处理敏感数据调用方要额外授权才能调。审计方面所有 Agent 之间的调用都要留审计日志记录谁在什么时候调了谁、传了什么类型的数据、结果如何。审计日志要单独存储不能跟业务日志混在一起而且要防篡改。5.4 性能与成本多 Agent 协作的隐性开销多 Agent 协作的性能开销比单 Agent 高不少。每多一跳就多一次网络往返、多一次序列化反序列化、多一次上下文切换。链路长了之后这些开销累积起来很可观。优化性能有几个方向。减少跳数是最直接的能合并的 Agent 就合并不要为了架构清晰而过度拆分。并行化是另一个方向没有依赖关系的 Agent 调用可以并行比如检索文件列表和检索历史记录可以同时做。缓存也很重要Agent Card、不常变的配置、重复的查询结果都可以缓存。成本方面多 Agent 协作的 token 消耗是单 Agent 的好几倍。每个 Agent 都有自己的系统提示词都要处理上下文这些都要花钱。控制成本的关键是精简每个 Agent 的上下文只给它真正需要的信息不要把整个任务历史都塞进去。另外不同环节可以用不同档次的模型检索、格式化这类简单任务用便宜模型推理、生成这类复杂任务用贵模型。优化方向具体手段预期收益减少跳数合并职责相近的 Agent延迟降低 20%-40%并行化无依赖调用并行执行延迟降低 30%-50%缓存卡片、配置、查询结果缓存延迟降低 10%-30%上下文精简只传必要信息token 成本降低 40%-60%模型分级简单任务用便宜模型成本降低 30%-50%这些优化不是一次做完的建议先上线跑起来拿到真实的性能数据和成本数据再针对性优化。没有数据支撑的优化往往是瞎优化。6. 从 Codex 到 A2A我踩过的那些坑6.1 消息格式约定不清导致的连环故障早期做多 Agent 协作时我犯过一个典型错误Agent 之间的消息格式没有严格约定靠大家自觉。结果检索 Agent 输出的文件列表有时候是 JSON 数组有时候是逗号分隔的字符串有时候干脆是一段自然语言描述。生成 Agent 拿到这些五花八门的格式解析逻辑写了一堆 if-else还是经常解析失败。更麻烦的是这种问题不是必现的。模型输出有随机性同样的提示词这次输出 JSON下次可能就输出自然语言。测试环境跑十次都正常生产环境跑一百次就出问题。排查的时候因为日志里只记了解析失败没记原始输出根本不知道失败时模型到底输出了什么。后来我的做法是所有 Agent 的输出必须经过 schema 校验校验不通过就重试重试三次还不行就报错。同时在日志里记录原始输出方便排查。这个改动之后格式问题基本消失了。代价是增加了一次校验和可能的重试但相比格式错误导致的链路失败这个代价完全值得。6.2 超时设置不当引发的雪崩有一次线上故障起因是一个 Agent 的响应变慢从平均 2 秒变成了 10 秒。上游 Agent 的超时设的是 30 秒所以没触发超时但上游 Agent 的线程被这个慢请求占着处理不了新请求。新请求排队队列满了之后开始拒绝拒绝又导致上游重试重试进一步加剧拥堵。最后整个链路雪崩。事后复盘问题出在超时设置没有分层。上游超时 30 秒下游超时也是 30 秒中间没有任何缓冲。正确的做法是上游超时要大于下游超时之和给下游留出重试和降级的时间。比如下游单个 Agent 超时 5 秒重试 2 次那上游超时至少要设 20 秒。另外超时时间要跟业务预期对齐。用户能接受等 3 秒你就不要把超时设成 30 秒。超时设太长用户等半天才看到失败体验很差超时设太短正常请求也被掐断成功率下降。这个值要通过压测和真实数据分析来确定不能拍脑袋。6.3 Agent 版本升级引发的兼容性问题Agent 升级是常态但升级引发的兼容性问题很隐蔽。有一次我们升级了检索 Agent把输出格式从文件路径列表改成了文件路径 相关度分数的对象列表。检索 Agent 自己测试没问题但下游生成 Agent 还在按老格式解析拿到对象列表后直接报错。问题在于我们升级时没有考虑下游。检索 Agent 的 Agent Card 里输出 schema 改了但版本号没变下游拉卡片时拿到的还是老版本号以为没变化就没更新解析逻辑。后来我们定了规矩任何 Agent 的输出 schema 变化必须升主版本号并且新旧版本要并行运行至少一周。并行期间调用方可以按老版本号调老版本按新版本号调新版本。一周后老版本下线调用方必须完成迁移。这个规矩执行下来再没出过升级导致的兼容性问题。6.4 监控缺失导致的问题发现滞后最惨的一次是一个 Agent 的成功率从 99% 掉到了 80%但我们三天后才发现。原因是那个 Agent 的调用量不大每天就几百次失败几十次在总请求量里占比很小没有触发告警。直到有用户投诉我们才去查发现已经持续了三天。这件事之后我们调整了告警策略不只看全局成功率还要看每个 Agent 的成功率。哪怕调用量小只要成功率低于阈值就告警。同时加了环比告警今天跟昨天比成功率下降超过 5% 就告警不管绝对值是多少。监控这东西平时觉得没用出问题时才知道重要。我的建议是Agent 上线前监控和告警必须先配好不要等出了问题再补。配监控的成本远低于问题发现滞后造成的损失。6.5 上下文污染导致的输出质量下降多 Agent 协作里上下文污染是个隐蔽但影响很大的问题。所谓上下文污染就是 Agent 的上下文里混入了不相关的信息导致模型注意力被分散输出质量下降。典型场景是Agent A 处理任务时把中间过程的调试信息、失败的尝试、无关的日志都塞进了上下文传给 Agent B。B 拿到这一大堆信息真正有用的可能只占 10%剩下 90% 都是噪音。模型在处理时会被这些噪音干扰输出的质量明显下降。解决办法是上下文要精简只传必要信息。Agent 之间传递的应该是结论而不是过程。A 处理完输出的是最终结果不是它怎么得出这个结果的。如果 B 需要知道过程那说明 A 和 B 的职责划分有问题应该重新设计。我做过一个对比测试同样的任务上下文精简后输出质量提升了大概 30%token 消耗降低了 50%。这个收益非常可观而且改动成本很低就是调整一下 Agent 之间传什么。7. 多 Agent 协作的边界与选型建议7.1 什么场景适合多 Agent什么场景不适合多 Agent 不是银弹用错了场景复杂度上去了效果反而更差。适合多 Agent 的场景有几个特征任务可以清晰拆分成多个阶段每个阶段需要不同的能力或上下文阶段之间有明确的输入输出契约。比如需求分析 → 代码生成 → 测试验证这种流水线就很适合。不适合的场景也很明显任务本身很简单一个 Agent 就能搞定硬拆成多个只会增加开销任务各阶段高度耦合拆开后上下文传递成本比收益还高任务对延迟极度敏感多一跳都不可接受。判断标准可以简化为一个问题拆成多 Agent 后每个 Agent 的上下文是不是更干净了如果是那拆分有价值如果拆完后每个 Agent 还是要拿到全量上下文才能干活那拆分就是白拆。7.2 自研还是用现成框架多 Agent 协作框架现在不少自研还是用现成的取决于你的需求有多特殊。如果需求是标准的流水线式协作用现成框架能省很多事协议、SDK、监控这些基础设施框架都帮你搞定了。如果需求很特殊比如有独特的调度逻辑、特殊的消息格式、极致的性能要求那自研可能更合适。我的建议是先用现成框架跑通遇到框架解决不了的问题再考虑自研。很多团队一上来就自研结果发现框架里已经解决的问题自己又踩了一遍坑。先用框架能快速验证方案可行性也能帮你理清真正的需求是什么。7.3 团队能力要求多 Agent 协作对团队的能力要求比单 Agent 高不少。除了模型调用的基本功还需要分布式系统的知识——服务发现、负载均衡、熔断降级、分布式追踪这些都得懂。还需要协议设计的知识——怎么定义消息契约、怎么保证兼容性、怎么做版本管理。如果团队里没有分布式系统经验的人建议先从简单的两三个 Agent 协作做起把基础打牢再扩展。不要一上来就搞十几个 Agent 的大系统复杂度会失控。8. 一些实用的排查技巧和工具8.1 本地复现多 Agent 链路的方法多 Agent 链路出问题最难的是本地复现。生产环境是分布式的本地怎么模拟我的做法是用 Docker Compose 把整条链路拉到本地每个 Agent 一个容器通过内部网络通信。这样能在本地完整复现生产环境的链路排查效率高很多。Docker Compose 里要配好每个 Agent 的依赖包括注册中心、消息队列、数据库。启动后用真实的请求打一遍看哪一跳出问题。因为环境跟生产一致复现出来的问题基本就是生产的问题。8.2 用 trace_id 串联全链路日志trace_id 是多 Agent 排查的生命线。每个请求进来时生成一个 trace_id所有 Agent 处理时都带上这个 trace_id日志里也记录。排查时用 trace_id 在所有 Agent 的日志里搜就能把一次请求的完整链路还原出来。实现上trace_id 要放在消息的 header 里而不是 payload 里。因为 payload 是业务数据header 是元数据trace_id 属于元数据。用 OpenTelemetry 的话trace_id 会自动注入和传播不用手动处理。8.3 压测上线前必须做的事多 Agent 链路的上线前压测比单服务重要得多。因为链路长任何一环成为瓶颈整个链路都会受影响。压测要覆盖几个场景正常负载、峰值负载、单 Agent 故障、网络延迟。压测时重点看几个指标端到端延迟、每个 Agent 的延迟、成功率、错误分布。如果某个 Agent 的延迟明显高于其他那它就是瓶颈要优先优化。如果单 Agent 故障时整个链路挂掉说明降级策略没做好要补。压测数据要保留作为容量规划的基线。后续扩容、优化都跟这个基线对比看有没有改善。9. 写在最后的一些个人体会多 Agent 协作这件事技术上的难点其实都能解决真正难的是克制。克制住多加一个 Agent 会不会更好的冲动克制住这个功能也拆出去的欲望。每多一个 Agent就多一份通信开销、多一个故障点、多一份维护成本。能用一个 Agent 解决的就不要用两个。A2A 协议的价值不在于它定义了多少字段、支持了多少模式而在于它让不同团队、不同语言、不同架构的 Agent 能对话。这个能对话的背后是大量的约定和妥协。用 A2A 的时候要理解这些约定背后的原因而不是机械地照搬。Java 接入这块我的体会是不要追求一步到位。先把最简单的同步调用跑通再逐步加异步、流式、监控、安全。每一步都验证通过再走下一步比一次性全上然后花几周排查问题要快得多。最后说个小事。我见过很多团队多 Agent 系统搭得很漂亮但没人愿意维护。原因是复杂度太高除了原作者其他人看不懂。所以设计的时候可维护性要放在很重要的位置。消息格式要清晰Agent 职责要单一文档要写清楚每个 Agent 干什么、怎么调、出问题怎么办。这些东西平时看不出价值等人换了一茬之后就知道有多重要了。
返回列表