很多做 AI 业务的公司,市场还没跑通,先把未来半年的 API 预算烧穿了。老板拍着桌子问:这些请求到底是从哪来的?一查日志,发现大量语义几乎一模一样的请求反复调用上游大模型接口,个别租户一天能发出几万次请求,把成本和内容安全全带崩了。这不是某一家的问题,而是 AI 应用规模化之后迟早要面对的一道坎。今天这篇,我就把在企业内部落地 AI API 网关时最核心的三板斧拆开讲清楚:动态语义缓存、敏感词熔断、多租户 Token 预算治理,顺便把我踩过的一些坑也一并交代了。这篇东西适合正在自建 AI 平台、或者马上要接大模型的企业后端开发者、架构师,以及被成本账单折磨得睡不着觉的负责人。
1. 先搞清楚痛在哪里:AI API 网关到底解决什么问题
1.1 三大痛点拆解
先说成本。大模型 API 是典型的按 token 计费资源,这种计费模式意味着每一次请求都在花钱,跟传统云主机的包月/按量完全不是一个逻辑。真正让成本失控的,不是用户量涨了多少,而是请求的“重复性”。同一个问题的不同问法、同一个知识库条目的反复检索、同一批 prompt 模板在多个租户之间被高频复用,这些请求到了模型服务商那边,每一个都按完整 token 计费。某公司接了大模型做客服助手后,上线一个月,账单翻了八倍,点开明细一看,Top 10 的高频问题占了 60% 以上的调用量,其中绝大多数是“换个说法问同一件事”。
第二个痛点是内容安全。大模型本身的输出是概率性的,它对某些恶意构造的问题会表现出不可控的边界。更重要的是,不同业务场景对内容的容忍度完全不一样,企业接入大模型后,需要在自己的业务边界内做统一治理,而不是完全依赖模型服务商自带的对齐。一旦不合规内容从你平台漏出去,任何中间层解释都太苍白。提前在网关层做检测和熔断,是代价最小的拦截位置。
第三个痛点是多租户的资源争抢。只要平台对多个业务方开放,一定会出现某个新接入的租户突然抽风,把整季度的预算烧光,其他业务正常请求全部报错。没有预算隔离、没有配额管理,整个网关就是“公地悲剧”:每个人都在抢占公共资源,最后谁都用不好。
1.2 为什么不能只靠现成的 Web 网关
很多人第一反应是“用 Nginx 或者现成的微服务网关不就行了”?我一开始也这么想过,但实际做下来发现完全不够。传统网关关注的是流量、路由、鉴权、灰度,它根本无感知请求内容本身。而 AI 网关有三个传统网关无法覆盖的点:
- 语义层缓存:传统网关只能按照 URL 精确缓存,可 AI 请求是“同一个问题、几十种问法”,必须做语义级的相关性判断。
- Token 计量与预算控制:请求的计费对象不是 HTTP 请求次数,而是 prompt 和 completion 的 token 长度,网关必须实时累积用量并和上游服务商对账。
- 流式响应安全拦截:大模型普遍用 SSE 流式返回,内容是一块一块吐出来的,传统网关没法对流块做中间态安全判断。
所以,如果企业里的 AI 调用已经不止一个场景,或者下游会接入多个模型厂商,就必须把这块能力从业务代码里抽出来,放到一个独立的、专门为 LLM 调用设计的网关层。这也是本文的核心前提。
2. 动态语义缓存:把重复成本打到最低
2.1 传统缓存为什么在 AI 场景失灵
先做一个对比。传统接口缓存的核心逻辑是:请求参数完全一样,就把之前的结果直接返回。这套逻辑套到 AI 请求上,命中率会低到什么程度?我见过一个真实案例,某团队给客服接口做了精确 key 缓存,上线跑了一周,命中率只有不到 3%。为什么?因为用户的自然语言表达天然是分散的,几乎不会有人一字不差地问同一个问题。哪怕语义一模一样,换个标点、换个语气词,key 就变了,然后又要重新调用模型。
更麻烦的是,大模型生成结果的计费跟输入长度强相关。同样是问“退货流程是什么”,输入 prompt 长度接近,但输出结果可能差异很大。如果不能把“等价的请求”在到达上游之前拦掉,成本就没有压缩空间。
2.2 语义缓存的核心实现思路
动态语义缓存的基本思路很简单:对请求的 prompt 做向量化,用向量相似度判断“这句话跟之前哪句话是同一个意思”,如果足够接近,就返回缓存结果,不调用上游模型。
整个链路分四步:
- 请求进入网关:解析出当前请求的 prompt 文本、参数(temperature 等)、租户信息。
- Prompt 向量化:调用一个 embedding 模型,把 prompt 转成向量。这个模型必须是轻量级的,不能为了省一次大模型调用反而引入新的高成本依赖。
- 向量检索:去向量库里找跟当前向量最接近的 TopK 记录,计算余弦相似度。
- 阈值判断:相似度超过预设阈值(比如 0.93),判定为语义等价,直接返回缓存响应;否则走正常上游调用,完成后把新响应写回向量库。
向量库的选择上,我建议根据规模来定。数据量在百万条以下,直接用开源向量库(比如 FAISS 或者一个带向量支持的数据库)足够;如果已经是千万级、需要分布式和高可用,再上分布式向量数据库不迟。不要一开始就把架构搞复杂,语义缓存真正的难点不在检索性能,而在“什么条件下该命中”和“缓存数据怎么管理”。
2.3 相似度阈值、缓存条件与一致性
很多团队第一次做语义缓存,最容易挂在“阈值怎么定”上。阈值太高,缓存放成了摆设;阈值太低,把两个意思完全不一样的问题判成同一个,用户拿到的就是驴唇不对马嘴的回答。我自己的经验是:
- 通用客服问答领域,0.92~0.95 是一个比较稳的区间。
- 垂直专业领域(比如医学、法律),语义表达差异大,建议阈值上浮到 0.95 以上,宁可不命中,不能给错答案。
- 如果业务本身就强调实时信息,比如行情问答,这类请求不应该缓存,要按规则绕过语义缓存层。
还要注意的是,同一个 prompt 在不同条件下不能直接复用缓存。至少要考虑三组隔离维度:
- 租户隔离:某个租户专属知识库的答案,绝对不能缓存给别的租户。
- 参数隔离:prompt 相同但 temperature 不同,输出风格会不同;流式输出和一次性输出的格式也不同,建议把采样参数并入向量记录的元数据里。
- 用户身份隔离:如果 prompt 里拼了当前用户的订单、上下文,这类请求本质上是“个性化请求”,不能缓存。
# 语义缓存命中判断的示意逻辑 def semantic_cache_lookup(tenant_id, prompt, temperature): vector = embed(prompt) candidates = vector_store.search(vector, top_k=5) for item in candidates: # 隔离维度校验:租户一致、采样参数一致 if item.tenant_id != tenant_id or item.temperature != temperature: continue if cosine_similarity(vector, item.vector) >= threshold: return item.cached_response # 命中 return None2.4 缓存命中的计量与写入策略
缓存命中之后,有一个细节特别容易被忽略:这次请求到底算多少 token 消耗?
我见过一个团队,缓存命中率做得很好看,但成本账单没怎么降,查了半天才发现,网关在命中后直接把缓存的完整响应记成了“本次生成消耗”,结果对账的时候上游账单和网关账单对不上。正确做法是:缓存命中时,token 消耗不能再向上游对齐,应该按“本地服务成本”单独记账。比如缓存命中不产生 prompt 和 completion token 费用,但会产生向量检索和 embedding 的费用,这两者要分开核算,否则账目永远是乱的。
缓存写入策略上,建议做到三点:
- 只缓存稳定的、非个性化的响应。实时数据和个性化结果要显式标记跳过缓存。
- 设置过期时间。即使在客服领域,知识库也可能变更,建议 TTL 默认设 24 小时,变更频繁的业务压缩到 1 小时。
- 淘汰策略和写回时机配合。不要在每次命中时反复改写缓存时间戳,做成分桶刷新,避免热点 key 造成缓存服务本身被打爆。
我这里再给一个真实的模拟数据。某客服场景,三个常见问题类目切换成语义缓存后,稳定跑了两个星期,缓存命中率大约 42%,配合把非缓存请求的 prompt 做瘦身,整体 API 成本下降了 35% 左右。注意,这个数字不是所有场景都适用,文档问答、知识检索类场景受益最大;如果业务是高比例的一次性长文本生成,语义缓存能帮你省的就非常有限。
3. 敏感词熔断:把内容事故挡在业务边界之前
3.1 为什么不能只信任模型自带的安全对齐
每次聊到内容安全,总有人说“大模型厂商不是已经做了对齐吗”?这想法在自用阶段问题不大,但到了企业级开放场景,完全靠不住。原因有三个:
- 提示注入:用户可以通过精心构造的 prompt 引导模型越过既有规则,这种攻击方式更新速度快,模型自身的防线不能覆盖所有变体。
- 输出非确定性:同样的 prompt 这次安全、下次可能就在边界线上,模型的安全对齐不是形式化验证。
- 企业责任边界:平台方对自身业务输出的合规责任不会因为“模型厂商已经过滤过”而转移。出了事,矛头一定指向你的平台。
所以,企业级网关必须在自己的边界内做“请求侧 + 响应侧”的双重检测,并且当违规比例异常升高时,能自动熔断,防止局势失控。
3.2 双路检测架构:请求侧拦截,响应侧兜底
请求侧检测的任务,是在 prompt 进入上游模型之前,判断它是否包含恶意注入、是否试图诱导模型输出违规内容。响应侧检测的任务,是在模型输出返回给用户之前,对流式内容做实时扫描。
这个架构里,“双路”不是摆设,而是两条线各自解决不同的问题。请求侧解决的是“不能让这种问题进来”,比如批量撞库式的违规 prompt;响应侧解决的是“万一模型还是输出出来了,不能让它流到用户端”。
具体落地时,我建议不要只依赖一套敏感词库,而是三层检测叠加:
- 规则层:正则和词库匹配,速度快,适合拦截带明显特征的违规内容,这一层做第一道闸,延迟控制在微秒级。
- 分类器层:训练/用一个小模型做文本分类,判断整体文本的主题是否越界,这一层能覆盖变体表达。
- 人工审核层:对双层模型判定“模糊”的结果,放入异步人工审核队列,宁可延迟,不可错放。
响应侧检测还有一个难点就是流式响应。大模型普遍用 SSE 流式返回,用户看到的是一个个 token 蹦出来。这时候不能等整个流结束再一起检测,那样体验会非常差,但又不能每个 token 都送检(成本太高,延迟也扛不住)。
我的做法是“积攒-检测-放行”三段式策略:
- 维护一个流式缓冲区,积累到一定长度(比如 64 个 token)或者一定时间窗口(比如 200 毫秒)就积攒一次检测。
- 检测通过的内容直接放行并清空缓冲区;检测可疑的内容,暂缓放行,继续积累上下文再二次判断。
- 最终判定违规的,立即中断当前流式连接,并对已经发送出去的内容做审计记录。
3.3 熔断状态机与降级策略
敏感词熔断不是简单的“检测到违规就拦一次”,而是一个完整的自我保护状态机。为什么要做熔断而不是单纯拦截?因为在某些攻击场景下,比如有组织地批量注入,你每拦截一次,对方就换一个花样继续打,如果只是单次拦截,网关会变得非常被动,更危险的是上游模型的调用量还在继续烧。
熔断的核心逻辑是:短时间窗口内违规率超过阈值,自动触发保护动作。
以我的项目经验为例,熔断触发条件和恢复条件可以这样配置:
| 参数 | 示例值 | 说明 |
|---|---|---|
| 窗口时长 | 60 秒 | 统计周期 |
| 违规率阈值 | 30% | 窗口内违规请求数 / 总请求数 |
| 最小触发量 | 20 次 | 防止低基数下的误触发 |
| 熔断持续时间 | 5 分钟 | 熔断后的冷却时间 |
| 半开状态流量 | 10% | 恢复探测期的放行比例 |
状态机是三态的:关闭 → 开启 → 半开启。正常时是关闭状态,只有窗口内违规指标达标才切到开启状态。开启状态下,网关不再把请求转发给上游模型,而是直接返回一条预设的安全响应,同时把请求原文、租户信息、判定类别记录到审计日志。冷却期结束后切到半开启状态,只放行一小部分流量,观察违规率是否回落到安全线以下,稳定了再回到关闭状态。
至于降级策略,要区分两类结果:
- 请求侧拦截:直接返回“当前提问无法处理”之类的兜底文案,不调用上游。
- 响应侧触发:立即断开流式连接,已经生成的内容做截断脱敏,并且触发一条告警。
这里我个人推荐再加一个“人工代答”选项:当某类问题频繁触发敏感词熔断,说明平台业务范围可能正被恶意试探,此时除了熔断,还应该通知运营介入,必要时把这类问题整体转入人工客服通道。
3.4 误杀与漏放的平衡
内容安全实施过程中,最头疼的不是漏放,而是误杀。特别是在健康、教育、职场等相对敏感的领域,正常业务内容本身就包含大量“边缘词汇”。如果直接用一套通用敏感词库去套,你会发现很多咨询类型的请求被拦掉,用户满意度直线下降。
我踩过的坑是:一开始部署了一个采购来的词库,覆盖面很广,结果上线当天误杀率接近 15%,最典型的是把“如何应对工作中的精神压力”这类正常提问拦了。后来花了两个迭代才把误杀率压下来,核心手段有三招:
- 白名单机制:某些类型的正常词在合法上下文内出现时,跳过敏感判定。
- 上下文规则:不能只靠单词匹配,要结合前后文,比如“暴力”出现在新闻报道语境和出现在提问语境,处置策略完全不同。
- 误杀样本回流:每周把被误杀的请求导出来人工打标,回滚修正分类器,实现“越用越准”。
这里必须强调一下,安全的优先级永远高于误杀。宁可让正常用户多等一次人工审核,也不能放走任何一条真正违规的内容。误杀可以通过迭代优化逐步收敛,漏放的风险是不可逆的。
4. 多租户 Token 预算治理:把成本管到每个租户头上
4.1 预算治理的粒度设计
多租户环境里,如果所有人的调用都从同一个池子里扣钱,一定会出现一个问题:某个租户的恶意或异常流量把公共预算烧光,其他所有人跟着遭殃。Token 预算治理,本质上是把“成本消耗”这件事从感受不到的资源,变成每个租户头上清清楚楚的硬限额。
在设计预算粒度时,我建议至少分成三层:
- 总预算:整个平台在某个模型服务商账户下的总额度上限,这是最后一道保险。
- 租户级日预算:每个租户每天最多消耗多少 token,超了就不能再调。
- 请求级限流:每个租户每秒最多多少次请求、每次请求最多多少个 token。
这里的核心是“预算”和“限流”要联动,而不能只做限流。限流解决的是瞬时冲击,预算解决的是长期消耗。一个租户可能请求速率并不高,但每一个请求都带着超长上下文,积累下来一天的 token 消耗反而是别人的几十倍,这种情况只靠 QPS 限流完全管不住。
4.2 Token 计量模型与实际成本拆分
Token 预算治理必须先解决“怎么算账”的问题。AI 请求的费用主要由三部分组成:prompt tokens(输入)、completion tokens(输出),以及部分厂商会单独计费的缓存读取。网关在做预算扣减时,不能只记一个总 token 数,而是要把这几个维度分开统计。
举个例子,一个租户的日预算是 10 万 token,他发了一个 2000 token 的 prompt,模型返回了 800 token 的结果,那么本次消耗应该是 2800 token,而不是某个笼统的数字。如果要更精确,还应该区分“实时生成”和“缓存命中”,缓存命中的响应不应该从租户的生成预算里扣全量 token。
这里有一个很实用的经验:在网关里建一张 token 计量明细表,每一次请求都记一条明细,包含租户 ID、请求 ID、模型类型、prompt token、completion token、是否命中缓存、是否计费。有了这张表,后面做成本回溯、账单拆分、异常检测都有据可依。
# Token 预算扣减的示意逻辑 def deduct_tenant_budget(tenant_id, request_tokens, response_tokens): used = get_today_usage(tenant_id) daily_limit = get_tenant_daily_limit(tenant_id) cost = request_tokens + response_tokens if used + cost > daily_limit: # 超额:默认拒绝并返回配额不足 return {"allow": False, "reason": "daily_token_budget_exceeded"} add_today_usage(tenant_id, cost) return {"allow": True, "remaining": daily_limit - used - cost}4.3 限流算法与优先级调控
限流算法上,我见过不少人直接套用令牌桶,但对 AI 场景来说,令牌桶模型有个天然盲区:它假设每个请求消耗的“资源”是等价的。可 AI 请求之间差异巨大,一个 50 token 的短请求和一个 8000 token 的长请求,成本差了百倍。所以更合理的做法是“双桶模型”:
- 第一个桶按请求数做令牌限流,控制每秒/每分钟的请求并发量。
- 第二个桶按token 总量做速率控制,控制单位时间内的消耗速率。
两个桶都通过,请求才放行。这套方案实现不算复杂,但能很好地应对“低请求数、高 token 量”的消耗型租户。
优先级调控方面,建议在预算分配时引入权重。常见的 SaaS 商业模式下,不同租户购买的服务等级不同,可以把租户分成高优先、普通、低优先三个等级:
- 高优先租户:预算单独隔离,限流阈值高,即使全局紧张也要保。
- 普通租户:共享池但按配额隔离,超预算即拒绝。
- 低优先租户:允许“借用”空闲预算,但全局紧张时先被降级。
最后要说的是动态调整。预算配置不能是写死的。比如某租户平时用量平稳,但遇到大促活动时需要临时扩容,网关需要支持通过配置中心动态调整预算和限流参数。这里的关键是设置一个“预算报警线”,比如用掉 70% 就告警,用到 90% 再告警一次,让运维有足够的时间处理,不能等超额了才知道。
4.4 计费对账与成本回溯
预算治理最后一个环节是对账。网关侧记录了租户消耗,但模型服务商侧也会出一份账单,两边必须对得上,否则要么预算形同虚设,要么财务数据失真。
对账时注意三个容易产生差异的点:
- 错误重试:上游返回 5xx,网关自动重试,重试产生的 token 消耗怎么归属?建议统一记录为原请求 ID 的子条目,并纳入租户消耗。
- 流式中断:SSE 流式响应中途被网关截断(比如触发了安全熔断),已经生成的 token 到底算不算消耗?不同厂商规则不同,必须按厂商的计费口径对齐。
- 缓存命中与正则匹配:有些模型服务商对缓存命中打折计费,有些完全不计费,要在对账逻辑里单独区分。
我个人的做法是每天凌晨跑一遍对账任务,把网关侧的计费明细汇总和模型服务商账单做差额比对,超过 1% 的差异就自动拉出来人工复核。别小看这一步,到月底财务拿着两份对不齐的账单来找流程问题的时候,再想补救就晚了。
5. 常见问题排查实录与避坑指南
5.1 语义缓存命中率一直上不去
我遇到过几次缓存命中率长期个位数的情况,排查下来无非几个原因。阈值设置不合理是最常见的,很多人一上来直接把阈值拉到 0.99,那基本等于关闭缓存。建议先放开阈值到 0.90,把命中的样本导出,人工看一下相似度分布,再逐步收紧。还有一类问题是 embedding 模型和业务领域不匹配。通用 embedding 模型对专业术语的理解能力有限,如果业务是法律、医疗这类垂直领域,需要评估是否要换成领域微调的模型。
向量检索的索引参数也有影响。如果使用的是量化索引,召回精度会下降,导致相似度计算偏差。建议把向量维度、索引类型、查询 TopK 都纳入排查范围。
5.2 误杀率居高不下
误杀率高的大多数原因不是模型不行,而是词库太粗糙。采购来的通用词库往往不考虑业务语境,上线前一定要人工抽样评估。另一个常见原因是只用了规则层,没有加分类器层。规则层对“这个词在句子里做什么角色”完全无感知,必须用分类器结合上下文做二次判断。
这里分享一个实操小技巧:把每次误杀样本单独存到一个数据集,每周做一次回归测试。你会发现模型和规则会随着迭代越来越好用,而不是越改越乱。
5.3 预算告警风暴与配额计算偏差
告警风暴通常发生在两类场景:一是租户并发突然放大,二是流式请求的 token 统计口径不一致。前者要通过“告警聚合”来解决,同一租户同一原因只发一条通知,避免运维被消息淹没。后者需要统一全链路的 token 计算方式,网关统计的 token 数必须和实际发送给上游的文本保持一致,不能用估算值。
特别提醒一点:很多模型服务商在流式返回的最后一帧才有 token 用量的准确数值,网关侧在做实时预算扣减时,可以先按模型名估算,收到最终帧后再做“多退少补”的修正。不做这一步,预算扣减偏差会越来越大。
5.4 网关自身的高可用与可观测性
网关是流量入口,本身就要求高可用。我的底线是网关不能成为单点故障源:至少两副本部署,全部无状态,向量检索和 token 计数这类状态放到外部存储。同时,所有关键路径都要记录结构化日志,包含租户 ID、请求 ID、模型服务商、token 消耗、缓存命中情况、安全判定结果。这些数据不仅是排查问题的依据,也是后续做成本预测和容量规划的基础。
我最后给一个落地顺序建议:第一周先做多租户 Token 预算治理和结构化日志,把成本失控这个最致命的问题按住;第二周再上双路敏感词熔断,确保平台合规底线;两件事稳定后再做动态语义缓存,进一步把成本打下来。先控风险,再讲优化,这个顺序最不容易翻车。
动态语义缓存、敏感词熔断、多租户预算治理,这三块能力做扎实之后,AI 网关就不再是一个简单的“反向代理”,而是企业 AI 平台的成本阀和安全闸。后续你还可以在这个底座上继续拓展负载均衡、模型路由、多厂商容灾这些能力,但前提是底层的成本和安全地基已经稳了。我的衷心建议是:不要一上来就追求大而全,把这三件事中的任何一件做扎实,都能让你的平台比大多数同类活得久得多。