Algomatic Tech Blog

Algomaticの開発チームによる Tech Blog です

Claudeのprompt cachingを活用する

LLM APIの最強の一角であるAnthropic Claudeprompt cachingが実装されました。簡単に言うとプロンプトの先頭から指定の位置までをキャッシュしてくれて、キャッシュを利用できた場合はAPI使用料金と、APIでかかる処理時間が大幅に改善するというものです。

キャッシュ書き込みではコストが1.25倍(つまり25%アップ)になってしまいますが、キャッシュ読み込みではコストが1/10(ただしHaikuの場合だけは$0.25/1Mtokensが$0.03になるので厳密には違う)になります。

速度に関しても、コストのうち処理時間が支配的なLLMにおいて値段が1/10になることから察する通りの速度がでます。

LLMの使い方に依存するものの、prompt cachingを使いこなせば、少なくない事業インパクトをもたらす事は間違いありません。

Google Geminiにも同様の機能であるcontext cachingが実装されていましたが、こちらは色々と使い勝手が悪く、ユースケースが極めて限られるものでしたが、Claudeのprompt cachingはとても使い勝手が良いので、Geminiのcontext cachingを検討したけど使わないという結論に至った方も、是非prompt cachingを再考すべきです。

この記事ではprompt cachingの使い方と、応用例についてまとめてみます。

使い方

prompt cachingは、ベータ用のヘッダ "anthropic-beta": "prompt-caching-2024-07-31"を付与して、プロンプトの先頭から cache_control で指定した位置までをキャッシュするものです。とても簡単ですね。将来的にはベータではなくなるはずなので、ヘッダの指定は不要になるはずです。

"system": [
  {
    "type": "text", 
    "text": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
  },
  {
    "type": "text", 
    "text": "<the entire contents of Pride and Prejudice>",
    "cache_control": {"type": "ephemeral"}
  }
],
"messages": [
  {
    "role": "user",
    "content": "Analyze the major themes in Pride and Prejudice."
  }
]

これは公式ドキュメントにあるサンプルのJSON表現です。このケースならば system のコンテキストがキャッシュされます。

ただし、実はこのサンプルはちょっとおかしくて、prompt cachingはsonnet(とまだ対応してないけどopus)なら1024トークンで、haikuなら2048トークンが下限値で、下限値を下回るとキャッシュがされない仕様なので、このサンプルだと、特にキャッシュされることはありません。

"usage": {
    "input_tokens":62,
    "cache_creation_input_tokens":0,
    "cache_read_input_tokens":0,
    "output_tokens":371
}

usagecache_creation_input_tokens が1.25倍のコストになるキャッシュ書き込みで cache_read_input_tokens が1/10になるキャッシュ読み込みをしたトークン数です。 input_tokens が62なので1024には到底足りません。そのため、1024tokensに足るようにキャッシュをさせる必要があります。

ちなみに、この機能はまだβテスト段階なので、仕様は今後変更になるかもしれません。実際に筆者が遭遇したタイミングでは、トークン数が1024を下回るとエラーが帰ってきていたんですが、今試すとそうならないようになっています。さすがにそれでは使い勝手が悪かったからでしょうか。

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "The message up to and including the first cache-control block must be at least 1024 tokens. Found: 138."
  }
}

現時点で筆者が確認している制約は他に cache_control には type: "ephemeral" しか指定することはできないことと、 cache_control は4つまでしか仕込めないらしい、というものがありますが、その制約も今後どうなるかはわかりません。

応用例

prompt cachingの応用例は色々ありえます。

たとえば、一般的なチャットアプリであれば、マルチターン応答と呼ばれる機能、つまり会話を継続できる能力を持っていますが、キャッシュを活用できる典型例です。

  1. 最初のLLM呼び出しを全部キャッシュする(system + user)
  2. 次の会話では、先ほどのキャッシュを利用しつつ新しく cache_control を追加する(1+assistant+user)
  3. それを繰り返す

大きなコンテキストを与えてデータ分析タスクをするような場合でも活用できる可能性はあります。一度だけしかLLMを使わない事が確定しているケースなら駄目ですが、複数回呼び出すケースなら、prompt cachingの恩恵を受けることができます。

たとえば分析タスクに関しても、複雑な分析を一度投げるだけよりも、簡単な分析を複数回投げる事で実現できることもあります。

同じプロンプトを複数回投げて値を合成するという方法もあります。単純なtrue/false判定くらいであれば、temperatureをある程度の値にして、雑に10回投げてその回数が多い方を採用するだけで精度向上が図れます。

より複雑なタスクの場合は、さらにチェック用のプロンプトを走らせると良いでしょう。どの回答がもっともらしいか?の判定をさせるのです。チェック項目を用意してそれぞれのチェック項目を達成できた数で判定させる方法がありますが、「どの回答がもっともそれっぽい?」みたいに聞いてみるだけでも雑に判定は可能です。このように色々な方法があります。

1024トークンに満たないシステムメッセージの場合は意味がないですが、システムメッセージが長大であれば、それだけでprompt cachingが有効です。

他にも色々と応用例はあると思います。是非ともprompt cachingを活用して、高速化とコスト圧縮を試してみてください。

追記(08/19)

prompt cachingでは、先頭から指定位置までをキャッシュするため、プロンプト設計がとても重要になりそうです。

  1. システムメッセージ
  2. ユーザーメッセージだけど、どのLLM呼び出しでも共通なもの
  3. ユーザーメッセージで、いくつかのLLM読み出しで共通なもの
  4. 共通ではないもの

みたいな順番で並べる必要性がありますね。こうすると、複数レベルでキャッシュがしやすくなります。 この場合、1,2,3にそれぞれcache_controlを仕込むイメージです。ただし、1と2は、どう使い分けるかが悩ましいので、どちらか片方しかない、みたいなこともあるでしょう。