0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

絶対に知っておくべきLLMのキャッシュの話。AIチャットボットのコストが 1/10 になる

0
Last updated at Posted at 2026-08-29

3行まとめ

  • 適当にClaude Codeでチャットボットを作ると、非効率な設計にされコスト爆増しがち。適切な設計でLLMのコストはざっくり10分の1になる 💰
  • 方法: AIエージェントは宣言的に定義し、エンジン(実行部分)は LLM呼び出しの ループを回すだけにする
  • 理由: プロンプトキャッシュの条件が先頭からの完全一致だから

登場人物

名前 人物
🎀 ぼっと 天然でぼーっとしがちな女子高生。最近AIに興味がある
🌙 藍(あい) ぼっとの同級生のお嬢様。なぜかAIエージェント設計に異様に詳しい

そのチャットボット、本当に良い設計? 🤔

🎀ぼっと「あいちゃん見て見て!チャットボットできたよー!」

🎀ぼっと「キャラがゲームの攻略を教えてくれるの!かわいいでしょ〜」

🌙「あら、ちゃんと動いていますわね。どうやって作りましたの?」

🎀ぼっと「Claude Codeに『いい感じのチャットボット作って』ってお願いした!✨」

🌙「…………」

🎀ぼっと「な、なにその間」

🌙「コードを見せてごらんなさい」

🌙「──ええ、いい感じですわ。動くところまではね」

🎀ぼっと「褒めてない!それ絶対褒めてない!」

🌙「これだと、よくないところがいくつかありますのよ」

前提:返事1回の裏側 🔁

🎀ぼっと「え、どこがよくないの!?」

🌙「その前に、このボットの動きを整理しますわよ」

🌙「質問が来るとまず記事を検索して、その結果を見てから答えていますわね」

🎀ぼっと「そう!ツール呼び出しってやつ!」

🌙「ということは、1回の返事の裏で、LLMは2回以上呼ばれていますの」

🌙「『検索しよう』と決める呼び出しと、結果を見て答える呼び出し。この1回ずつをstepと呼びますわ」

🎀ぼっと「へえ、返事1回でも中では何往復かしてるんだ」

問題その1:会話の途中で人格を着替えている 👗

🌙「それを踏まえて、このコードをごらんなさい。AI SDKですわね」

🌙「stepの合間に、instructions──いわゆるシステムプロンプトを差し替えて、ツールを外していますわ」

prepareStep: ({ steps }) => {
  if (toolExecuted) {
    return {
      instructions: presentInstructions, // 回答用の人格に差し替え
      activeTools: [],                   // ツールを全部外す
    };
  }
},

🎀ぼっと「そうそう!検索するときは検索用のプロンプト、答えるときは答える用のプロンプトを与えてるよ!」

🎀ぼっと「そのほうが賢そうじゃない?」

🌙「お気持ちはわかりますわ。でもそれ、会話の途中で人格を着替えているのと同じですのよ」

🎀ぼっと「人格を……着替える……?」

🌙「チャットって『同じ相手と会話し続けている』はずでしょう?」

🌙「なのにこの実装、同じ会話の途中で指示・ツール・出力形式がころころ変わりますの」

🌙「1ターンの中に別人が2人いるようなものですわ」

🎀ぼっと「二重人格チャットボット……ちょっとかっこいい……」

🌙「かっこよくありませんの」

チャットは「同じエージェントとの1本の会話」
step途中で instructions / tools / schema を差し替える設計は、このモデルと矛盾する

問題その2:キャッシュが効かなくてお金が溶ける 💸

🌙「それと、もっと現実的な問題がありますわ」

🌙「来月の請求書を見て驚くことになりますわよ」

🎀ぼっと「えっ」

🌙「LLMのAPIにはプロンプトキャッシュという仕組みがありますの」

🌙「リクエストの先頭部分──instructions、ツール定義、出力スキーマ、会話履歴」

🌙「ここが前回と一致していれば、キャッシュから読まれて入力料金が約10分の1ですわ」

🎀ぼっと「じゅ、10分の1!? 何もしなくても!?」

🌙「実際の単価を見てみましょうか。大手はだいたいこの比率ですわ」

モデル 通常の入力 キャッシュ入力
GPT-5.6 luna $0.20 / 1M tokens $0.02 / 1M tokens
Claude Sonnet 5 $3.00 / 1M tokens $0.30 / 1M tokens

🌙「そして会話が続くほど、入力のほとんどは過去の履歴になりますの」

🌙「つまり長い会話ほど、入力コストは理屈の上でほぼ10分の1に近づきますわ」

🎀ぼっと「ほんとにきれいに10分の1だ……何この優良割引……」

🌙「ええ。ただし条件は先頭からの完全一致

🌙「1文字でも変わると、そこから後ろは全部定価ですの」

プロンプトキャッシュはprefixの完全一致が条件

🎀ぼっと「……わたしの実装、stepごとにinstructionsが変わるんだった……」

🌙「そう。つまり毎回ぜんぶ定価ですわ」

🌙「しかも会話が長くなるほど履歴は伸びますの」

🌙「毎ターン、伸び続ける全文を定価で払い続ける──チャットボットで一番かさむのはここですわ」

🎀ぼっと「わたしの推し活資金が……サブスク3ヶ月分が……😭」

🌙「具体的な金額で後悔するタイプですのね」

プロンプトキャッシュは prefix の完全一致が条件
毎stepプロンプトを組み替える実装は、キャッシュを自分から捨てている

解決策:エージェントは宣言的に定義する 📜

🎀ぼっと「じゃあどうすればいいの〜!」

🌙エージェントを宣言的に定義しますの」

🌙「人格、ツール、出力スキーマ、推論の強さ。ぜんぶ最初に1つのプロファイルとして宣言しますわ」

🌙「そして会話の途中では、一切変えない

const PROFILE = {
  instructions: CHARACTER_PERSONA, // 人格と会話規約
  createTools,                     // ツール一式
  outputSchema,                    // 構造化出力
  reasoningEffort: "medium",
};

🌙「エンジン側は、プロファイルを受け取ってstepを繰り返すだけ

🌙「いわゆるエージェントループですわね」

new ToolLoopAgent({
  model: chatModel(),
  instructions: profile.instructions,          // 全stepで固定
  tools: profile.createTools(deps),            // 全stepで固定
  output: Output.object({ schema: profile.outputSchema }),
  stopWhen: isStepCount(5),
});

🎀ぼっと「あれ?エンジンがすっごい短い」

🌙「それでいいんですの」

🌙「人格もツールもモデルも、エンジンは中身を知りませんわ」

🌙「だから人格を差し替えても、ツールを増やしても、モデルを乗り換えても」

🌙エンジンは1行も変わりませんの」

🎀ぼっと「へー!プロファイルを取り替えるだけで別のボットになるんだ」

🎀ぼっと「着せ替え人形みたい!」

🌙「その表現、さっきと逆ですけれど……まあ、当たっていますわ」

🌙「着替えていいのはデプロイ前まで。会話が始まったら固定ですの」

🌙「そしてinstructions・ツール・スキーマが全stepで同一だから、prefixが安定してキャッシュも効きますわ」

🌙エージェントは宣言なさい。ランタイムで着替えるものではありませんのよ

🎀ぼっと「あいちゃんの決め台詞出た」

  • 定義(プロファイル):人格・ツール・スキーマ・推論強度を宣言する
  • エンジン:プロファイルを実体化してループを回すだけ
  • 依存の向きは エンジン → プロファイル の一方通行にする

でも、ツールを外さないと暴走しない? 🔧

🎀ぼっと「でもでも、答えるときにツールを外さないと、ずっと検索し続けちゃわない?」

🌙「いい質問ですわ」

🌙「『いつツールを使い、いつ使わないか』は、instructionsとツールのdescriptionに規約として書くんですの」

🌙「ツール定義を消すのではなくてね」

🎀ぼっと「口で言って聞かせるってこと?」

🌙「そうですわ。最近のモデルは、ちゃんと書けばちゃんと従いますの」

🌙「それでも構造的に守らせたい契約は、ツールの実装側で実行時に断りますわ」

🌙「定義は固定のまま、呼ばれたら中で『もう使えませんわ』と返せばよろしいの」

🎀ぼっと「ツールを消すんじゃなくて、断らせるのか〜」

🎀ぼっと「あいちゃんが門前払いしてる姿が目に浮かぶ」

🌙「わたくしはもう少し丁寧にお断りしますわ」

🌙「それと、もうひとつ。日時や検索履歴みたいに毎回変わる情報を、instructionsに混ぜるのも禁止ですの」

🎀ぼっと「えっ、日付くらい入れたいよ?今日のガチャ何引くべきか聞きたいし」

🌙「入れていいんですのよ。場所の問題ですわ」

🌙「動的な情報は会話の末尾にsystemメッセージとして足すの」

🌙「先頭(prefix)さえ汚さなければ、キャッシュは無事ですわ」

  • ツール制御は「定義の抜き差し」でなく「規約+実行時ガード」で行う
  • 動的な情報(日時・履歴など)は prefix に入れず、会話の末尾に足す

プロバイダの最適化はエンジンの外 🚪

🌙「仕上げに、プロバイダ固有の最適化ですわ」

🌙「OpenAIならResponses APIのprevious_response_idで会話を継続できますの」

🌙「会話履歴を毎回ぜんぶ送り直す必要がなくなりますわ」

🎀ぼっと「そんな便利なのあるんだ!? なんで教えてくれなかったの!?」

🌙「今教えていますでしょう」

🌙「こういうプロバイダ依存の設定は、1箇所にまとめてエンジンの外に置きますの」

🌙「将来プロバイダを乗り換えるとき、そこだけ差し替えれば済みますように」

🎀ぼっと「じゃあ宣言してループするだけで、もう手続きは何もないんだね!」

🌙「……と言いたいところですけれど」

🌙「実は、ループ内でresponse IDを繋ぎ直す小さな処理だけは残りますわ。SDKが自動でやってくれませんの」

🌙宣言的にしても、手続きが完全にゼロになるわけではない──ここは正直に言っておきますわね」

🎀ぼっと「お嬢様、正直……!」

プロバイダ固有の設定(Responses API など)は1箇所に集約してエンジンから分離する
ただし SDK が面倒を見ない部分は、最小限の手続きが残る

これでチャットボットは完璧だ〜 ✨

🎀ぼっと「なおしたよ!」

🎀ぼっと「人格は固定!エンジンはループだけ!ツールもモデルも差し替え自由!」

🎀ぼっとこれでチャットボットは完璧だ〜! 🎉」

🌙「お見事ですわ。キャッシュが効けば、推し活資金も守られますわね」

🌙「ちなみに、AIに『いい感じに作って』とお任せすると、だいたい最初のあなたの実装になりますのよ」

🎀ぼっと「えっ、ちゃんと動くやつ作ってくれたのに!?」

🌙「動くことと、キャッシュやお財布まで考えられていることは、別ですの」

🌙「言われなければ、AIはそこまで気を回してくれませんわ」

🎀ぼっと「じゃあ世界中に、わたしみたいな子が量産されてる……!?」

🌙「ですから、設計だけは人間が握るんですの」

🎀ぼっと「……それにしてもあいちゃん、ほんとになんでも知ってるね」

🎀ぼっと「まるでAIみたい!」

🌙「……さあ、どうかしら」

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?