1. 为什么多 Agent 系统需要一道“闸门”
如果你只是搭过一两个单 Agent 的 Demo,可能很难理解我下面这段话的含金量:单 Agent 跑飞了,你重试几次基本就回来了;但一旦多个 Agent 开始协作,你会立刻发现,系统里最大的不稳定因子不是网络、不是代码逻辑,而是每一个 Agent 背后那个号称“生成式”的模型接口。我今年做了好几个多 Agent 项目,从意图识别串联信息抽取,再到生成、校验、改写这样的典型链路,最深的感受就是:多 Agent 系统的可靠性,不是靠某个 Agent 变强,而是要靠一层“闸门”来兜底。
这篇要聊的 AgentGate,正是这样一个专门为多 Agent 系统设计的可靠性框架。它不优化模型,不改变 Prompt,也不试图让某个 Agent 变聪明,它只做一件事:把你的 Agent 编排过程变得可控、可观测、可恢复。适合谁看?正在从单 Agent 迈向多 Agent 编排的开发者、被线上 Agent 调用折腾得焦头烂额的后端团队,以及所有想少踩一点生产环境坑的人。
1.1 单 Agent 的错误还能忍,多 Agent 的错误会传染
先讲一个很容易被忽视的差异。单 Agent 应用,比如一个简单的聊天机器人,它只有一个入口、一个出口,运行失败了重新生成一次就行。就算模型偶尔抽风,返回一个格式混乱的结果,影响面也就是当前这一次对话,用户刷新一下就能重来。
但多 Agent 系统不是这样。Agent A 的输出会作为 Agent B 的输入,Agent B 的结果又要喂给 Agent C。这就成了一条流水线。流水线上只要有一个环节给出“看起来正常但内容错得离谱”的结果,后面所有 Agent 都会基于这份垃圾继续加工,最后产出的东西就是垃圾中的垃圾。
更麻烦的是错误会传染。我之前在做一个智能客服工单系统的时候,意图识别 Agent 有一次把“退款申请”识别成了“产品咨询”,下游的信息抽取 Agent 理所当然地没抽出任何订单号,再下游的回复生成 Agent 没有订单号就开始写“抱歉,我们暂时没有查到您的订单信息”。整个链路看似走完了,每一步都成功了,但最终结果是错的。单 Agent 时代你只需要关心“这次调用成没成功”,多 Agent 时代你要关心的是“整条链路的结果对不对”。
这就是 AgentGate 要解决的第一个问题:链路级的结果校验,而不是单点调用的成功判断。
1.2 多 Agent 可靠性的四座大山
把我在多个项目里踩过的坑归纳一下,多 Agent 系统的可靠性问题基本集中在四类。
第一类是模型调用本身的不稳定。LLM 接口的响应时间波动极大,慢的时候能比平时慢十倍,还时不时给你抛个限流错误。多 Agent 系统里一次任务要连续调用好几个模型,任何一个接口抖动都会被放大成整体任务的延迟飙升。
第二类是 Agent 之间协作的死锁与循环。Agent A 等着 Agent B 的结果,Agent B 又在等 Agent C,Agent C 居然回头等 Agent A,这种循环依赖在配置复杂业务流的时候特别容易出现。更隐蔽的是死锁不报错,所有任务都停在“等待中”,你得盯半天才意识到出了什么问题。
第三类是可观测性差。我之前排查过一个线上问题,三个 Agent 组成的流水线总共消耗了多少 Token、每一步花了多久、每一步输出了什么,几乎无迹可查。出问题的时候只能靠猜,而“靠猜”在生产环境是最贵的排障方式。
第四类是下游系统负载问题。多 Agent 系统通常要对接数据库、第三方 API、内部服务。一个 Agent 的重试风暴会引发下游接口被反复调用,下游一挂,整个系统雪崩。这不是模型问题,是工程治理问题。
1.3 AgentGate 的定位:不优化模型,而是治理编排
明白了上面的痛点,你就知道 AgentGate 为什么叫“可靠性框架”而不是“性能优化框架”了。它的核心思路是:模型是黑盒,我们改变不了它的随机性,但我们可以改变系统对随机性的容忍度。
AgentGate 站在 Agent 之上、业务之下,像一个调度台一样管理每一次 Agent 调用。它负责确认这个 Agent 的输出是否合规,负责在失败时重试还是降级,负责在流量异常时熔断,负责把每一次调用的轨迹记录下来。一句话:让不可靠的 LLM 调用,变成你系统里一个可控、可观测、可恢复的步骤。
这个定位带来的直接好处是,业务代码不需要感知模型层面的波动,AgentGate 把那些脏活、累活、容易错的事全接走了。你写的 Agent 只需要专注做一件事:把自己的任务做好,剩下的交给闸门。
2. AgentGate 整体设计:编排、韧性与治理三层拆解
AgentGate 的整体架构可以分成三层:编排层、韧性层、治理层。这三层各司其职,有点像机场的塔台、跑道和维护团队的关系。
2.1 编排层:用 DAG 把任务组织成可执行拓扑
编排层的核心是任务拓扑。多 Agent 协作本质上是一个有依赖关系的工作流,AgentGate 用有向无环图(DAG)来描述这套依赖关系,每个节点是一个 Agent 任务,每条边是一次数据传递。
我最早试图用“链表式”的思路编排 Agent,一个接一个地串下去,结果发现业务一复杂就根本维护不了。有了 DAG,你就能清楚地表达哪些步骤可以并发执行,哪些步骤必须等前置依赖完成。比如一个内容生成系统里,“查资料”“查历史记录”“分析用户偏好”三个 Agent 彼此独立,可以并行跑,跑完统一汇总给“内容生成” Agent。如果串行执行,总耗时就是三者之和,并行之后总耗时只有最慢的那个。
AgentGate 的编排层还会为每个节点维护状态机:pending(等待执行)、running(执行中)、succeeded(成功)、failed(失败)、skipped(跳过)。这个状态机看起来简单,但它是所有可靠性能力的基础。因为只有清楚地知道每个节点处于什么状态,才能判断下一步是重试、是跳过、是熔断还是整体终止。
2.2 韧性层:让失败成为一个可以响应的“事件”
韧性层是 AgentGate 最核心的部分。它包含超时控制、重试、退避、熔断、降级这五类能力。很多人会把这五个词混为一谈,其实它们解决的问题完全不同。
超时控制回答的是:这个 Agent 最多等它多久。重试回答的是:它失败了要不要再来一次。退避回答的是:重试时要不要等一会儿、等多久。熔断回答的是:这个 Agent 持续失败,是不是干脆别调它了。降级回答的是:它不行了,有没有 Plan B。
这五者的组合,构成了 AgentGate 对失败的核心处理逻辑。你可以把它理解成一层包裹在 Agent 外部的“防护膜”,每个 Agent 调用都会先经过这层防护膜,拿到结果后再做合法性校验,整个过程对业务透明。
2.3 治理层:配额、观测与审计
治理层解决的是“看得见”和“花得值”的问题。AgentGate 提供了统一的调用事件总线,每一次 Agent 调用的开始、结束、失败、重试都会发出事件。你可以把这些事件接入监控系统,也可以在界面上实时查看每一条流水线的运行轨迹。
配额控制同样重要。很多项目一开始不关心 Token 消耗,等月底账单出来才发现,一个失控的重试循环烧掉了大半预算。AgentGate 支持为每个 Agent、每条流水线甚至每个租户设置 Token 预算,超了就自动熔断,从机制上避免预算失控。
3. 五个关键可靠性机制,逐个拆给你看
这一部分我挑五个最核心、也最值得你抄作业的机制详细展开。每一个都是我实际用下来觉得“少了它真不行”的能力。
3.1 结构化输出门控:拦截“看起来成功”的坏结果
很多 Agent 的真实失败不是抛异常,而是返回了一个“没按要求来”的结果。比如你让 Agent 输出 JSON,它给你输出了一段包含 JSON 的 Markdown 文本;你让 Agent 返回一个包含“订单号”字段的对象,它给你返回了一个没有这个字段的对象。这种失败不会报错,但会污染下游链路。
AgentGate 对每个 Agent 支持声明结构化输出模型,并在拿到模型返回后做一次强校验。校验不过的情况不会进入下游,而是直接触发重试或降级逻辑。以 Python 项目为例,你只需要声明一个 Pydantic 模型:
from pydantic import BaseModel, Field from agentgate import Agent, OutputGate class OrderInfo(BaseModel): order_id: str = Field(description="订单号") amount: float = Field(description="订单金额") status: str = Field(description="订单状态") extract_agent = Agent( name="order_extract", prompt="根据用户消息抽取订单信息", output_schema=OrderInfo, output_gate=OutputGate(schema=OrderInfo, retry_on_failure=True), )这里最关键的不是普通校验,而是retry_on_failure=True这个参数。也就是说,当模型返回的结果不符合 OrderInfo 结构时,AgentGate 会自动把校验失败的错误信息追加到 Prompt 中重新请求模型,相当于告诉模型“你上次格式不对,请重新生成”。实际跑下来,大多数校验失败经过一到两次重试就能恢复,相较于直接失败让业务方去处理,体验提升了不止一个档次。
3.2 带抖动的指数退避:重试不是越快越好
很多初写重试逻辑的开发者会踩同一个坑:失败后立刻重试,再失败再立刻重试,重试几次失败了几次,下游服务直接被重试请求打穿。模型的限流错误尤其需要谨慎,限流本来就是保护机制,你越急着重试,限流就越严重。
AgentGate 默认的重试策略是指数退避加抖动。退避时间随重试次数指数增长,再加上一个随机抖动因子,避免多个请求同时重试造成惊群。公式大致是:
wait_time = min(max_wait, base_delay * (2 ^ attempt)) + random_jitter例如初始延迟 1 秒,每次翻倍并加上 0 到 500 毫秒的随机抖动,重试上限 3 次。那么第一次失败后等待约 1 秒,第二次约 2 秒,第三次约 4 秒。这样既给了下游恢复时间,又避免了所有失败请求集中在同一时刻发起洪峰。
3.3 熔断器:给下游一个喘息的机会
重试解决的是偶发故障,但如果一个 Agent 接口持续失败,再多的重试都是徒劳,反而会加重下游负担。熔断器就是干这个的:当某个 Agent 的失败率达到阈值,熔断器打开,后续请求直接快速失败,不再实际调用模型。
AgentGate 的熔断器是经典的三种状态模型:关闭(closed)、打开(open)、半开(half-open)。关闭状态正常放行;打开状态下快速失败;经过冷却时间后进入半开状态,放行少量探测请求,如果探测请求成功,则关闭熔断器,恢复流量,如果失败则重新打开。
核心配置项有这么几个:
from agentgate import CircuitBreaker breaker = CircuitBreaker( failure_threshold=5, # 连续失败 5 次进入打开状态 recovery_timeout=30, # 30 秒后进入半开状态 half_open_max_calls=2, # 半开状态最多放行 2 个探测请求 half_open_success_threshold=2, # 探测请求全部成功则关闭熔断器 )参数怎么选后面我会专门讲,这里只提一句:半开的探测并发数一定不能设得太高,我见过有人设 10,恢复的一瞬间直接把模型接口打限流了,熔断器刚合上又被弹开,形成抖动循环。
3.4 局部退化:没有 A 就启用 B 路径
熔断之后怎么办?直接返回错误给用户肯定不行,AgentGate 提供了局部退化机制。
典型的退化路径有三条:第一条是切换模型,主模型不可用时降级到备用模型,比如从效果好的大模型切换到更快更便宜的小模型,牺牲一点质量换回可用性;第二条是精简任务,比如一个复杂的多轮分析 Agent 不可用时,降级成“只输出关键结论”的单轮 Agent,保证核心功能不塌;第三条是显式告知,实在没法降级,就返回一段预设的兜底文案,让用户知道当前服务暂不可用,而不是给出一堆乱码。
局部退化这个机制的设计思路是:与其整体失败,不如局部承接。多 Agent 系统里最忌讳的就是“一个环节挂了,整条链路全挂”,退化的价值在于,就算某个环节不能以最优方式运行,系统也能以“次优但可用”的方式继续服务。
3.5 事件总线:可观测性要下沉到每一次调用
可观测性这块,AgentGate 把事件做成了一套标准化结构,每个事件包含调用 ID、Agent 名称、流水线 ID、状态、耗时、Token 消耗、错误信息等字段。
from agentgate import EventBus, InMemoryEventLog bus = EventBus(event_log=InMemoryEventLog()) bus.subscribe("agent.started", lambda event: print("started:", event.agent_id)) bus.subscribe("agent.failed", lambda event: log_error(event)) bus.subscribe("agent.succeeded", lambda event: metrics.record(event.duration_ms, event.token_usage))你可以订阅各类事件做实时监控,也可以把事件落库之后做离线分析。为什么这个机制重要?因为多 Agent 系统的状态太多了,你不可能通过打日志的方式全局排查,统一的、结构化的事件流是排障的基础设施。
4. 从零接入:一个三 Agent 工单链路的完整配置
光讲概念没有用,我来还原一个我实际搭过的智能工单场景。整个链路有三个 Agent:意图识别、信息抽取、回复生成。前端用户发来一条消息,系统要判断用户想做什么,抽取必要信息,再生成回复。
4.1 最小接入:包装你的第一个 Agent
先做最小接入。AgentGate 提供了装饰器的方式来包装一个 Agent,把原有 Agent 函数变成可靠性可控的调用单元。
from agentgate import Gate gate = Gate() @gate.agent(name="intent_classify", timeout=30, max_retries=2) def classify_intent(user_message: str) -> dict: # 原始 Agent 逻辑,比如调用模型 return model_response接入过程无非两步:初始化一个 Gate 实例,把你的 Agent 函数装饰起来。装饰器会自动套上超时、重试、熔断这些默认能力。先把最小闭环跑通,再加配置,这是最稳的接入路径。
4.2 配置一个三 Agent 编排链路
接下来,把三个 Agent 编排成一个流水线。AgentGate 用 DAG 来描述依赖关系,下面是一段配置代码:
from agentgate import Workflow, Edge wf = Workflow(name="ticket_workflow") wf.add_node(agent=intent_agent) wf.add_node(agent=extract_agent) wf.add_node(agent=reply_agent) # 依赖关系:意图识别完成后,并行执行抽取;抽取完成后,执行回复 wf.add_edge(Edge(from_node="intent_classify", to_node="order_extract")) wf.add_edge(Edge(from_node="order_extract", to_node="reply_generate"))这段配置表达的逻辑是:先跑意图识别,完成之后跑信息抽取,最后跑回复生成。实际业务里抽取和回复之间通常还会有条件判断,比如识别结果是“退款申请”才走退款链路,AgentGate 支持在边上面加条件表达式,这里不展开,原理是一样的。
4.3 关键参数怎么定:用 P95 而不是拍脑袋
配置参数是最容易出问题的地方。很多人上来就拍脑袋定超时时间,结果要么是太短把正常任务误杀,要么是太长把故障时间拉满。
我的建议是:先不做任何超时和重试配置,跑一周,收集 Agent 调用耗时的分布数据,然后按 P95(95 分位耗时)来定超时。比如某个 Agent 平时耗时在 2 到 8 秒之间,P95 是 6 秒,那么超时设为 10 秒左右比较合理,留出约 1.5 倍余量。
重试次数和熔断阈值,参考一个经验表格:
| 参数 | 推荐初值 | 调整方向 |
|---|---|---|
| 超时时间 | P95 的 1.5 倍 | 误杀多就调大,故障恢复慢就调小 |
| 最大重试次数 | 2 到 3 次 | 失败后可重试性差就减少 |
| 熔断失败阈值 | 连续 5 次 | 下游脆弱就调小,抖动频繁就调大 |
| 熔断恢复时间 | 30 秒 | 下游恢复慢就调大,业务敏感就调小 |
| 半开探测并发 | 1 到 2 个 | 探测失败多就调小,恢复慢就调大 |
这些只是初值,真实环境一定要根据线上数据持续调整。调参的过程,本质上是在“可用性”和“故障影响面”之间找平衡点,没有一劳永逸的答案。
5. 生产环境踩坑实录:超时、重试、熔断的那些教训
这一节的内容,都是我在真实项目里踩过、并且后来在团队里反复讲过的坑。希望你看到的时候能少走几步弯路。
5.1 你以为成功了,其实什么都没有
最隐蔽的失败就是空输出。模型有时候会返回一个空字符串或者一个全是空字段的 JSON,状态码 200,接口调用没有抛任何异常。我的第一个多 Agent 项目就因为这个问题查了半天:Agent 显示成功,下游却拿不到任何有效数据。
AgentGate 的结构化输出门控能挡住这类问题,但我必须提醒你:门控只能拦截“结构不对”的情况,拦截不了“结构对但内容错”的情况。如果你要的是高可靠场景,建议再加一层语义校验,比如规则校验、相似度校验或者用另一个小模型做结果审核。
5.2 重试导致下游重复消费
重试机制是好东西,但它有一个副作用:同一个任务可能被执行多次。如果 Agent 内部有写库操作,重试就会导致重复写入。在设计 Agent 时一定要保证幂等性,最常用的做法是给每个任务生成唯一 ID,下游消费方根据 ID 去重。
这个问题我在一个订单处理场景里吃过亏。订单系统重试时重复推送了同一个订单,结果生成了两条工单,用户在后台看到了重复记录,处理人员一脸懵。后来加了幂等键,整个世界清净了。记住:重试是网络层面的承诺,幂等是业务层面的承诺,两者都要。
5.3 熔断恢复后立刻过载
熔断器从半开恢复到关闭的瞬间,所有积压的请求会一起涌入。如果恢复时你没有限制并发,下游马上会被打挂。我的经验是半开探测并发数设为 1 到 2 个,并且打开状态下的快速失败要返回一个明确错误码,让上游知道“当前不可用,稍后再试”,而不是默默重试。
5.4 超时一刀切,把正常任务误杀了
不同 Agent 的耗时差异可以非常大。一个简单的分类 Agent 可能不到 1 秒就返回了,一个需要生成长篇内容的 Agent 可能要跑 30 秒。如果你用一个统一的超时时间,短的会被放得太宽,长的会被卡得太死。
正确的做法是对每个 Agent 单独设置超时。事实上 AgentGate 设计之初也考虑了这个点,它在每个 Agent 接点上独立配置,而不是在全局统一配置。一个全局的宽松超时兜底,加上每个 Agent 的精细化超时,才是比较稳妥的组合。
5.5 Token 预算失控,月底账单教做人
最后提醒一句:Token 预算一定要设。我的一个项目曾经因为一个 Agent 的重试次数设得过高,某天模型接口出现大面积超时,重试全部堆积,烧掉了平时一个月的 Token 量。事后看账单,数字真的肉疼。
给每个 Agent 设定每日 Token 预算,超出后自动熔断,这种“小痛”绝对好过月底“大痛”。AgentGate 的治理层有现成的配额控制,别嫌麻烦,配上。
6. 写在最后:我的几点使用体会
把 AgentGate 接入项目一段时间之后,我最明显的感觉是:排障不再靠猜了。以前多 Agent 系统出了问题,我要各个 Agent 逐个看日志,追踪数据是怎么传递的,经常看半天还是一头雾水。现在打开 AgentGate 的调用轨迹,每一步花了多久、消耗了多少 Token、输出了什么、哪里重试了、哪里熔断了,一目了然。
我个人实际用得最多的其实是局部退化配置。主模型偶尔抽风是常态,有了备用模型和精简任务路径,线上几乎再没出现过“整条链路不可用”的尴尬。重试加抖动这个默认策略,也让限流引发的连锁问题少了很多。
最后再分享一个小技巧:AgentGate 接入初期,先把事件日志完整落库一段时间,不要设任何采样。你后面调参数、做容量规划的时候,这些历史数据都是宝贵的依据。多 Agent 系统的可靠性,本质上是一个持续调优的过程,没有什么配置是一劳永逸的。但只要你把骨架搭对了,后面每一步调整,都会变得非常清晰。