基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-02 11:28

为 AI 智能体设计背压,控制排队与重试

dev.to · AI#Agent#背压#可靠性
Editor brief · 编辑速览

文章分析模型或工具变慢时,无限制接单如何引发队列积压、超时、重试和重复执行。提出围绕有效截止时间设计背压机制,涵盖租户公平性、队列上限、重试规则及用户可见状态。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

Agent 服务出问题时,监控面板可能还很正常:接口能返回,worker 在忙,只有队列越来越长。等模型或工具变慢,积压才集中变成超时、重试和重复执行,用户看到的则是一直没有内容的聊天窗口。

作者给出的办法是建立反压(backpressure)机制:系统必须根据当前容量,明确决定哪些任务立即执行、哪些排队、哪些延期或拒绝。文章的价值在于把队列、重试、调度和前端状态连成了一份契约。不过,其中的容量数字都是示例,没有实测对照。

为什么加了队列,用户还是会一直等

队列负责存放任务,反压负责决定放不下时,上游该怎么办。只要入口继续接单,队列就可能把即时失败变成几分钟后的失败。

Agent 更容易放大这个问题。一次请求会拆出模型、检索和工具调用;一个慢依赖会长期占住并发槽位;重试又会在容量最紧张时增加工作量。批量导入甚至可能和聊天请求争抢同一份模型配额。

因此,作者建议先问:这个任务等一会儿,还有没有价值?交互请求如果已经赶不上用户需要的时间,就应尽快返回可重试的拒绝;后台任务可以返回任务引用,让用户查询状态。

截止时间必须从任务获准进入系统时开始计算,排队也要消耗这份时间。

原文举的例子很直观:一个允许执行五分钟的工作流,排队已经用了四分钟,剩下就只有一分钟。每个阶段都要接收同一个截止时间,避免过期任务继续占用新容量。

还要分清四种控制:限流判断某个客户在时间窗口内能否再发请求;预算控制 token、工具调用和费用;准入控制判断系统现在能否承诺接下任务;反压则规定不能接时,上游如何调整行为。客户没超过每分钟请求数,不代表共享 worker 还有空位。

容量限制要管到模型和工具,不能只管消息队列

任务可能堆在 HTTP 连接池、进程信号量、任务队列、模型服务、工具客户端和数据库连接池。只给消息队列设上限,积压可能转移到别处。

作者提出四层并发限制:全局限制保护共享资源,租户限制防止单个客户占满槽位,工作流限制约束扇出调用,依赖限制隔离慢模型或慢工具。例如,一个计划同时发起五十次调用,不能只算作一个任务。

下面的示例把准入结果明确分成执行、排队、延期和拒绝:

type Admission =
  | { kind: "admitted"; runId: string; deadlineAt: number }
  | { kind: "queued"; runId: string; positionHint?: number }
  | { kind: "deferred"; retryAfterSeconds: number }
  | { kind: "rejected"; retryAfterSeconds: number; reason: string };

function deadlineFromNow(ms: number) {
  return Date.now() + ms;
}

type Capacity = { running: number; queued: number };

async function admitRun(input: {
  tenantId: string;
  priority: "interactive" | "background" | "bulk";
  capacity: Capacity;
}): Promise<Admission> {
  const MAX_RUNNING = 24;
  const MAX_QUEUED = 200;
  const tenantRunning = await countTenantRunning(input.tenantId);

  if (tenantRunning >= 3) {
    return { kind: "deferred", retryAfterSeconds: 30 };
  }
  if (input.capacity.running < MAX_RUNNING) {
    return createAndLeaseRun(input, deadlineFromNow(90_000));
  }
  if (input.priority !== "interactive" && input.capacity.queued < MAX_QUEUED) {
    return enqueueRun(input, deadlineFromNow(15 * 60_000));
  }
  return { kind: "rejected", retryAfterSeconds: 15, reason: "capacity_unavailable" };
}

要看清 input.priority !== "interactive" 这一行:示例里的交互任务没有容量就拒绝,后台任务才有机会排队。24 个运行槽位、200 个排队任务、租户并发 3、90 秒和 15 分钟截止时间,都不是推荐配置。

生产实现还有一个关键前提:检查容量和占用槽位必须形成原子操作,并跨副本共享状态,可以通过数据库、Redis 脚本或队列原语实现。否则多个 Node 实例都看到空位,依然可能同时放行。

队列还应限制长度,大文件和原始网页放在外部存储,任务记录保存引用即可。

公平性则需要调度规则。作者建议按优先级分组,组内按租户轮询,并示例预留 60% 给交互任务、25% 给普通后台任务、10% 给关键修复或审批恢复任务、5% 给批量任务;批量份额只在其他组为空时被回收。后台任务等待过久还应提高优先级,避免长期饥饿。

重试和流式响应,也必须服从这份契约

超时只能说明调用方没及时拿到结果,不能证明下游没有执行。尤其是写入工具,盲目重试可能产生重复操作。

作者要求每次尝试记录依赖、幂等键、截止时间、错误类型和重试次数。只有临时且可安全重试的错误才重试;遇到 429 或过载响应,遵守服务端提供的 Retry-After,使用指数退避和随机抖动。

下面的代码展示了如何让重试受截止时间和模型并发限制约束:

async function callModelWithRetry(run: Run, request: ModelRequest) {
  for (let attempt = 0; attempt < 3; attempt++) {
    if (Date.now() >= run.deadlineAt) throw new Error("deadline_exceeded");
    try {
      return await modelGate.withPermit(() => model.generate(request));
    } catch (error) {
      if (!isRetryable(error) || attempt === 2) throw error;
      const delay = retryDelayWithJitter(error, attempt);
      if (Date.now() + delay >= run.deadlineAt) throw error;
      await recordRetry(run.id, attempt, error);
      await sleep(delay);
    }
  }
  throw new Error("unreachable");
}

重点看 Date.now() + delay >= run.deadlineAt:如果等待后已经没有时间,就不要继续安排重试。循环最多尝试 3 次,每次 model.generate(request) 都要取得许可。

这里原文的文字解释比代码更宽:它说信号量包住整个调用及重试决策边界,但实际代码只在 withPermit 中执行模型调用,退避和错误判断在外面。另外,示例没有展示等待许可和调用过程如何响应超时,不能据此认定它已经实现严格的端到端截止时间。

前端也要配合。连接保持打开,不代表模型正在生成;任务可能仍在队列里。作者建议发送真实状态:accepted、queued、running、waiting_for_dependency、paused_for_approval、completed、failed,不要用虚假的百分比或「快完成了」安抚用户。

无法等待的请求应在开始流式传输前拒绝。原文错误载荷包含 error: "capacity_unavailable"、retry_after_seconds: 15、request_id: "run_8b2" 和 safe_to_retry: true。客户端应遵守等待时间,并复用幂等键,避免网络抖动把一次点击变成多个任务。

降级也要保留事实边界:检索不可用,就说明只使用了现有上下文;写入没完成,就不能宣称成功。合并相似请求时,还必须确认授权上下文和输入指纹等价,不能只比较问题文本。

我的判断

我认可文章把「是否接单」提前到入口。对于接入模型和工具的 Node 服务,连接数、CPU 或请求频率都无法单独代表可用容量。增加 worker 只有在下游仍有余量时才可能有效,否则可能加重模型或数据库压力。

但这篇属于设计经验,没有给出吞吐、完成率或成本的前后对照。容量比例和优先级策略需要结合业务验证;示例函数也没有实现完整的租户排队上限、预留调度和资源租约。

相比单纯限流,这套方案能处理慢依赖造成的容量下降,代价是维护更多状态和调度规则。简单服务可以先做全局上限、租户上限和明确拒绝,再根据数据增加复杂度。

验证时,队列深度必须和最老任务年龄一起看。两者同时上升,说明任务持续积压;队列满但 worker 空闲,则可能是调度、租约或分区出了问题。作者建议人为拖慢一条模型路由,再恢复,观察混合租户流量下是否出现重试激增,以及恢复后是否发生第二轮冲击。这是演练建议,原文没有报告演练结果。

能拿来做什么

  • 给 Node Agent 入口增加明确的准入结果和统一截止时间,先用观察模式记录「本该放行、排队或拒绝」的判断,再对非关键后台功能启用限制。
  • 把聊天界面的空白等待改成真实任务状态,提供轮询或订阅入口;无法接受等待时,在建立流式响应前返回稳定的错误载荷。
  • 检查重试是否遵守 Retry-After、截止时间和依赖并发限制;涉及写入的工具增加幂等键与结果核对机制,处理「已经成功但响应超时」。
  • 按模型路由、工具、租户等级和任务类型记录排队年龄、首次有效输出耗时、完成率及每个完成工作流的成本,并用慢依赖演练验证容量设置。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
Cursor 写完接口请求,还要过契约校验
下一篇
MCP 工具如何用分页避免结果静默截断

资讯速览

dev.to · AI · 2026-11-02 11:28

AI 评分 8.0 / 10

相关标签

#Agent#背压#可靠性

继续阅读

  • 智能体跨场景复用,审批必须落到 API
  • Cursor 写完接口请求,还要过契约校验
  • MCP 工具如何用分页避免结果静默截断
  • AI 修复重复缺陷时,应先找齐所有调用点
  • 给 Agent 的空结果也要附带诊断