シリーズ: AI とともにコードを学ぶ

今日、すべてのLLM呼び出し元にプロンプト長ガードが入った

コスト急増、監査、そしてすべてのLLM呼び出し元へのプロンプト長ガード。

すべてのLLM呼び出し元に、1つのガード 呼び出し元ごとのトークン予算、トランケーション階層、テレメトリ、CIによる強制 無制限 5つの呼び出し元、長さ上限なし 上限付き 予算化・記録・境界あり LLMCallGuard トランケートして記録 粗いフォールバック 大きな音で失敗 CIの退行ゲート
1つの共有ガードが、静かなコスト増幅を、大きな音を立てる境界付きの失敗へと変える。

無制限のプロンプトは、起きるのを待っているコスト増幅攻撃だ。RakuAIはプロンプト長の規律をセキュリティインフラとして扱う——記憶に頼るのではなく、ビルドの中で強制する。

ランタイムは、私が把握していたよりも多くの場所でLLMを呼び出していた。分かりやすいものもある(音声ベースのARエクスペリエンスを駆動するXRAssistantService)。あまり分かりやすくないものもある(NPCの振る舞い層で、世界の中のエージェントの頭脳の一部がLLM呼び出しになっている)。最も分かりにくいものもある(ランタイムのビルドに忍び込み始めた開発時ツール群——スキーマバリデータ、デバッグアナライザ、自動プレイテスト要約ツール)。

これらの場所はそれぞれ、過去6か月にわたって独立して書かれてきた。それぞれがPRの時点でレビューされていた。今週末までは、どれ一つとしてプロンプト長についての統一された規律を持っていなかった。

この投稿は、それがなぜ問題になったのか、監査で何が見つかったのか、そして新しいガードレールがどのようなものかについて書くものだ。

問題が表面化した経緯

きっかけは日常的なコスト監視チェックだった。日々のLLM API支出を見ていたところ、ある特定の開発環境が、他の環境の15倍を消費していた。エージェント数も同じ、テストワークロードも同じ、モデルも同じ。コストは15倍。

この急増を追跡すると、プレイテスト要約ツールのコードパスに行き着いた。この要約ツールは、完了したプレイテストセッションを受け取り、関連するシーン状態をプロンプトに詰め込み、クラウドLLMに送信し、セッションの構造化された分析結果を受け取る。この関数は、シーンがまだ小さかった頃に書かれたものだった。「関連するシーン状態」とは、起きたことの構造化された要約だった。時間が経つにつれ、この要約は肥大化していった。ある特定の開発環境では長時間のプレイテストが行われており、それが巨大な要約を生み出していた。要約ツールは、その要約をチェックなしにモデルへ送っていた。モデルはトークン単位で課金していた。

その単一の呼び出し元についての修正は明白だった。要約の長さに上限を設ける。必要ならトランケートする。トランケーションが発生したらログに残す。

より深い問いは、その次に私が発した問いだった。ランタイム内の他にいくつの呼び出し元が同じ脆弱性を抱えているのか? 私はそれを探しに行った。監査こそが本当の仕事だった。

監査で見つかったこと

ランタイム内でLLMを呼び出している場所は11か所。そのうち11か所の内訳は以下の通り。

  • 2か所は明示的な長さチェックを持ち、オーバーフロー時の挙動も妥当だった(トランケート、記録、より小さいコンテキストでの再試行)。これらは問題なかった。
  • 5か所はまったく長さチェックを持っていなかった。呼び出し元から渡されたものをそのまま送っていた。
  • 3か所は長さチェックを持っていたが、緩すぎて役に立たなかった(10万トークン、まともな使用量をはるかに超えているが、破滅的なレベルには少し足りない)。
  • 1か所は本来本番バイナリに含まれるべきではないデバッグ経路だった。長さチェックはあったが、デバッグフラグで簡単に迂回できた。

長さチェックがまったくなかった5か所こそが急を要するものだった。それらが対象としていたのは、プレイテスト要約ツール(すでに特定済み)、NPCの振る舞いの頭脳(NPCが複雑なシーンを観察できる場合、潜在的に巨大になり得る)、スキーマバリデータ(任意のファイルを渡され得る)、ワールド状態の説明ツール(任意に大きなワールドチャンクを説明し得る)、そして開発者向け診断ツールの一つだった。

これらはそれぞれ別々のPRで着地していた。それぞれのPRは単体で見れば妥当だった。「すべてのLLM呼び出し元が長さ上限を持つ」という集約的な規律は、誰の仕事にもなっていなかった。だから誰もそれをやっていなかった。

ガードはどのようなものか

土曜日の朝、小さなインフラが着地し、その日のうちにすべての呼び出し元に適用された。

共有のLLMCallGuardユーティリティ。 LLMと話すすべての場所は、直接リクエストを組み立てる代わりに、今やこのユーティリティを経由するようになった。このユーティリティはプロンプトテンプレート、コンテキストペイロード、そして対象モデルを受け取る。長さ上限を強制する(モデルごとに設定可能で、そのモデルの文書化されたコンテキストウィンドウに基づく妥当なデフォルト値を持つ)。予算内に収まっている場合はINFOレベルで、トランケートせざるを得なかった場合はWARNINGレベルで、トランケーションしても上限に収まらなかった場合はERRORレベルで、実際に使用されたプロンプト長を記録する。

呼び出し元ごとの予算。 ランタイム内の各LLM呼び出し元は、今や明示的な呼び出しごとのトークン予算を持つ。この予算はモデルのコンテキストウィンドウよりも小さい。応答のための余白を残しておきたいのと、モデルが静かに失敗する前に大きな音を立てて失敗させたいからだ。予算は数千トークン(スキーマバリデータ)から2万トークン(プレイテスト要約ツール。元のインシデントを防ぐ実際の上限付き)までさまざまだ。

テレメトリ。 すべてのLLM呼び出しは今、プロンプトサイズ、応答サイズ、モデル、消費した予算、そしてレイテンシを記録する。このテレメトリこそが、請求書が届く前に次のインシデントに気づかせてくれるものだ。

失敗モードの階層。 ある呼び出し元のペイロードが予算を超過したとき、このユーティリティは順番に一連の修正を試みる。まず、賢くトランケートしようとする(最も新しいコンテキストを保持し、最も古いものを落とし、システムプロンプトは無傷のまま保つ)。次に、賢いトランケーションでもまだ収まらない場合は、より粗いトランケーションを試みる(セクション全体を落とす)。最後に、どのトランケーションでも収まらない場合は、モデルが拒否するであろう巨大なリクエストを送るのではなく、明確なエラーで大きな音を立てて失敗する。この階層により、ほとんどのケースは優雅に回復し、エッジケースは観測可能な形で失敗し、どの呼び出し元も無制限のペイロードを送ることは決してなくなる。

退行を防ぐCIチェック。 LLMと話す新しいコードはLLMCallGuardを経由しなければならない。CIスキャンは、このユーティリティを迂回する新しい直接LLM呼び出しを見つけ、そのPRにフラグを立てる。このパターンは、数週間前の週末にセキュリティ監査が管理者エンドポイントに対して使ったのと同じものだ。規律をレビュー時ではなくビルド時に強制する。

学んだこと

3つある。

誰の仕事でもない規律は、実行されない規律である。 11のLLM呼び出し元は、それぞれPR時点では妥当だった。集合的な振る舞いがレビューされなかったのは、この横断的な関心事を誰も所有していなかったからだ。修正は、この横断的な関心事を、すべての呼び出し元が通らなければならないインフラの一片にすることだった。それにより、この規律を忘れることが不可能になる。

コストはセキュリティの関心事である。 私は「無制限のプロンプト」を正しさや堅牢性の問題として考えていた。開発環境でのコスト急増は、それをセキュリティの問題として考えるよう私に教えてくれた。ランタイムからのLLM呼び出しの中身に影響を与えられる攻撃者は、運用者に対して任意のコストを積み上げることができる。同じ防御策(長さ上限、予算の強制、テレメトリ)が、堅牢性の失敗とコスト増幅攻撃の両方から守ってくれる。

定期的な監査を。 今日の監査は、PRレビューが見逃していた5つの脆弱性を見つけた。次の監査は他のものを見つけるだろう。特定の横断的な関心事について定期的な監査パスを実行するという規律は、レビューが見逃すものを表面化させる唯一の信頼できる方法だと私は分かった。

パートナーとビルダーがここから汲み取るべきこと

もしあなたが本番コードからLLMを呼び出す何かを運用していて、すべての呼び出し元をプロンプト長の規律について監査したことがないなら、やるべきだ。監査は小さい。見つかる可能性は高い。今日それをやるコストは、インシデントの後にやるコストよりもずっと小さい。

もしあなたが他のLLMを呼び出すエージェントを構築しているAIラボなら、最適化すべき指標は「エージェントが無制限のプロンプトを組み立てているときにそれをフラグとして立てるかどうか」だ。ほとんどのエージェントはそうしない。そうするエージェントこそが、私が重要な仕事で信頼するものだ。

もしあなたがパートナーシップのためにエンジンを評価していて、そのエンジンがクラウドLLMと統合しているなら、プロンプト長の規律について尋ねてみるとよい。正しい答えは「すべての呼び出し元が、呼び出し元ごとの予算とテレメトリとCIガードを備えた共有ユーティリティを経由する」だ。間違った答えは「その問題にはまだ遭遇していません」だ。

土曜日の午後。ランタイム内のすべてのLLM呼び出し元が、今や同じガードを経由する。次の監査サイクルもカレンダーに入っている。

さあ、開発に戻ろう。

すべてのLLM呼び出しを守るランタイムの上に構築する

RakuAIは、あなたのモデルが本番環境で駆動するAIネイティブな空間ランタイムだ——プロンプト長の予算、テレメトリ、CIによる強制が境界に組み込まれている。あなたのスタックにとって、規律あるインフラが何を解放するかを見てほしい。

← すべての記事