系列: 与 AI 一起学编程

今天,每一个调用 LLM 的地方都加上了提示词长度护栏

一次成本飙升,一次审计,以及加在每一个 LLM 调用点上的提示词长度护栏。

Every LLM Caller, One Guard Per-caller token budgets, truncation hierarchy, telemetry, CI enforcement UNBOUNDED 5 callers, no length cap CAPPED budgeted, logged, bounded LLMCallGuard Truncate & log Coarse fallback Fail loud CI regression gate
一道共享护栏,把悄无声息的成本放大变成了响亮而有边界的失败。

一个没有上限的提示词,就是一次等着发生的成本放大攻击。RakuAI 把提示词长度纪律当作安全基础设施来对待——固化在构建里,而不是寄望于人去记住。

这个运行时调用 LLM 的地方,比我原本追踪到的要多。有那个显而易见的地方(驱动基于语音的 AR 体验的 XRAssistantService)。有那个不那么显而易见的地方(NPC 行为层,世界中一个智能体的「大脑」有一部分就是一次 LLM 调用)。还有那个最不显而易见的地方(开始悄悄渗入运行时构建的开发期工具:模式校验器、调试分析器、一个自动的playtest 总结器)。

这些地方在过去六个月里都是各自独立编写的,每一个都在 PR 时经过了评审。直到这个周末之前,它们之中没有一个对提示词长度有统一的纪律。

这篇文章要讲的是,这为什么会成为一个问题,审计发现了什么,以及新护栏长什么样。

问题是怎么浮出水面的

触发点是一次常规的成本监控检查。查看每日 LLM API 花费时,发现某一个特定的开发环境的花费是其他环境的十五倍。同样数量的智能体,同样的测试负载,同样的模型。成本却是十五倍。

追查这次飙升,最终定位到了 playtest 总结器的代码路径。这个总结器接收一次已完成的 playtest 会话,把相关的场景状态打包进一个提示词,发送给一个云端 LLM,拿回一份该会话的结构化分析。这个函数最初是在场景还很小的时候写的,「相关场景状态」是对发生之事的一份结构化摘要。随着时间推移,这份摘要变大了。某个特定的开发环境一直在跑很长的 playtest,产出了巨大的摘要。总结器把这些摘要原封不动地发给了模型,模型按 token 计费。

对这一个调用点的修复很直白:给摘要长度设一个上限,需要时截断,截断发生时记录日志。

我接下来问的那个更深层的问题是:运行时里还有多少其他调用点存在同样的漏洞?我去找了。这次审计才是真正的工作量所在。

审计发现了什么

运行时里有十一处调用 LLM 的地方。这十一处之中:

  • 两处有显式的长度检查,并且在溢出时行为合理(截断、记录日志、用更小的上下文重试)。这些没问题。
  • 五处完全没有长度检查,调用方给什么就发什么。
  • 三处有长度检查,但宽松到没什么用(十万个 token,远高于任何理智的使用量,但也远低于灾难性的量级)。
  • 一处是一个本不该出现在生产二进制文件里的调试路径;它有长度检查,但通过一个调试标志就能轻易绕过。

那五处完全没有长度检查的是最紧急的。它们分别是:playtest 总结器(已经查出来了)、NPC 行为大脑(如果一个 NPC 能观察一个复杂场景,这里可能变得非常巨大)、模式校验器(可能被喂进任意文件)、世界状态描述器(可能要描述一个任意大的世界区块),以及某个面向开发者的诊断工具。

这些地方每一个都是在各自独立的 PR 里落地的,每个 PR 单独看都合情合理。而「每一个 LLM 调用点都要有长度上限」这份汇总起来的纪律,却不是任何人的分内之事,所以也就没有人去做。

护栏长什么样

周六早上落地了一小块基础设施,并在这一整天里被应用到了每一个调用点上。

一个共享的 LLMCallGuard 工具。 现在每一个和 LLM 对话的地方都要经过这个工具,而不是直接构造请求。这个工具接收一个提示词模板、一份上下文载荷和一个目标模型。它强制执行一个长度上限(按模型可配置,根据该模型文档记录的上下文窗口设有合理的默认值)。对于预算内的正常情形,它以 INFO 级别记录实际使用的提示词长度;需要截断时以 WARNING 级别记录;截断后仍无法满足上限时以 ERROR 级别记录。

逐调用者的预算。 运行时里的每一个 LLM 调用点,现在都有一个显式的、按次调用计算的 token 预算。这个预算低于模型的上下文窗口,因为我们既想给响应留出余量,也想在模型悄无声息失败之前就大声地失败。预算范围从几千个 token(模式校验器)到两万个 token(playtest 总结器,配有一个能防止最初那次事故重演的真实上限)。

遥测数据。 现在每一次 LLM 调用都会记录提示词大小、响应大小、模型、消耗的预算和延迟。正是这份遥测数据,让我能在账单到来之前就注意到下一次事故的苗头。

一套失效模式层级。 当某个调用点的载荷超出预算时,这个工具会按顺序尝试一系列修复方案。首先,它尝试智能截断(保留最近的上下文,丢弃最旧的,保持系统提示词完整)。其次,如果智能截断仍然放不下,它会尝试更粗粒度的截断(整段丢弃)。第三,如果任何截断都放不下,它会响亮地失败,给出一个清晰的错误,而不是发送一个模型会直接拒绝的超大请求。这套层级意味着大多数情况都能优雅恢复,边缘情况会以可观测的方式失败,而且没有任何一个调用点会再发送一个无上限的载荷。

一道防止回归的 CI 检查。 新写的、要和 LLM 对话的代码必须经过 LLMCallGuard。这个 CI 扫描会找出任何绕过该工具、直接调用 LLM 的新代码,并标记出对应的 PR。这和几个周末前那次安全审计针对管理端点用的模式是一样的:把纪律固化在构建层面,而不是寄托在评审上。

我学到了什么

三件事。

没有人负责的纪律,就是不会发生的纪律。 这十一个 LLM 调用点,每一个在 PR 时看起来都合情合理。它们汇总起来的整体行为之所以没被评审到,是因为没有人拥有这个横切关注点。修复办法是把这个横切关注点变成一块每个调用点都必须经过的基础设施,这样一来,这份纪律就变得不可能被遗忘了。

成本是一个安全问题。 我之前一直把「无上限的提示词」当作一个正确性或健壮性问题来看待。这次开发环境的成本飙升教会了我把它当作一个安全问题来看待。一个能够影响运行时发出的 LLM 调用内容的攻击者,可以让运营方产生任意的成本。同样的防御手段(长度上限、预算强制执行、遥测)既能防御健壮性故障,也能防御成本放大攻击。

按节奏审计。 今天这次审计发现了五个 PR 评审漏掉的漏洞。下一次审计会发现别的。针对某个特定横切关注点、按计划运行一次审计,是我目前找到的唯一可靠的、能让评审漏掉的东西浮出水面的办法。

合作伙伴和构建者应该从中得到什么

如果你在生产代码里跑任何调用 LLM 的东西,而且还没有针对提示词长度纪律审计过每一个调用点,去做一次。审计的工作量不大,发现问题的概率不小。今天做这件事的成本,远小于事故发生之后再做的成本。

如果你是一家构建会调用其他 LLM 的智能体的 AI 实验室,值得优化的指标是「这个智能体在构造一个无上限的提示词时,是否会主动标记出来」。大多数智能体不会。会这么做的那些,才是我在正经工作中信得过的那些。

如果你正在评估一个引擎是否值得合作,而这个引擎与云端 LLM 有集成,去问问提示词长度纪律的问题。正确的答案是「每一个调用点都经过一个共享工具,配有逐调用者的预算、遥测数据和一道 CI 护栏」。错误的答案是「我们还没遇到过那个问题」。

周六下午。运行时里的每一个 LLM 调用点现在都经过同一道护栏。下一次审计的节奏已经排上了日历。

回去继续构建。

在一个能守住每一次 LLM 调用的运行时上构建

RakuAI 是你的模型在生产环境中驱动的 AI 原生空间运行时——提示词长度预算、遥测数据和 CI 强制执行,都固化在边界层面。看看有纪律的基础设施能为你的技术栈解锁什么。

← 所有文章