AI网关超长输入裁剪与上下文预算治理落地

日期:2026-08-10

一、项目背景

客户是一家投资管理公司,内部有十几个团队在用 AI 能力,统一走我们部署的 AI 网关接入后端的几个模型实例。网关这块跑了大半年,一直算稳定,直到研究部上了一个"年报速读"的工具。

事情是这样的:那个工具的逻辑很简单,用户上传 PDF,前端解析成文本,整段塞给模型让它总结。一份年报解析出来三十多万字,直接进请求体。第一天上线,网关的错误率曲线直接翘起来,模型侧大量返回上下文超长的错误,同时因为这些请求在被拒绝之前已经占了排队位置和预处理资源,别的团队的正常请求延迟也跟着涨。

更麻烦的是配额。这家公司按部门做 token 计量和预算,研究部当月的额度在三天里被吃掉了六成,其他团队眼看着共享的模型实例被挤,投诉到了 IT 负责人那里。

复盘时我们意识到一个问题:网关一直在管流量、管权限、管路由,但从来没管过"一个请求可以占多大的上下文"。这就像一个只查身份不查行李重量的安检口。

二、落地场景

我们在网关的接入层和路由层之间加了一道上下文预算治理,主要做四件事。

输入长度预估。请求进来先估 token 数,不是等模型报错才知道。预估要快,网关上不能跑重的分词。我们的做法是内置轻量分词器做精确计算,同时对超大请求体先走快速估算(按字符类型分权重)判断是否明显超限,明显超限的直接进裁剪流程,边界附近的才做精确分词。

按策略裁剪与摘要前置。超限了不能简单截断,那样语义丢得厉害。我们提供了几种策略供业务方在网关侧配置:头尾保留(丢中间)、按段落打分保留(用轻量相关性打分选段)、摘要前置(先用小模型把长文压缩成摘要再送主模型)、直接拒绝并返回明确提示。年报速读这种场景配的是摘要前置加段落打分的组合。

上下文预算分配。每个应用(AppKey 维度)配一个单请求的上下文预算上限和一个时间窗内的总预算。超过单请求上限就触发裁剪,超过窗口总预算就限流。预算是按部门额度拆下来的,管控台上能看到消耗曲线。

超限提示与友好降级。以前业务方拿到的是模型返回的原始错误,看不懂。现在网关返回结构化的提示:超了多少、触发了什么策略、裁剪掉了哪些部分、建议怎么改。这个改动看着小,帮工单量降了不少。

三、关键技术挑战与解决思路

token 预估和实际的偏差。 网关是模型无关的,后端挂着好几个模型,各自的分词器不一样,同一段文本在不同模型上的 token 数能差百分之二十以上。网关如果按某一个模型估,路由到另一个模型就可能估错。我们的处理是:预估在路由决策之后做,也就是先知道要发给谁,再用对应模型的分词器估。为此网关侧缓存了各模型的分词器配置,加载一次常驻内存。另外留了百分之五的安全余量,宁可裁多一点,也不要恰好卡在边界上被模型退回来。

裁剪导致的语义丢失。 这是最难的部分,也是我们返工过的部分。第一版做的是简单的头尾保留,业务方很快反馈总结质量下降明显,因为年报里的关键信息经常在中间的财务附注部分。第二版改成段落打分:把长文按段落切开,用一个很小的 embedding 模型算每段和用户问题的相关性,按分数排序保留,保留时维持原始顺序不打乱。这一版效果好多了,但对"总结全文"这类没有具体问题的请求还是没辙,因为没有可以对齐的查询。第三版针对无查询场景引入了摘要前置:分块并行让小模型各出一段摘要,再拼起来送主模型。代价是延迟增加,一份年报要多花七八秒,业务方接受了这个交换。

多模型上下文窗口的差异。 后端模型的窗口从三万两千到十二万八千不等。同一个请求路由到不同模型,能不能塞下结论完全不同。我们把窗口大小做成了模型注册信息的一部分,路由决策时会把"这个请求需要多大窗口"作为一个因子:需求大的优先路由到大窗口模型,如果大窗口模型排队严重,才降级到小窗口加裁剪。这个联动是后来加的,加完之后裁剪触发率降了大概三分之一,因为很多请求其实换个模型就装得下。

降级策略怎么选。 我们一开始想让网关自动判断该用哪种裁剪策略,做了一套规则,跑了两周发现判断经常不合业务预期。后来改成业务方在管控台自己配,网关只提供能力和默认值。这个决定现在看是对的,业务方比网关更清楚自己的文本长什么样、什么信息不能丢。做平台的容易想把智能做在平台侧,有时候把选择权交出去反而更好。

案例片段(已脱敏):某应用的上下文预算与裁剪策略配置

yaml app: research-annual-report context_budget:  single_request_max_tokens: 96000  window: 1h  window_total_tokens: 4000000  on_window_exceed: throttle        # 或 reject truncate_policy:  order: [route_to_larger_model, summarize_first, paragraph_rank, reject]  summarize_first:    chunk_tokens: 8000    parallel: 6    summary_model: small-7b    target_ratio: 0.18  paragraph_rank:    keep_ratio: 0.55    preserve_order: true    always_keep: ["^第[一二三四五六七八九十]+节", "重大风险", "关联交易"] safety_margin_pct: 5

案例片段(已脱敏):一次超长请求的处理链路日志

[req 9f2a41c7] app=research-annual-report  raw_chars=331,842 [route] 候选模型 M-A(128k, 排队12) / M-B(32k, 排队2)        需求预估 ~108k tokens -> 选 M-A(需求驱动) [estimate] tokenizer=M-A  精确计算 = 111,204 tokens           预算 96,000 * (1-5%) = 91,200  -> 超出 19,  触发裁剪 [truncate] summarize_first: 14 chunk 并行摘要,耗时 6.9s,压缩后 24,860           + paragraph_rank 保留原文关键段 61,300           最终输入 86,160 tokens  (预算内) [forward] M-A 首字 1.6s,总耗时 22.4s,输出 1,842 tokens [meter] 计入 research 部门额度:input 86,160 / output 1,842

四、效果数据

这套东西上线三个多月,覆盖了网关上全部十七个应用。

超长报错率从峰值时期的百分之四点一降到百分之零点零六。剩下那一点点是配置了"直接拒绝"策略的应用,属于预期行为。模型侧因为超长返回错误的情况基本消失。

平均输入 token 数降了百分之三十一。这个降幅里有一半来自裁剪,另一半其实来自业务方看到了预算消耗曲线之后自己做的优化,有几个应用发现自己每次都把整个知识库的检索结果全塞进去,改成 top-5 之后省了一大截。可观测本身就是一种治理。

裁剪后的效果保持率,我们用两百份年报做了人工对照评测,裁剪后的总结跟未裁剪(用大窗口模型完整输入)的总结相比,关键信息覆盖率约百分之九十一,评分从四点三降到四点零(五分制)。业务方认为可以接受,毕竟另一个选项是直接失败。

配额超支次数从上线前的月均十一次降到一次,那一次是月底集中跑批,业务方提前申请了临时额度。

还有一个没预期到的收益:网关整体 P95 延迟降了约百分之十八,因为超长请求不再占着预处理资源和队列位置了。以上为项目复盘口径数据,已脱敏。

五、可复用经验总结

网关这层最容易被忽略的一件事,是它应该管"请求的重量",而不只是"请求的身份"。我们做了大半年的鉴权、路由、限流,一直没意识到上下文长度是一种需要被治理的资源,直到出了事。如果有类似架构,建议尽早把上下文预算纳入网关的管控范围,成本不高。

裁剪策略不要指望有一个通用最优解。头尾保留最简单但丢信息,段落打分需要有查询,摘要前置质量好但慢。三种我们都用上了,按场景配。第一版想用一套规则通吃,返工了。

先看能不能换模型,再考虑裁剪。这条是我们后来才加上的路由联动,收益不小。很多"超长"其实只是路由错了,换一个大窗口的实例就解决了,何必损失信息。

把可观测做到业务方能看见的地方,比加任何限制都有效。预算消耗曲线上线后,好几个应用自己就把输入优化了,我们一行代码都没帮他们改。管控台上那张曲线图的实际价值,比我们写的裁剪逻辑还高。

目前还有个短板:摘要前置对表格类内容处理得不好,年报里的财务报表被压缩后经常丢数字。我们暂时的办法是在 always_keep 里用正则把表格区域整块保住,治标不治本。表格的结构化抽取要单独做,还在排期里。