
你很难在第一天就发现 LLM 调用的重复问题。上线一个 AI 翻译功能时请求量小单次返回几百毫秒费用也不起眼。直到某个星期账单突然翻倍你翻日志才发现同一篇 9000 字的合同摘要被同一个下游任务调了 11 次。真正贵的地方不是模型不会回答而是你的后端在反复问同一个问题。ModelGate 这个名字给我的第一感觉是它不是要挡住模型而是要建立一个“门禁”让每次调用都留下记录。它要解决的不是生成质量而是后端系统的调用治理问题——哪些 LLM 调用是重复的、哪些调用是昂贵的以及为什么它们会一次次发生。用一句话概括我的判断ModelGate 这类工具的价值不在“更快”而在让模型调用成本从不可见变成可见再从可见变成可收敛。文章后面所有内容都围绕这个判断展开。1. 真正要治理的不是模型能力而是不可见的重复和成本很多团队做 LLM 功能时第一版都是“把接口调通”。这当然没有错因为功能没跑通之前谈成本没有意义。问题是跑通之后很多人就没有再往前走一步没有去问同一个请求是否被多次发送同一个结果是否被反复计算1.1 单次调用不贵但重复调用是线性放大的一次 LLM 调用的费用由输入 token、输出 token 和模型单价决定。单个请求可能只要几分钱看起来好像无所谓。但如果这个请求被重复执行 100 次成本就是单次的 100 倍。更隐蔽的是重复调用往往不会在日志里显示为“报错”。它看起来像正常请求返回也正常只是同样的输入被反复送进模型。账单上涨不会触发接口报错所以很多团队直到看云账单时才意识到出了问题。这里有一个不算复杂的成本模型总成本 单次调用成本 × 调用次数单次调用成本可以通过模型单价和 token 数算出来。真正难以控制的是“调用次数”背后的业务逻辑。1.2 重复调用通常不是恶意攻击而是业务逻辑没有收敛从工程经验看后端里的重复 LLM 调用通常来自下面几类情况。第一同一个业务事件被多个流程分别处理。比如用户提交一条消息后系统要判断意图、生成摘要、再去做检索。不同模块各自调用一次模型输入里有一段很长且重复的公共历史对话。单独看每个模块都有必要合起来看就是浪费。第二异步任务没有幂等。队列消费失败后重新投递定时任务没有清理上一次的执行状态消息重复消费同一个记录被两个 worker 同时处理。于是同一份文本被反复送去生成摘要。第三前端行为引发重复请求。用户连续点击“生成”或者提交按钮没有置灰同样的 prompt 被发送到后端。后端如果没有做幂等控制不会知道这是同一个请求。第四prompt 模板里塞进了大量动态内容。比如把整个知识库片段、整封邮件、整份合同都放进上下文每次调用都一样。这种情况下调用本身可能没有错但上下文里绝大多数 token 是冗余的。这些场景都不是模型能力问题而是系统设计问题。ModelGate 这类工具本质上是在帮你把系统问题暴露出来。1.3 昂贵调用通常来自上下文失控而不是模型单价高单次调用昂贵不一定是因为选了大模型。很多时候是因为上下文太长、输出配置太浪费或者重试机制把一次请求变成了多次计费。举个例子。你让模型总结一份文档直接把 5 万字全文塞进 prompt。模型确实有能力理解但真正有价值的输入可能只需要前 5000 字再加上文档标题和关键章节。多出的 4.5 万字每次调用都在付费。再比如模型返回给用户只需要 200 字但你的 max_tokens 设置成了 4096。这不会直接导致多扣钱因为最终按实际 token 计费可如果模型在生成长文本时跑偏生成了大量无用内容输出成本就会明显上升。还有一种典型情况是超时重试。客户端请求模型时超时于是重新发一次请求。但服务端可能已经处理完第一次请求只是响应没有及时返回。结果就是一次用户请求背后产生了两次甚至更多次计费调用。这些成本很难通过“模型选择”解决必须通过调用链路的记录和观测来解决。2. ModelGate 的位置放在业务代码和大模型之间ModelGate 想做的工作翻译成工程语言是在所有 LLM 调用前面加一层统一门面让请求先经过它再到达模型供应商。这一层可以记录、识别、统计甚至拦截一些明显不合理的调用。2.1 Gate 的两种姿态先观察再拦截一个常见的误区是团队刚接入这类 gate 就想让它帮你拦截重复请求。我先劝你一句不要急。ModelGate 如果只有观察模式它不会影响业务。请求照常发出只是把调用信息记录下来。你先知道现状再决定要不要管。如果直接开启拦截模式风险很高。判断“重复”这件事很容易误伤尤其是在语义重复但需要不同结果的场景里。用户问“今天天气怎么样”这个问题昨天也有人问过但今天和昨天的天气完全不同命中缓存返回旧结果就是灾难。所以更稳妥的接入顺序是先开启观察模式记录所有调用。跑几天数据找出重复和昂贵请求的真实规模。针对具体场景设计拦截规则。小流量验证规则再逐步放开。这个顺序也适用于你自己设计同类工具。2.2 统一出口是这一切的前提无论 ModelGate 是独立服务还是集成在代码里的 SDK它都需要所有 LLM 请求从一个统一出口经过。如果你的代码直接散落着多个 provider SDK 调用gate 根本无能为力。这里需要做一次技术债清理把底层 HTTP 调用统一到一个 client 函数中。例如后端是 Python可以先把调用收口为chat_completion这样的统一函数后续再接入网关。# llm_gate.py def chat_completion(model, messages, **kwargs): started time.time() # 这里是统一的 provider 调用入口 response call_model_provider(modelmodel, messagesmessages, **kwargs) log_llm_call( modelmodel, messagesmessages, latency_ms(time.time() - started) * 1000, input_tokensresponse.usage.prompt_tokens, output_tokensresponse.usage.completion_tokens, ) return response这个统一入口不需要一开始做得很复杂能记录关键字段就够了。关键是所有调用都必须经过它。2.3 在中间层能做的不只是记录当请求统一经过 gate 层后你可以做三类事情。第一类是计量。计算每次调用的 token、延迟、成本和调用来源。第二类是识别。通过请求内容 hash、用户 ID、会话 ID、模型 ID 组合出重复特征在日志里标记重复请求。第三类是策略执行。如果某类请求已经被验证适合缓存可以在 gate 层直接返回缓存结果如果某段时间预算已经超了可以对非核心请求做降级。大部分团队一开始只需要第一类和第二类。策略执行要等到有足够数据后再说否则容易做出错误判断。3. 先跑通最小闭环从一次调用的日志开始如果 ModelGate 在你后端里还没有落地我并不建议你第一天就搭一套完整的可视化报表。更实际的做法是先设计一条可以被分析的调用日志把它接入统一出口跑一天数据再用脚本分析。3.1 记录一条可分析的调用日志日志至少要包含这些字段我用一张表来表示字段含义为什么需要timestamp调用发生时间用于时间窗口聚合trace_id一次用户请求的追踪 ID串联同一个上游请求里的多次调用session_id会话 ID分析同一会话内的重复行为user_id用户或租户 ID成本分摊和个性化判断model使用的模型名称计算价格和判断模型路由是否合理prompt_hash规范化后的消息摘要第一层精确重复检测input_tokens输入 token 数成本估算和上下文长度判断output_tokens输出 token 数成本估算和输出浪费判断latency_ms调用耗时发现超时和性能瓶颈cache_hit是否命中缓存判断缓存策略是否有效retry_count重试次数发现重试导致的多倍计费source调用来源模块定位哪个业务方产生最多浪费3.2 给 prompt 做一个稳定 hash要判断重复最常见的手段是给 prompt 计算一个稳定的 hash。注意不能直接对原始 messages 做 hash因为里面可能有动态时间、随机数、无意义的 trace 信息。一个简单做法是先对消息做一次归一化去掉会导致误判的字段再计算 hash。import hashlib import json def stable_hash(messages, ignore_keys(now, timestamp, request_id)): normalized [] for item in messages: if isinstance(item, dict): item {k: v for k, v in item.items() if k not in ignore_keys} normalized.append(item) payload json.dumps(normalized, sort_keysTrue, ensure_asciiFalse) return hashlib.sha256(payload.encode(utf-8)).hexdigest()这个函数帮助找到“完全相同的请求内容”。它会漏掉语义相似但字面上不完全相同的请求这没关系先解决最确定的问题。3.3 先做每天一次的离线分析如果你还没有可视化的 dashboard可以每天凌晨跑一个脚本统计昨天的调用日志。重点看三个指标按prompt_hash聚合调用次数最多的 Top 请求。按model加input_tokens output_tokens估算成本累计成本最高的 Top 请求。按source聚合看哪个模块产生了最多调用与成本。这类分析不需要很精准能看出 top 异常就足够。真正复杂的情况等到你已经处理完最明显的问题后再处理。3.4 日志本身不能影响主流程给 LLM 调用加日志听起来简单落地时有一点必须注意日志逻辑不能阻断或拖慢主流程。记录日志时如果发生异常不能抛出业务异常否则模型功能会直接失败。常见做法是用异步写入、本地队列或直接让log_llm_call吞掉异常。另一个风险是隐私。不要把所有原始 prompt 和 response 完整写入生产日志尤其当系统面向 C 端用户时消息里可能有敏感信息。更稳妥的做法是记录 hash、截断后的摘要、token 数和必要统计字段。原始内容只在需要排查的最小范围内保留并做好权限控制。4. 把重复找出来先精确后语义重复检测是 ModelGate 这类工具的核心能力。许多团队的误区是想一步到位做“语义重复识别”结果模型成本又增加一层而且效果还不稳定。正确的路径应该是分层检测。4.1 第一层基于规范化 Key 的精确去重先把这些组合作为精确重复的判断条件prompt_hashmodeltemperaturemax_tokens时间窗口用户或会话范围例如同一个用户在同一小时内提交了 5 次完全相同的总结请求且模型参数一致就可以判定为重复。即便不能直接拦截也应该在报表中标记出来。在接入 ModelGate 时我一般建议先跑一次精确去重的离线分析。这部分数据准确率高、解释成本低最适合给团队做第一轮治理。4.2 第二层针对模板化问题的语义去重当精确 hash 无法发现问题但你已经从报表里看到一些文本高度相似、成本又很高的调用这时才引入语义层面的近似检测。思路是将提示词或消息中的关键文本做 embedding然后计算向量相似度。相似度高于某个阈值时视为同一类请求。但这里有两个前置问题。一是 embedding 调用本身也有成本。如果每天有几十万次 LLM 调用你不可能全部做 embedding 再聚类。通常只对离线抽样或高成本请求做语义分析。二是阈值没有固定值。不同业务下0.92 和 0.98 的意义完全不同。你需要用小样本人工看一批结果确认阈值不会把“两个需要不同回答的问题”合并在一起。4.3 边界哪些“看起来重复”不能直接拦截不是所有相同问题都适合返回同一个答案。下面这些场景即使请求高度相似也不应该直接合并。实时性要求高的场景。查天气、查股价、查快递轨迹结果随时在变命中旧缓存会直接造成错误。个性化场景。同一个模板生成的“推荐方案”用户 A 和用户 B 的背景不同重复请求看起来相似但模型需要结合每次的用户画像重新推理。随机性要求高的场景。如果用户刻意希望每次生成不同内容比如文案创意直接拦截会破坏产品体验。不同业务上下文。两个不同的业务模块可能发送相同的文本但它们背后的指令和约束不同结果也就不同。所以ModelGate 的“重复检测”只应该定位成发现工具而不是自动执行工具。是否处理、怎么处理需要业务方案共同确定。5. 把昂贵找出来成本是一套估算不是账单模型供应商的账单通常滞后且聚合你很难从中看到某一次业务调用的成本。ModelGate 需要在请求发生时估算成本并把成本打回到业务维度上去。5.1 成本估算至少需要模型单价和 token 数估算核心公式很简单成本 (输入 token 数 / 1,000,000) × 输入单价 (输出 token 数 / 1,000,000) × 输出单价实际落地时要注意不同模型版本、不同上下文缓存模式、不同批处理接口单价都可能不同。价格表需要独立配置不能写死在业务代码里。PRICE_CONFIG { model-a: {input_price_per_million: 5.0, output_price_per_million: 15.0}, model-b: {input_price_per_million: 0.5, output_price_per_million: 1.5}, } def estimate_cost(model, input_tokens, output_tokens): price PRICE_CONFIG.get(model) if not price: return None input_cost input_tokens / 1_000_000 * price[input_price_per_million] output_cost output_tokens / 1_000_000 * price[output_price_per_million] return input_cost output_cost注意价格会变化配置要留出入口随时更新。估算值用于横向比较帮助定位异常它不等同于最终账单。5.2 昂贵并不只看单次调用还要看累计成本单次最贵的调用当然值得关注但它不一定是账单的大头。真正危险的是单次看起来不贵、调用次数却巨大的请求。比如单次成本只有 0.01 美元看起来完全不用在意。但如果一天被调用 10 万次成本就是 1000 美元。这种请求往往不是偶然而是某个循环、定时任务或高频接口造成的。所以在分析中至少看两个排序维度按“单次调用成本”降序找极端异常。按“聚合后总成本”降序找大盘贡献者。ModelGate 的报表如果只展示“单次最贵”会漏掉另一半真相。5.3 延迟、失败与重试也要一起看成本还有一个不怎么被计入但影响巨大的维度重试。一次调用如果因为网络超时被客户端重试 3 次最终只有 1 次成功但实际计费可能是 2 到 4 次。协议层有时无法从客户端精确判断服务端是否处理了请求。所以日志里一定要记录retry_count和每次重试的开始时间。如果发现大量请求的retry_count很高要先排查网络超时配置、连接池和上游限流而不是急着优化 prompt。另外延迟也是一个信号。模型延迟高时用户等不到结果就会刷新或再次点击这又会产生新的调用。所以一个“慢接口”不只会让用户体验变差还会从行为层面制造更多重复成本。6. 发现之后怎么办收敛重复与预算的实践路径工具把问题找出来只是第一步。真正有长期价值的是你基于这些报告把系统改到“不需要重复调用”的程度。6.1 从 Top 重复和 Top 成本开始治理拿到 ModelGate 第一份分析报告时不要试图一次性解决所有问题。我建议先挑出两个 Top 列表里的前三项逐个分析成因。处理时走一遍这个判断流程这个调用是用户明确触发的还是内部流程自动触发的它的输出结果会随时间变化吗如果结果可复用上游业务能接受多旧的数据如果不可复用是因为 prompt 里有动态内容还是因为模型本身有随机性如果用小模型或精简 prompt 能达到效果能不能切换这个流程可以帮助你将问题归入缓存、幂等、裁剪、路由或模型降级这几类方案。6.2 加缓存不是无脑缓存降低重复调用最直接的手段是结果缓存。但它有前提你清楚地定义了缓存 key、TTL 和生效范围。一个相对安全的缓存 key 结构是业务域:用户维度:模型:规范化请求 hashTTL 需要按业务容忍度设置。对于固定知识型问题可以是几小时甚至一天对于需要较新鲜结果的场景可能只适合几十秒。缓存命中时不代表一定正确。如果用户明确要求“不要用旧结论”你必须跳过缓存直接调用模型。6.3 从流量治理升级到预算治理当重复和昂贵调用已经被压缩一轮后ModelGate 的另一个价值才会体现它可以变成一个预算治理闸门。你可以设置日预算或小时预算。当成本超过阈值系统自动对低优先级调用做降级或告警。比如非核心推荐场景可以返回规则兜底而不是继续请求模型。这类能力需要模型调用方配合给每次调用标注优先级。否则 gate 层不知道哪些请求可以先被丢弃。从实践来看“设置预算上限并让系统自动收敛”是 LLM 后端长期运行的必要条件。模型调用不像传统 API它是没有固定账单预期的只有加一层预算控制才敢把功能放到生产环境。7. 如果接入后仍然觉得混乱按这张链路排查ModelGate 本身是一个观测和治理工具但它也会引入新问题。实际接完这类网关后团队经常遇到“为什么报表数据不对”“为什么有些重复没识别出来”的情况。这时不要瞎调按下面的链路一层层查。7.1 现象层先看异常的具体表现常见的现象有没有任何日志或指标。部分请求没有被统计到。报表中成本偏高或偏低。开启拦截后业务效果异常。网关本身导致接口延迟上升。先确认现象再决定下一步。不要直接怀疑模型供应商或业务代码。7.2 输入层检查消息、hash 和字段没有统计到部分请求优先检查这些请求是否真的经过了统一出口。如果业务代码里有人绕过了统一 client直接调用了 provider SDKgate 根本看不到。prompt_hash 没有统计出重复则要检查 messages 里是否包含动态字段。时间戳、随机数、trace_id 这些字段会让相同语义的请求产生不同 hash需要做归一化。成本字段缺失常见原因是 provider 返回的 usage 结构不一致或者模型版本不同导致字段签名不同。7.3 环境与架构层检查任务调度和调用路径如果重复调用来自异步任务不要只盯着 LLM 配置还要看任务调度。队列里同一个 job 是否被多个 worker 抢到定时任务是否因为上一次执行超时又启动了下一个实例多副本部署时内存级缓存天然无法共享。如果团队是用本地缓存给 LLM 结果做复用在多个副本下命中率会很低。要检查缓存是否应该放到 Redis 这类共享存储中。7.4 参数与业务需求层检查模型配置如果某个请求被判定为重复但你不确定是否可以缓存要把 temperature、max_tokens、system prompt 也放进判断维度。例如相同问题在 temperature0.2 和 temperature1.0 下产生的结果可能差异很大gate 默认会认为它们是两类请求。这是保守设计不是 bug。还要确认成本估算是否和实际计费口径一致。有些供应商对缓存输入 token 会打折如果你的价格表没有区分估算就可能偏高。7.5 工具边界层区分“工具 bug”和“使用方式问题”ModelGate 能发现重复不代表它能理解业务背后的潜台词。它无法知道“用户刚刚修改了资料不想再获得旧答案”这件事。所以当报表出现误判时不要急着说工具不行。先问是不是配置的忽略字段太多是不是缓存的 TTL 太长是不是把个性化场景也纳入了默认缓存把工具当成一个显眼但可配置的观测层而不是一个会替你思考的策略层使用体验会顺很多。8. 什么阶段适合 ModelGate什么阶段先不要上最后聊一聊边界。这类工具确实有用但不是所有项目都需要立刻引入复杂能力。8.1 适合什么团队如果你的后端已经满足下面几个条件接入 ModelGate 或参考它做一套内部方案会比较合适已经有稳定的 LLM 业务功能每天调用量上千次或更多。代码里已经有一个统一调用入口或者你愿意花钱花时间做统一改造。团队对成本敏感模型账单已经不能靠“拍脑袋”判断。有日志系统或可以快速接入日志系统。业务中有较多固定 prompt、定时任务、批量任务或重复性摘要场景。这种团队接入后通常能很快看到效果因为过往浪费已经积累在系统里只差一个“照妖镜”。8.2 不适合什么场景如果你的项目还在 demo 阶段每天只调几十次模型或者需求经常变化先不要投入做复杂的 go 网关、缓存和预算系统。更推荐的做法是手写一个简单的日志函数或者直接用原有链路追踪工具记录模型调用的 token 和成本。当调用量增长到让你开始焦虑时再上正式工具。另外如果你的业务逻辑高度个性化每次调用都必须依赖实时状态那么重复检测的价值会大打折扣。你仍然需要成本观测但“缓存去重”这层要谨慎。8.3 从内部脚本到工程化差的不是功能而是稳定流程自己写一个 LLM 调用分析脚本很简单难的是让它每天稳定跑、准确识别、能让团队达成共识并且不因为误报被丢弃。一个可用的内部方案至少要包含稳定的日志采集、按业务维度的聚合、价格配置、异常告警以及每周 review 的节奏。ModelGate 这类工具真正的长期价值不只是某一个重复拦截功能而是让团队养成一种习惯每一次模型调用都应被记录每一笔成本都应有归因每一个重复都应经过业务确认后处理。当你把模型调用看作一种需要治理的资源而不是一种无限供给的能力时LLM 应用才真正进入生产阶段。所以我的建议也很简单先别急着做复杂系统。从今天开始统一你后端的 LLM 调用入口记录每一次请求的关键字段把重复和昂贵的东西列出来。你会很快发现ModelGate 这个名字里最重要的不是 Model而是 Gate——你需要在每一笔模型费用流出去之前先给自己建一道看得见的闸门。