日期:2026-07-18
本文为工程实践复盘,客户信息已脱敏;所述数据均为示意值。
我们在这个项目里服务的是某股份制银行的智能问答与报告生成场景。他们的业务链路天然是多步的:先要检索知识库,再调大模型生成,最后还要做一次合规校验。早期他们直接在前端写死调用顺序,主模型一抖就整链路 504,长一点的报告(几分钟级别)前端一直转圈,用户以为卡死就狂点重试,把网关打垮。更糟的是,重试会重复计费、重复写库,产生脏数据。我们当时接手,核心诉求就两条:把“多步编排”从业务代码里抽出来交给网关统一管;让长任务能流式返回,前端有进度、用户有耐心。本质上,是要把“调用编排”和“容错”这两个横切关注点从每个业务系统里剥离出来。
我们采用我们采用的 AI 网关,把原来散落在各业务系统里的多模型调用收口到网关的编排层。典型流程是:网关收到请求 → 接入层做鉴权与协议归一化 → 路由层按成本/延迟画像选模型 → 编排层串联“检索增强 → 生成 → 校验”三步,其中检索和生成的部分子任务可并行 → 流式逐段回传生成内容 → 校验失败触发回退或补偿。同时网关的限流熔断信号会反向驱动我们采用的底座做弹性扩缩容,形成流量—算力联动。网关本身只做编排与转发,不持有业务状态,所有临时状态落在 Redis,这样网关实例可以随意扩缩而不丢任务。同时计量层对每次调用做 token 与耗时的统一计量,按部门与用户沉淀配额,既方便后续成本归因,也让我们在压测时能精确看到回退路径多烧了多少算力。
多步编排与依赖编排。 三步里“生成”依赖“检索”结果,但“合规校验规则加载”可以和“检索”并行。我们用 DAG 描述依赖,网关按拓扑序调度,支持节点级超时与并行度控制。这样业务侧只声明“我要什么”,不用管“怎么调”。
# orchestration.yaml(脱敏示意)
dag:
- id: retrieve
type: parallel_group
nodes: [vector_search, rule_load]
- id: generate
depends_on: [retrieve]
model: primary-llm
timeout_ms: 120000
- id: validate
depends_on: [generate]
fallback: validate_lite
flow:
stream: true流式输出与前端体验。 长生成任务最伤体验的是“黑盒等待”。我们在网关与模型之间做了 SSE 流式代理,把模型输出的 token 块边收边转,前端拿到首段就能渲染,配合一个进度估计(已生成 token / 预估总量)。首字时延从“等整段”降到秒级。这里有个坑:SSE 要正确处理客户端断连,否则后端还在拼命生成、前端早关了,白白烧算力。我们加了连接探活,断连即停。
长任务超时与断点恢复。 报告类任务可能跑几分钟,网关侧不能无限挂连接。我们用“任务句柄 + 断点续传”:网关先返回 task_id,后端异步执行,前端用 task_id 轮询或订阅;中途网关重启可从检查点恢复,已生成片段不丢。检查点粒度设为每约 2000 token 落一次 Redis,恢复时从最近检查点续写 SSE。
案例片段(已脱敏):一次 5 分钟级报告任务,主模型在 110s 处超时,网关日志触发回退:
[orch] task=t-8a21 stage=generate timeout=120000 [orch] primary-llm unreachable, fallback -> standby-llm [orch] resume from checkpoint token=18432 span=3200 [orch] done task=t-8a21 elapsed=312s ok
失败回退与补偿。 编排层每个节点都配了 fallback:主模型失败切备用模型;校验严格模式失败降级到宽松模式并打标“需人工复核”。补偿用幂等任务表(以 task_id 为唯一键),失败重试不重复计费、不重复写库。我们当时特意把“回退路径”也纳入压测,因为生产环境里主模型抖动才是常态,快乐路径反而罕见。
我们在生产环境灰度约六周,取脱敏统计窗口内的数据(均为示意值):
| 指标 | 改造前 | 改造后 | 说明 |
|---|---|---|---|
| 编排成功率 | 约 82% | 约 99.2% | 含回退成功 |
| 首字时延(P50) | 约 4.1s | 约 0.8s | 流式首段 |
| 任务完成率 | 约 88% | 约 98.5% | 长任务 |
| 回退耗时 | — | 约 1.2s | 主切备 |
长任务(>60s)的用户中途放弃率从约 35% 降到约 9%,前端“假死”类工单基本归零。网关侧峰值 QPS 约 1.2 万,限流触发时联动底座扩容,平均扩容生效约 40s,未再出现级联雪崩。我们当时还特意统计了“回退命中率”,在窗口内约 3.7% 的请求走了回退路径,也就是说如果没有回退设计,这批请求会直接失败——这恰恰印证了“回退优先”的必要性。
第一,编排设计以“失败回退”为先,而不是以“快乐路径”为先。这个项目里我们前期只画了成功流程,上线才发现主模型抖动才是常态,回退和补偿才是保命逻辑,必须把回退路径也写进压测用例。
第二,流式不只是体验优化,更是容错手段。把长任务拆成可中断、可续传的流,用户容忍度上去了,系统也更容易做超时兜底;但一定要处理客户端断连,避免后端空转烧算力。
第三,限流熔断要能反向驱动算力扩缩容,否则网关挡得住流量、底座却撑不住,联动闭环才稳。信号流方向是“网关 → 底座”,别只做单向限流。
第四,长任务务必脱离“请求—响应”同步模型,任务句柄化 + 异步订阅是必选项,别让 HTTP 连接扛几分钟;检查点粒度要调好,太粗丢进度、太细刷 Redis。
第五,网关只做编排与转发、不持状态,所有临时状态外置到 Redis,这样网关可随意扩缩而不丢任务,是水平扩展的前提。