很多团队优化大模型系统时,会先盯住模型本身:换一个更快的模型、缩短 Prompt、增加并发连接,或者在超时后自动重试。低负载测试中,这些方法往往立刻见效;一旦 Agentic Workflow 进入真实流量,系统却可能出现反直觉现象:平均延迟变化不大,P99 突然翻倍;单次调用价格下降,完整任务成本反而上升;扩充并发后吞吐量没有同步增长,429、超时和重复任务却快速增加。在这里插入图片描述

原因是多模型 Agent 不是一个简单的 HTTP 客户端,而是一张由多个服务中心构成的排队网络。文本推理、向量检索、图像生成、视频队列和外部工具拥有完全不同的服务时间分布。规划节点的一次重试会让下游工作量成倍增加,某个视频节点的长尾会占住整条任务的关键路径,跨区域链路的轻微抖动也会被多阶段调用放大。

因此,企业级 Agent 的核心问题不只是“模型是否足够聪明”,而是如何做 AI Traffic Engineering:描述工作负载,估算容量上限,控制队列,传播 deadline,管理重试预算,再用 Token Dynamic Routing 在质量、成本和延迟之间寻找可解释的运行点。本文从性能工程和 SRE 角度建立一套完整方法,并给出排队模型、容量公式、Benchmark 设计与 FastAPI/Asyncio 网关实现。

一、平均延迟正在说谎:Agent 性能问题发生在长尾

在这里插入图片描述

1. 一个请求实际上会裂变成多少调用

传统 Web API 通常是一进一出,而一个 Agent 请求可能先调用规划模型,再并行执行检索、代码分析和视觉理解,随后调用验证模型;若验证失败,还会进入修复循环。

假设规划器平均生成四个子任务,每个子任务有 10% 的重试概率,那么上游一个请求对应的内部调用数并不是固定的四次,而是一个带长尾的随机变量。

调用放大系数可以定义为:

[
A=\frac{N_{model}+N_{tool}+N_{retry}}{N_{root}}
]

其中 N_root 是根任务数,分子分别是模型、工具和重试调用数。若 A 从 5 上升到 8,即使用户 QPS 不变,内部基础设施负载也增加了 60%。只监控入口请求率,会让平台在容量耗尽后才发现问题。

LangChain、LlamaIndex、AutoGPT 等框架可以快速组织工具与模型,但框架层的“链长度”不等于实际调用放大。条件分支、解析修复、回调重放和外部 SDK 重试都可能产生隐藏调用。

生产环境需要在 API Gateway、执行器和工具层使用同一个 trace_id,才能得到真实的放大系数。

2. 为什么 P99 比平均值更重要

假设一个工作流包含六个串行阶段,每个阶段只有 1% 的概率进入慢路径。完整流程至少遇到一次慢路径的概率约为:

[
P_{slow}=1-(1-0.01)^6\approx5.85%
]

节点越多,长尾被命中的概率越高。即使每个模型的 P50 都很好,最终任务的 P95 仍可能很差。并行分支也不会自动解决问题,因为汇聚节点通常要等待最慢分支,整体延迟近似于多个分支的最大值,而不是平均值。

这解释了为什么“平均响应 3 秒”不足以描述 Agent 体验。至少要同时监控 TTFT、节点 P50/P95/P99、关键路径延迟、队列等待时间和 deadline 超时率。

视频等异步任务还要拆分提交延迟、排队延迟、生成延迟与资产拉取延迟,否则所有问题都会被压缩成一个没有诊断价值的总耗时。

3. Token 长度让服务时间不再服从简单分布

LLM 服务时间与输入长度、输出长度、批处理状态、采样参数和硬件调度有关。两个相同路由的请求,一个只输出 50 Token,另一个输出 4000 Token,它们占用解码资源的时间相差巨大。

使用“每请求平均耗时”做容量规划,会把长输出请求对队列的影响严重低估。

更合理的工作量单位是 Token work:预填充阶段统计输入 Token,解码阶段统计输出 Token,并分别估算单位时间成本。图像和视频任务则使用像素、分辨率、时长、帧数或采样步数描述工作量。

统一调度层不要求所有模态使用同一个物理单位,但必须把它们转换为可比较的配额消耗和预计服务时间。

4. 性能问题首先是观测问题

每个节点都应记录 queue_msconnect_msttft_msservice_msvalidation_msinput_tokensoutput_tokensattemptroute_alias

只有 service_ms 属于上游实际处理,其他阶段由网关、网络或业务验证引入。把它们合并成 latency_ms,会导致团队错误地更换模型,却没有解决真正的排队瓶颈。

此外,指标必须按任务类型分桶。代码修复、短分类、长文档总结、图片生成和视频生成的延迟分布不可混在一起。总体 P99 下降,可能只是低成本分类请求占比增加,而核心工作流并没有改善。

观测还要区分“用户放弃”和“系统完成”。当客户端在等待十秒后断开,上游可能继续生成数分钟;如果报表只统计成功返回给客户端的请求,就会漏掉这部分无效成本。

网关需要记录断开时间、取消是否成功以及取消后继续产生的 Token。单位成功任务成本的分母只能使用真正交付并通过验收的任务,而不是所有收到 200 状态的模型调用。

采样策略也会影响长尾判断。为了降低日志量而随机采样 1% 请求,极端慢请求数量可能不足,P99 估计会剧烈波动。

可以对普通请求低比例采样,对超时、重试、降级和高成本请求全量保留,再通过权重还原总体分布。任何分位数都应同时报告样本量和时间窗口。

二、先描述工作负载:到达率、服务时间与任务指纹

在这里插入图片描述

1. 四类不能混合的流量

Agent 平台至少要区分交互流量、批处理流量、异步生成流量和系统维护流量。交互流量关注 TTFT 与截止时间;批处理关注吞吐和单位成本;异步生成允许较长等待,但需要可靠状态;维护流量包括评测、回放和健康探针,不应挤占用户配额。

如果所有请求进入同一队列,大批量离线摘要可能堵住实时问答,视频轮询也可能消耗文本推理连接。

隔离队列并不意味着部署四套系统,而是为不同流量定义独立的优先级、并发上限、deadline 和降级策略。

2. 用任务指纹代替模型名称

调度器需要描述任务本身,而不是先决定模型。一个任务指纹可以包含输入 Token 桶、期望输出 Token、约束数量、工具数量、模态、是否流式、是否含外部副作用、截止时间、质量等级和租户优先级。

指纹相同的任务通常拥有相近的服务时间与失败模式,适合建立独立容量基线。

from dataclasses import dataclass
from statistics import mean, pstdev


@dataclass(frozen=True)
class WorkloadSample:
    input_tokens: int
    output_tokens: int
    service_ms: float
    success: bool


def summarize(
    samples: list[WorkloadSample]
) -> dict[str, float]:
    times = [
        sample.service_ms
        for sample in samples
    ]
    if not times:
        raise ValueError("empty_samples")

    average = mean(times)
    variation = (
        pstdev(times) / average
        if average
        else 0.0
    )

    return {
        "count": float(len(samples)),
        "service_mean_ms": average,
        "service_cv": variation,
        "success_rate": (
            sum(s.success for s in samples)
            / len(samples)
        ),
        "input_tokens_mean": mean(
            s.input_tokens
            for s in samples
        ),
        "output_tokens_mean": mean(
            s.output_tokens
            for s in samples
        ),
    }

service_cv 是服务时间变异系数。它越大,说明长尾越明显,相同平均服务时间下需要预留更多容量。

只比较平均延迟而忽略变异系数,会让高波动模型在压测初期看起来很快,却在高利用率下形成严重排队。

3. 到达过程也会突发

真实流量很少是平滑的泊松过程。定时任务可能在整点集中启动,热点内容会带来短时峰值,上游故障恢复后还会释放积压请求。

容量规划需要同时报告平均到达率、五分钟峰值、突发持续时间和租户集中度。

可以使用 token bucket 描述允许的突发:桶容量决定短时间最多吸收多少工作量,补充速率决定长期平均吞吐。

对文本请求,桶消耗可以按预计输入加输出 Token 计算;对视频任务,按预计 GPU 秒或成本指数计算。请求数相同不代表资源消耗相同,因此不能只用 RPS 限制所有流量。

工作负载基线需要按时间持续更新,但不能被事故数据直接污染。若上游故障导致大量超时,把这些样本纳入正常服务时间预测,会让调度器长期高估资源需求;若只保留成功样本,又会低估真实风险。

更稳妥的做法是分别维护健康基线与故障分布,容量规划使用健康基线加故障余量,异常检测则比较当前窗口与两类分布的距离。

任务指纹还可以用于成本归因。相同入口可能服务多个业务场景,只有把根任务映射到稳定指纹,才能判断成本上涨来自流量结构变化、输出长度变化还是某个路由退化。

报表应同时展示“同指纹成本变化”和“指纹占比变化”,避免把用户开始提交更复杂任务误判为平台效率下降。

4. 模型别名必须与统计版本绑定

DeepSeek-R1、DeepSeek-V3、Claude 3.5 Sonnet、GPT-4o、Qwen2.5-72B、Llama-3.3 等名称可以作为候选配置,但容量数据必须绑定 provider_model_id、区域、参数、协议版本和测试时间。

Flux.1 Dev/Pro、Midjourney V6 API、Kling V1.5/V3 API、MiniMax Video-01、Sora API 映射等生成能力尤其容易受队列策略与账户权限影响,不能复用另一个环境的服务时间数据。

诸如 GPT-5.6、Qwen 3.7 Max 等字符串若仅是内部路由别名,应明确标注别名属性,不能被写成供应商当前公开能力的确定结论。

路由层读取实际部署映射,报表同时保存别名和解析后的真实 ID,避免版本切换后历史曲线失去意义。

三、容量不是连接数:用排队论找到利用率悬崖

在这里插入图片描述

1. Little’s Law 的工程含义

Little’s Law 给出一个简单关系:

[
L=\lambda W
]

L 是系统中平均在途任务数,λ 是平均到达率,W 是平均停留时间。若入口每秒接收 20 个根任务,完整任务平均停留 8 秒,那么系统中平均存在约 160 个任务。

这里的在途任务不仅是正在调用模型的任务,还包括排队、等待工具、等待视频回调和等待验证的任务。

这个公式可以反向用于容量检查:如果状态存储、队列或回调服务只能可靠管理 100 个在途任务,那么即使模型并发还有剩余,系统也无法支撑当前到达率。

性能瓶颈可能出现在任何一个服务中心,而不仅是推理后端。

2. 利用率接近 100% 时延迟会非线性增长

以理想化的 M/M/1 队列为例,平均等待时间与服务率 μ、到达率 λ 的关系为:

[
W_q=\frac{\lambda}{\mu(\mu-\lambda)}
]

λ 接近 μ 时,分母快速趋近于零,排队时间会陡增。这就是利用率悬崖。

模型后端长期维持 95% 以上利用率,看上去资源十分充分,实际上已经没有空间吸收输入长度波动、网络抖动与重试流量。

生产系统通常把目标利用率控制在较低区间,并为突发、故障转移和长请求预留余量。具体阈值取决于服务时间变异、扩容速度和 SLO,不能照搬固定数字。

视频生成的冷启动和队列波动较大,所需余量通常高于短文本分类。

M/M/1 假设到达与服务时间都服从指数分布,真实 Agent 往往更接近 G/G/1。此时可以用 Kingman 近似理解等待时间:

[
W_q\approx\frac{\rho}{1-\rho}\cdot\frac{C_a2+C_s2}{2}\cdot E[S]
]

ρ 是利用率,C_aC_s 分别是到达间隔和服务时间的变异系数。

公式揭示了两个工程事实:利用率相同,突发越强、输出长度差异越大,等待时间越长;降低波动有时比增加少量算力更有效。

将超长任务拆到独立队列、平滑整点批任务、限制输出上限,都能降低变异系数。

容量评审还应报告置信区间。只用一天平均流量计算实例数,无法覆盖工作日与周末、活动期间与普通时段的差异。

可以选择历史高分位到达率,再叠加单区域故障转移余量和扩容反馈时间内的积压量。最终容量不是一个精确数字,而是一组明确假设下的安全区间。

3. 关键路径决定端到端容量

Agentic Workflow 是一个排队网络。串行节点的延迟相加,并行节点在汇聚时取最大值,条件分支按发生概率加权。

假设规划、检索和生成三个阶段分别为 1 秒、2 秒、6 秒,即使前两个阶段扩容到接近零等待,端到端容量仍可能被生成阶段限制。

优化时应先画出关键路径,并计算每个节点的排队贡献。若视频节点占完整延迟的 80%,优化文本模型 TTFT 只能带来有限收益。

反之,若大量请求在网关等待配额,换更快的模型也不会改善用户体验。

4. 并发上限要按资源池分层

单个全局 semaphore 会让不同模态互相阻塞。更合理的层级是:平台总并发、租户并发、能力池并发、供应商并发和单路由并发。

请求必须同时获得相关额度才能执行,释放时也要完整归还。

额度不能只依据连接数。流式长回答会长时间占用连接,批量 Embedding 可能单次请求包含大量文本,视频提交请求虽很短却会在上游创建昂贵任务。

调度器需要将“连接槽位”和“计算配额”分开,防止短 HTTP 调用绕过资源控制。

5. 自动扩容存在反馈延迟

根据 CPU 或队列长度扩容并非立即生效。新实例启动、模型加载、连接预热和健康验证都需要时间。

如果扩容观察窗口比流量突发持续时间更长,系统将在实例就绪前完成拥塞和超时。

可以使用预测扩容处理已知周期,使用队列斜率而不是绝对长度发现快速积压,并保留少量热容量。

扩容后要缓慢引入流量,避免冷实例同时接收长请求。缩容则必须等待在途任务排空,不能把正常执行误判为故障。

扩容策略必须防止正反馈。队列上升触发扩容,新实例冷启动期间健康探针变慢,如果控制器误判为容量仍不足并继续扩容,就可能造成资源浪费;反过来,突发结束后同时缩容又会清空热容量。

控制器需要设置冷却窗口、最大变化速率和手动保护阈值,并把扩容决策写入事件流,便于事故复盘。

四、Deadline 才是真正的调度信号:优先级、公平队列与背压

在这里插入图片描述

1. 每个根任务都需要时间预算

“网关超时 60 秒”不是完整的 deadline 设计。根任务应携带绝对截止时间,编排器再把剩余时间分配给各节点。

节点开始前重新读取剩余时间,若已经不足以覆盖该能力的 P95 服务时间,就应改变计划或停止执行。

时间预算可以沿关键路径反向传播。最终验证预留 2 秒,视频资产拉取预留 5 秒,生成节点便只能使用剩余部分。

并行分支也需要独立子预算,不能让一个非关键分支消耗所有时间,导致主结果无法提交。

2. FIFO 对 Agent 并不公平

先进先出忽略了请求大小。一个预计输出 8000 Token 的长任务排在前面,可能阻塞后续几十个短分类请求,形成 head-of-line blocking。

可以使用 Shortest Remaining Processing Time 降低平均延迟,但纯粹偏向短任务又会让长任务饥饿。

生产调度器通常组合使用租户加权公平队列、任务优先级、deadline 和 aging。每个租户获得与组织策略匹配的权重,等待时间越长,任务优先级逐步提升。

高优先级只能影响顺序,不能绕过成本、区域和安全约束。

加权公平不能只按请求数轮转,否则一个租户提交的长任务和另一个租户的短任务会被视为相同成本。可以使用预计 Token work 作为虚拟完成时间,调度器优先选择虚拟完成时间最小的队列。

实际用量与预测偏差在任务结束后回写,持续校准估算器。为了防止调用方通过低报输出长度获得优先级,最终配额仍按真实 usage 结算,并对长期偏差较大的指纹增加安全系数。

队列还需要预留控制流通道。取消、状态查询、回调确认和熔断探针的资源消耗很小,却决定系统能否从拥塞中恢复。

如果它们与大模型调用共享所有线程和连接,过载时用户无法取消任务,回调也无法提交完成状态。独立的小型控制队列可以确保这些操作始终有执行空间。

3. Token Dynamic Routing 应感知队列

静态路由只根据任务难度选择模型,流量工程路由还要读取实时队列。一个质量略高但排队 20 秒的深度推理路由,可能不适合 deadline 只剩 8 秒的请求;轻量模型如果能在时限内通过验收,整体效用反而更高。

这也说明多模型调度不能退化为普通 Load Balancing。负载均衡默认后端能力近似一致,而模型候选在质量、上下文、模态、价格和服务时间上都可能不同;队列最短的后端未必满足任务语义,价格最低的后端也未必能通过验收。

可将候选效用写成:

[
Score(m)=\alpha Q_m-\beta C_m-\gamma(E[W_{q,m}]+E[S_m])-\delta R_m
]

其中 Q 是预测质量,C 是预计成本,W_q 是排队时间,S 是服务时间,R 是错误或漂移风险。

先过滤不满足模态、上下文、区域和最低质量的候选,再对剩余模型排序。队列指标需要平滑和滞回,避免流量在两个后端之间来回振荡。

4. 背压比无限排队更可靠

无限队列只是把失败推迟。任务等待时间已经超过业务 deadline 后,即使最终执行成功,结果也没有价值,还会继续消耗 Token。

队列必须设置最大长度、最大等待时间和按任务类型定义的丢弃策略。

背压可以表现为立即拒绝、返回异步句柄、降低非关键生成质量、暂停低优先级批任务,或要求上游稍后重试。

选择哪种行为由业务契约决定。关键是让过载状态显式传播,而不是让每一层都以为下一层还有容量。

5. 降级必须保持语义

从深度推理模型切换到快速模型不是无条件安全的。路由策略需要知道最低质量、工具支持、结构化输出和上下文要求。

若备用模型不满足硬约束,应转人工审核或延迟处理,而不是为了降低延迟返回不可用结果。

降级响应要携带 degraded=true、原始路由、实际路由和降级原因。业务层可以决定草稿是否继续、是否显示部分结果、是否等待完整版本。

没有标记的静默降级会污染质量数据,也会让问题排查失去依据。

deadline 到期时也不应一律返回空结果。对于可以增量交付的任务,系统可提交已经通过验证的部分节点,并附带未完成列表;对于必须原子交付的任务,则进入失败或异步继续状态。

是否允许部分结果必须在任务契约中预先声明,不能由网关临时猜测。这样既避免丢弃已有计算,也不会把残缺内容误当成完整交付。

五、重试会制造第二次故障:超时、对冲与熔断策略

在这里插入图片描述

1. 超时不等于失败

客户端超时只表示没有在期限内收到结果,不代表上游没有执行成功。对于纯文本推理,重复计算主要带来额外成本;对于视频提交、数据库写入或消息发送,重复执行可能产生多个资产或外部副作用。

所有可重试调用都需要幂等键。网关将幂等键与租户、能力、规范化输入和计划节点绑定。

重复请求先查询结果账本:已完成则返回原结果,执行中则返回相同任务句柄,只有明确未提交时才创建新调用。

2. 重试预算必须小于请求预算

如果每层都独立重试三次,Agent 执行器、网关和 SDK 的组合可能把一次失败放大成数十次上游调用。

重试权应该集中在最了解业务 deadline 的一层,其他层只上报可重试错误与 retry_after

根任务分配总重试预算,例如最多额外消耗原计划成本的 15%。每次重试同时扣减时间预算、成本预算和尝试次数。任一预算耗尽即停止。

指数退避加入随机扰动,避免大量请求同时醒来形成同步冲击。

3. Hedged Request 只适合少数场景

对冲请求是在主请求迟迟没有首包时,向另一个健康后端发送相同请求,取最先完成的结果。它可以降低长尾,但会增加调用量和成本,也可能破坏确定性。

只有纯函数式、无外部副作用且幂等的节点才适合对冲。

对冲触发点不应是固定秒数,而应根据路由近期延迟分位数设置,例如超过 P95 且仍有足够 deadline 才启动。

主请求完成后要尽快取消备用请求,并记录额外 Token。若上游不支持取消,对冲可能让成本翻倍,必须纳入预算模型。

4. 熔断器保护的是整个调用图

当某个后端持续超时,继续发送请求只会扩大队列。熔断器在错误率或慢调用率越过阈值后进入打开状态,新请求不再进入故障路由;冷却后只允许少量探针通过,确认恢复再逐步增加流量。

熔断状态应按区域、能力和错误类型隔离。一个模型的视觉接口故障,不代表文本接口也不可用;鉴权配置错误也不能被当作短暂网络故障自动恢复。

熔断事件需要进入路由决策和容量报表,否则故障转移后备用池可能被突然流量压垮。

5. Agent 状态机要识别“不确定结果”

常见状态只有成功和失败,但分布式调用还存在 UNKNOWN:请求已发送,连接在响应前断开,无法判断副作用是否提交。

此时执行器应先通过查询接口、回调记录或幂等账本确认,而不是直接重试。

对于长时间异步任务,状态可以包含 ADMITTEDQUEUEDRUNNINGWAITING_CALLBACKVALIDATINGSUCCEEDEDRETRYABLEUNKNOWNCANCELLED

每次状态转换追加事件,重启后从事件账本恢复,不能依赖进程内 Future。

事故中还要防止“恢复洪峰”。上游恢复后,如果所有失败任务立即从 RETRYABLE 进入执行,备用池和刚恢复的主池都会再次过载。

恢复控制器应按租户和任务年龄分批释放,先处理接近业务 deadline 的任务,再处理离线任务;每批观察错误率与队列斜率,稳定后才扩大恢复速率。恢复本身是一段需要容量预算的流量阶段。

对于无法确认结果的 UNKNOWN 状态,应设置专用核对流程和最长保留时间。核对可能调用供应商查询接口、检查资产仓库或比对外部回执。

超过保留时间仍无法确定时,系统转人工处置并冻结自动重试。把未知结果简单归类为失败,会掩盖重复副作用风险。

六、把统一网关做成流量计量点:协议、配额与异步实现

在这里插入图片描述

1. 为什么网关是最佳测量位置

业务代码只知道 Agent 目标,上游供应商只知道单次模型调用,统一 API Gateway 则同时看到租户、能力、路由、Token、延迟和错误。

它适合承担鉴权、配额、协议转换、流式归一化和计量,但不应决定工作流业务逻辑。

不同后端可能使用 OpenAI Compatible Protocol,也可能返回私有 JSON、SSE 事件或异步任务 ID。网关把它们转换为统一信封,并保留上游 request ID 供追踪。

在协议压测环境中,可将统一接口测试上游配置为 base_url="https://178.nz/bo",凭证通过密钥服务注入;该地址仅用于验证转换、限流、SSE 与 Token 计量链路,不写成生产代码的固定依赖。

2. 请求信封和流量契约

请求信封至少携带 tenant_idrun_idnode_idcapabilitydeadline_atidempotency_key、预计输入输出 Token 和流式标记。

网关先检查 deadline,再预留配额,随后选择驱动。完成后用真实 usage 结算并释放剩余额度。

流式事件统一为 response.startedresponse.deltaresponse.tool_callresponse.usageresponse.completedresponse.failed

客户端断开时,网关应取消上游或记录孤儿调用。若缓冲区持续增长,必须暂停读取或终止请求,不能把慢客户端造成的内存压力传遍整个平台。

3. FastAPI 与 Asyncio 实现骨架

import asyncio
import os
import time
from datetime import datetime, timezone
from typing import Any

import httpx
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel, Field


app = FastAPI()


class TrafficRequest(BaseModel):
    run_id: str
    node_id: str
    capability: str
    deadline_at: datetime
    idempotency_key: str
    expected_input_tokens: int = Field(ge=0)
    expected_output_tokens: int = Field(ge=0)
    messages: list[dict[str, Any]]


ROUTES = {
    "reasoning.fast": os.environ[
        "FAST_ROUTE_URL"
    ],
    "reasoning.deep": os.environ[
        "DEEP_ROUTE_URL"
    ],
}


def seconds_left(
    deadline: datetime
) -> float:
    now = datetime.now(timezone.utc)
    return max(
        (deadline - now).total_seconds(),
        0.0
    )


async def upstream(
    url: str,
    payload: dict[str, Any],
    timeout: float
):
    limits = httpx.Limits(
        max_connections=256,
        max_keepalive_connections=64
    )
    async with httpx.AsyncClient(
        http2=True,
        limits=limits
    ) as client:
        response = await client.post(
            url,
            json=payload,
            timeout=timeout
        )
        if response.status_code == 429:
            raise RuntimeError("RATE_LIMITED")
        response.raise_for_status()
        return response.json()


@app.post("/internal/traffic/invoke")
async def invoke(
    req: TrafficRequest,
    x_tenant_id: str = Header()
):
    url = ROUTES.get(req.capability)
    if url is None:
        raise HTTPException(
            400,
            "UNSUPPORTED_CAPABILITY"
        )

    remaining = seconds_left(
        req.deadline_at
    )
    if remaining <= 0:
        raise HTTPException(
            408,
            "DEADLINE_EXCEEDED"
        )

    started = time.perf_counter()
    payload = {
        "messages": req.messages,
        "metadata": {
            "tenant_id": x_tenant_id,
            "run_id": req.run_id,
            "node_id": req.node_id,
            "idempotency_key": (
                req.idempotency_key
            ),
        },
    }

    try:
        result = await asyncio.wait_for(
            upstream(
                url,
                payload,
                remaining
            ),
            timeout=remaining,
        )
    except asyncio.TimeoutError as exc:
        raise HTTPException(
            504,
            "UPSTREAM_TIMEOUT"
        ) from exc
    except RuntimeError as exc:
        raise HTTPException(
            503,
            str(exc)
        ) from exc

    return {
        "run_id": req.run_id,
        "node_id": req.node_id,
        "route": req.capability,
        "latency_ms": round(
            (
                time.perf_counter()
                - started
            ) * 1000
        ),
        "result": result,
    }

示例将路由地址放在环境变量中,避免源码耦合供应商。生产实现还需要分布式令牌桶、幂等结果表、价格版本、断路器和可观测事件。

配额预留与结算必须具备事务边界,防止调用成功但 usage 写入失败,造成成本账本缺口。

FastAPI 负责异步入口与连接生命周期,Pydantic 负责请求信封的边界校验,Asyncio 负责等待上游 I/O;三者都不应承载业务路由规则。

路由、配额与计量保持为独立服务后,Web 框架升级或驱动替换才不会改变流量策略。

4. 多维配额而不是单一 RPM

RPM 只能限制请求数,TPM 限制 Token,长流式连接还占用 socket 和内存,视频任务则消耗异步槽位与资产带宽。

网关需要为这些维度建立独立计数器,并按租户、能力、区域分层。

配额错误要返回明确维度,例如 INPUT_TPM_EXCEEDEDASYNC_SLOT_EXHAUSTEDSTREAM_LIMIT_EXCEEDED,同时给出安全的重试时间。

模糊的 429 会让客户端盲目重试,进一步加重拥塞。

5. 计量数据必须能够对账

每次调用写入预计用量、实际用量、价格版本、缓存命中、重试来源和取消状态。根任务汇总所有节点后得到完整成本,不能只使用最后一次响应的 usage。

Semantic Cache 命中也要产生计量事件,标记避免的调用成本,否则平台无法评估缓存投入是否有效。

对于供应商缺少 Token 明细的能力,可以使用本地 tokenizer 或工作量指数估算,但必须标记 estimated=true

估算值与供应商账单定期对账,偏差超过阈值时更新模型,不能把估算数据当成精确事实。

对账需要处理流式中断、缓存命中、上游重试和异步任务跨日完成等边界。网关账本以内部调用 ID 为主键,供应商账单以对方 request ID 或任务 ID 关联;无法关联的记录进入差异队列。

差异不能直接平均摊回租户,否则会隐藏某条驱动的计量缺陷。平台应按路由、错误类型和日期追踪未对账比例。

内容安全拒绝也要进入成本账本。有些后端在完成部分推理后才拒绝请求,仍可能产生用量;业务任务却没有成功结果。

报表需要同时展示模型调用成本、有效交付成本和安全拒绝成本。只有这样,团队才能判断应在入口增加更早的规则检查,还是调整工作流输入。

七、Benchmark 不能只测单模型:构建并发、成本与质量联合实验

在这里插入图片描述

1. 测试矩阵必须覆盖负载阶段

一次可靠压测至少包含预热、阶梯加压、稳定平台、突发、故障注入和恢复六个阶段。

预热建立连接池与缓存;阶梯加压寻找吞吐拐点;稳定平台观察内存、队列和错误;突发验证桶容量;故障阶段验证熔断与降级;恢复阶段检查积压是否形成二次冲击。

每个阶段固定数据集版本、路由配置、客户端区域和并发模型。测试结果必须带时间戳,因为模型后端、价格和限流策略会变化。

不同日期的数据不能在缺少环境说明时直接横向比较。

2. 一张有意义的联合指标表

下面是说明报表结构的示例数据,不代表任何模型或供应商的固定表现:

能力路由 并发 TTFT P50 延迟 P99 成功率 调用放大 单任务成本指数
reasoning.fast 16 0.7 s 4.9 s 99.2% 1.04 0.31
reasoning.fast 64 1.5 s 15.8 s 96.4% 1.18 0.38
reasoning.deep 8 2.1 s 16.7 s 98.8% 1.06 1.00
reasoning.deep 32 5.9 s 41.3 s 92.1% 1.34 1.29
video.compose 4 4.8 s 63.0 s 95.7% 1.02 2.44

表中高并发下成本指数上升,并不一定是单价变化,更可能来自超时、重试与修复调用。

调用放大系数与 P99 应放在一起分析。若延迟上升但放大不变,瓶颈可能是排队;若两者同时上升,重试策略很可能正在制造额外负载。

3. 质量、延迟和成本构成 Pareto 前沿

不存在一个模型在所有指标上都最优。路由评测应寻找 Pareto 前沿:只有当一个候选在质量不低、延迟不高、成本不高的情况下至少一项更优,才能完全支配另一个候选。

未被支配的模型根据任务 SLO 分配流量。

质量指标必须与任务对应。结构化抽取检查字段准确率和 Schema 通过率,代码任务检查测试通过率,视觉任务检查约束满足率,视频任务检查时长、画面比例和安全规则。

用通用主观评分替代任务验收,会让路由得到错误的优化方向。

4. 压测客户端也可能成为瓶颈

客户端必须监控自身 CPU、连接数、事件循环延迟和网络带宽。若压测机无法及时读取 SSE,测到的是客户端背压而不是服务端性能。

请求数据要预先准备,避免生成 Prompt 的时间混入发送延迟。

连接池大小、HTTP/2、多路复用、DNS 缓存和 TLS 复用都应固定。每个请求使用唯一 ID,服务端 trace 与客户端时间线对齐。

只有双方指标一致,才能判断延迟发生在发送前、网络中、网关队列还是模型后端。

压测还要避免 coordinated omission。若客户端每次等待上一个请求完成后才发送下一个请求,服务变慢时发送速率会自然下降,最拥塞阶段反而缺少样本,测试结果会低估长尾。

开放模型压测按照预定到达率发送,即使系统已经变慢也继续记录排队;闭环模型则用于模拟真实用户等待行为。两种结果应分别报告,不能混成一个延迟分布。

统计比较需要足够样本和置信度。两个路由 P95 相差几百毫秒,并不一定具有实际意义。

可以对任务分层后使用 bootstrap 估计置信区间,同时报告失败样本,因为只计算成功请求会让高失败率路由看起来延迟更低。

任何性能结论都应写明数据集、并发模式和观察窗口。

5. 用故障演练验证路由承诺

实验中主动让一个深度推理路由变慢,观察新请求是否停止进入、备用池是否过载、旧请求是否正确完成;注入 429,检查客户端是否遵守 retry_after;中断 SSE,检查幂等键是否阻止重复工具调用;延迟视频回调,检查根任务是否保持可恢复状态。

通过标准不是“最后拿到结果”,而是过载时延迟有界、成本不越界、状态可解释、无重复副作用。

一个依靠无限重试最终成功的系统,在真实事故中并不可靠。

回归实验要保存路由配置、价格版本、服务目录快照、压测种子和观测查询。发现版本退化时,可以用相同负载重放并只替换一个变量。

若同时更换模型、Prompt、网关和调度策略,即使指标改善也无法知道收益来自哪里。性能实验与模型质量实验都应遵循单变量原则。

八、用 SLO 和错误预算运营 Agent:从模型接入走向性能治理

Agent SLO 应围绕用户任务定义,而不是只围绕模型接口。可以分别设定计划生成成功率、首个可用结果时间、完整任务 P95、关键约束通过率和单位成功任务成本。
在这里插入图片描述

供应商返回 200 并不代表任务成功,验证失败和超出 deadline 都应计入业务错误。

错误预算把可靠性目标转换为可运营资源。预算充足时可以灰度新路由、调整缓存或提高利用率;预算快速消耗时冻结高风险变更,降低非关键流量,并优先修复长尾和重试放大。

成本也可以设置异常预算:单位成功任务成本偏离基线时,即使可用性正常,也触发调查。

日常运行需要一份面向事件的操作手册。P99 上升时先判断队列等待还是服务时间上升;429 增加时检查入口流量、内部放大和供应商配额;成本异常时拆分输出长度、重试、缓存和路由占比;异步积压时检查回调、worker 租约和资产存储。

每个告警都应指向第一组验证查询与可逆缓解动作,避免事故中临时猜测。

容量变更也要经过灰度。提高并发上限可能增加吞吐,也可能把拥塞从网关转移到模型后端;减少超时可能降低资源占用,却增加业务失败。

每次变更只作用于少量租户或流量分片,观察至少一个完整高峰周期,再决定是否扩大。回滚条件在变更前定义,不能等指标恶化后现场讨论。

真正成熟的多模型系统不会追求所有后端永远健康,而是接受故障必然发生,并通过隔离、背压、deadline、熔断、幂等和可解释降级控制影响范围。

模型仍会持续变化,但工作负载指纹、排队模型、流量契约和验收指标可以保持稳定。

当团队开始用到达率、服务时间分布、调用放大、关键路径和错误预算讨论 Agent,系统才真正从“几个 API 的串联”进入基础设施阶段。

决定生产质量的,不只是模型能力上限,更是高并发和故障条件下,平台能否守住延迟、成本与结果质量三条边界。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐