LLMは、1回の呼び出しが高コストで高レイテンシです。同じシステムプロンプトや長いコンテキストを毎回丸ごと処理させ、似た質問に毎回ゼロから生成させる——本番でこれを放置すると、請求書とp95レイテンシが静かに膨らみます。キャッシュは、LLMアプリのコストとレイテンシを最も安く削減できるレバーです。
ただし「キャッシュ」には性質の異なる2種類があり、使い分けが要点です。
- プロンプトキャッシュ(プロバイダ機能) — プロンプトの安定したプレフィックス(システム指示・長いコンテキスト)を再利用し、その入力トークンを割引する。完全一致ベース。
- セマンティックキャッシュ(アプリ層) — 埋め込みの類似度で「言い換えた似た質問」に過去の応答を返し、LLM呼び出し自体を省く。閾値ベース(=偽ヒットのリスクがある)。
この記事は、pgvectorによる本番RAGの知見をキャッシュに応用し、各プロバイダの公式ドキュメントを検証した上で、TypeScriptの実コードで両方を設計します。これは、レイテンシ・コスト面から信頼性・回復性を補完する「本番AI」の一手です。
⚠️ 本記事の価格・トークン閾値は2026年時点の公式値で、モデル名(Opus 4.8 / GPT-5系 / Gemini 3.x など)に紐づきます。頻繁に変わるため、実装前に必ず各出典を再確認してください。割引率はモデル依存です。
2種類のキャッシュの違い
| プロンプトキャッシュ | セマンティックキャッシュ | |
|---|---|---|
| 層 | プロバイダ(API機能) | アプリ(自前 / GPTCache 等) |
| 一致方法 | プレフィックスの完全一致 | 埋め込みの意味的類似(言い換えを拾う) |
| 効果 | 入力トークンを割引 | LLM呼び出しそのものを省略 |
| リスク | ほぼ無い(安全) | 偽ヒット(要・閾値/TTL/評価) |
| 向く用途 | 大きな固定コンテキスト(RAG・システム指示・few-shot) | FAQ・定型質問・言い換えの多い問い合わせ |
まず安全なプロンプトキャッシュから入れ、次にセマンティックキャッシュを慎重に足す——が実務の順序です。
プロンプトキャッシュ:静的コンテンツを「前」に置く
プロンプトキャッシュの効果は、プロンプトの構造で決まります。共通する原則は、変わらない大きな部分(システム指示・長いコンテキスト・few-shot例)をプロンプトの前に、変わる部分(ユーザー入力)を後ろに置くこと。プレフィックスが一致するほどキャッシュが効きます。
- OpenAI は自動です。コード変更なしに、一定長(1,024トークン)以上のプロンプトで効き、以降は128トークン単位でヒットします。静的コンテンツを前に置くだけで恩恵を受けられます。キャッシュ読み取りの割引はモデル依存(GPT-4o世代は約50%、GPT-5系は最大90%)。
- Anthropic は
cache_control(ephemeral)で明示します。最大4つのブレークポイント、既定TTLは5分(1時間オプションあり)。価格はキャッシュ書き込み1.25×(5分)/2×(1時間)、読み取り0.1×(基本入力比、2026年時点)。 - Google Gemini は明示的な
CachedContentAPI に加え、2.5系以降は暗黙キャッシュが既定で効きます(キャッシュ保存に時間課金がある点に注意)。
Vercel AI SDK なら、最も簡単なのは AI Gateway の自動キャッシュです。
import { generateText } from "ai";
// 最も簡単:AI Gateway の自動キャッシュ。プロバイダに応じ最適な戦略を適用する
// (Anthropic系は cache_control を注入、OpenAI/Google は暗黙キャッシュを利用)。
const { text } = await generateText({
model: "anthropic/claude-opus-4-8",
messages,
providerOptions: { gateway: { caching: "auto" } },
});
明示制御するなら、安定プレフィックスに cacheControl を置きます。
// 明示制御:静的コンテンツを"前"に置き、そこに cacheControl を付ける。
const { usage } = await generateText({
model: "anthropic/claude-opus-4-8",
messages: [
{
role: "system",
content: largeStaticContext, // 大きく・変わらない部分(RAG文脈やfew-shot)
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
},
{ role: "user", content: userQuestion }, // 変わる部分は後ろ
],
});
// usage.inputTokenDetails.cacheReadTokens でヒットを計測(読み取りは入力の約0.1×課金)
プロンプトキャッシュはほぼノーリスク(完全一致なので誤答は起きない)。まずここから入れるのが鉄則です。
セマンティックキャッシュ:呼び出しそのものを省く
一段踏み込むと、「似た質問には、そもそもLLMを呼ばない」——これがセマンティックキャッシュです。質問を埋め込みベクトルにし、過去の質問とコサイン類似度で照合。閾値を超えたら過去の応答を返します。プロンプトキャッシュが「完全一致のプレフィックス再利用」なのに対し、こちらは言い換え(paraphrase)まで拾えるのが強みです。
判定ロジック(閾値・鮮度)は純粋関数に分離してテスト容易にします。
// セマンティックキャッシュ:埋め込み類似で「言い換え」まで拾う。判定は純粋関数で分離。
interface CacheHit {
readonly answer: string;
readonly similarity: number; // 0..1(コサイン類似)
readonly ageMs: number;
}
// 純粋・テスト容易:閾値と鮮度(TTL)の両方を満たすヒットだけ採用する(SRP)。
function acceptCacheHit(hit: CacheHit | null, minSimilarity: number, ttlMs: number): boolean {
return hit !== null && hit.similarity >= minSimilarity && hit.ageMs <= ttlMs;
}
// cache-aside:ヒットなら即返す(LLM呼び出しを省く)、ミスなら生成して保存。
async function cachedGenerate(query: string, scope: string): Promise<string> {
const embedding = await embed(query);
// pgvector で最近傍を1件(scope でユーザー/地域を隔離し、他者の応答を返さない)
const hit = await cache.nearest(embedding, scope);
if (acceptCacheHit(hit, 0.95, 3_600_000)) return hit!.answer; // 高閾値で偽ヒットを抑制
const answer = await callLlm(query);
await cache.put({ query, embedding, answer, scope }); // ミス:保存して次回に備える
return answer;
}
セマンティックキャッシュのリスクと制御
セマンティックキャッシュの怖さは偽ヒットです。「2024年の売上は?」と「2025年の売上は?」は埋め込みが酷似しますが、正解は別。緩い閾値は誤答を量産します。制御は4点。
- 閾値 — 高めから(0.90〜0.95)始め、偽ヒット率を監視しながら調整(AWSも「閾値がヒット率と品質のトレードオフを決める」と明言)。
- TTL — データの揮発性で。リアルタイム(価格・在庫)は5〜15分、静的な事実は24時間が目安。長いTTLはヒット率を上げるが陳腐化リスクも上げる。
- スコープ — ユーザー・地域・商品IDでフィルタし、パーソナライズされた応答を他者に返さない。
- 無効化 — ソースデータ更新時に、該当エントリをコンテンツ起点で無効化する。
そしてヒットの品質を評価で測ること。オフライン評価に「キャッシュヒットが正しいか」を含め、偽ヒット率を回帰ゲートにします(LLMの評価・テスト)。定番のOSSは GPTCache で、閾値やベクトルストアを差し替えられます。
キャッシュしてはいけないケースも明確に。 常に最新性が要る、強くパーソナライズされる、監査上で毎回生成が要る——これらは偽ヒット/陳腐化のコストが便益を上回るため、素直に毎回呼びます。
可観測性:効果と副作用を測る
キャッシュは測って初めて安全に使えます。ヒット率・削減トークン/コスト・p95レイテンシを記録し、加えて偽ヒット率(評価由来)を監視します。「ヒット率は高いが偽ヒットも増えた=閾値が緩すぎ」のように、指標がチューニングを教えてくれます。土台は OpenTelemetryによる本番可観測性 を参照してください。なお、キャッシュはモデル/インフラの経済性(自前ホスト vs API)とは別レイヤーの最適化です。そちらは 生成AIのコスト設計 を参照してください。
導入チェックリスト
- プロンプトキャッシュ(まず安全策) — 静的コンテンツを前に置き、OpenAIは自動、Anthropicは cacheControl(or Gateway caching:'auto')を有効化したか。
- 構造 — 変わらない大きな部分を前、可変部分を後ろに置いているか。
- セマンティックキャッシュ(慎重に) — 高い類似度閾値(0.90〜)から始めているか。
- TTL — データの揮発性に応じて設定し、ソース更新で無効化しているか。
- スコープ — ユーザー/地域でヒットを隔離し、パーソナライズを漏らしていないか。
- 偽ヒット監視 — 評価にキャッシュヒットの正しさを含め、回帰ゲートにしているか。
- 非キャッシュ判断 — 最新性必須・強パーソナライズ・監査要件のクエリを除外したか。
- 可観測性 — ヒット率・削減コスト・レイテンシ・偽ヒット率を監視しているか。
まとめ
LLMアプリのキャッシュは、**「安全なプロンプトキャッシュを土台に、セマンティックキャッシュを慎重に重ねる」**のが定石です。
- プロンプトキャッシュは完全一致でほぼノーリスク。静的コンテンツを前に置くだけで入力コストが下がる(Anthropic読み取りは約0.1×、OpenAIは自動)。
- セマンティックキャッシュは呼び出しごと省ける強力な手段だが、偽ヒットが本質的リスク。高い閾値・TTL・スコープ・評価で管理する。
- 測って運用する。 ヒット率だけでなく偽ヒット率を監視し、キャッシュしない判断も設計に含める。
速く・安く・正確に——この3つは、キャッシュを正しく設計すれば同時に得られます。