チャットボットの請求額を眺めていると、同じ答えを何度も買い直しているような気分になります。
「返品したい」「返品はできますか」「送り返す方法を教えて」は、利用者にとっては同じ質問です。
ところが従来のキャッシュは文字列が一字でも違えば別物として扱うため、この三つはすべて新しいリクエストとしてモデルに届きます。
そこで登場するのが、意味の近さでキャッシュを引くセマンティックキャッシュという考え方です。
文字列一致のキャッシュが効かない理由
まず、普通のキャッシュでは歯が立たない理由から整理します。
一般的なキャッシュは、リクエストの文字列そのものをキーにして答えを保存します。
同じ URL、同じクエリ、同じパラメータであれば、二度目以降は保存済みの値を返すだけで済む仕組みです。
しかし自然文の入力では、同じ意図の質問が無限に言い換えられるので、キーが一致する確率がほとんどありません。
Redis の解説でも、従来型のキャッシュは完全一致でしか引けず、言い換えられた質問を拾えないと整理されています。
出典: What is semantic caching?(Redis)
つまり問題は、キャッシュの速さではなく、キーの作り方そのものにありました。
埋め込みと類似度で「同じ質問」を判定する
セマンティックキャッシュは、キーを文字列からベクトルに置き換えます。
処理の流れは、大きく四つの段階に分かれます。
- 埋め込み: 入力文を埋め込みモデルでベクトルに変換する
- 類似検索: 過去の質問ベクトルとコサイン類似度を比べる
- キャッシュヒット: 閾値を超える近さなら保存済みの答えを即返す
- キャッシュミス: 超えなければモデルを呼び、質問と答えの組を新たに保存する
判定の勘どころは閾値(しきい値)の置き方にあります。
先ほどの Redis の解説では、実運用で使われる値としておおむね 0.85〜0.95 が挙げられ、まずは 0.90〜0.95 から始めて誤ヒットの割合を見ながら詰めていく進め方が示されていました。
擬似コードで書くと、アプリ側の分岐はこれくらい素朴なものになります。
vec = embed(question)
hit = cache.search(vec, threshold=0.92) # 近いものを1件だけ探す
if hit:
return hit.answer # LLM を呼ばずに返す
answer = llm.chat(question) # 略
cache.put(vec, answer, ttl=3600)
return answer
呼び出しの前に一段はさむだけなので、既存のアプリへ後から差し込みやすいのも利点でしょう。
オープンソースでは GPTCache がこの構造をそのまま部品に分けており、埋め込み生成・キャッシュ保管・ベクトル検索・類似度の評価・追い出しがモジュールとして並んでいます。
出典: GPTCache(zilliztech/GPTCache)
プロンプトキャッシュとの違い
名前が似ているせいで混ざりやすいのが、モデル提供側のプロンプトキャッシュです。
あちらは同じ前置き(プレフィックス)の計算結果をモデル側で再利用する機能で、呼び出し自体は毎回発生します。
対してセマンティックキャッシュはアプリケーション側に置く層であり、当たればモデルを一度も呼ばずに済む点が決定的に違います。
守備範囲が重ならないので、長い共通指示はプロンプトキャッシュに、繰り返される質問はセマンティックキャッシュに、と両方を重ねて効かせられます。
前者の仕組みは別の記事で整理しているので、あわせて読むと輪郭がはっきりするはずです。
外したときに何が起きるか
便利な反面、外し方を決めずに入れると事故の温床になる仕組みでもあります。
閾値を下げすぎると、「似ているだけの別の質問」に過去の答えを返してしまいます。
料金・在庫・順位のように変わり続ける情報は、有効期限を短く切るか、元データの更新に合わせて捨てる設計が要ります。
利用者ごとに答えが変わる問い合わせは、そもそもキャッシュに載せてはいけません。
誤ヒットの割合を記録して監視するところまで含めて、ようやく運用に乗る道具です。
Redis の解説も、閾値の調整だけで誤ヒットが収まらない場合は構成そのものを見直すべきだと釘を刺しています。
最後に
セマンティックキャッシュが効くのは、同じことを言い換えて何度も聞かれる場面だけです。
逆に言えば、問い合わせ窓口や社内ナレッジ検索のように質問が偏る用途では、費用と待ち時間の両方に素直に効いてきます。
まずは誤ヒットを測る仕組みとセットで小さく試すのが、無理のない入り方だと私は考えています。
質問ごとに呼ぶモデルを変える発想と組み合わせると、費用の削りどころがもう一段見えてきます。
以上です。












コメントを残す