AI・生成AI

LLM の利用コストを下げる — 実測して効いた5つの手

生成AIを機能に組み込むと、利用が増えるにつれ費用が無視できなくなります。
実際に計測して、効果が大きかった順に整理します。

前提:機能別に計測する

まず「どの機能にいくらかかっているか」を出します。これが無いと手を打てません。

class ApiUsage(models.Model):
    feature = models.CharField(max_length=40)     # 機能名
    model = models.CharField(max_length=40)
    prompt_tokens = models.IntegerField()
    completion_tokens = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=True)

大抵、1〜2機能が費用の大半を占めています。そこだけ対処すれば済みます。

1. キャッシュ(効果:最大)

同じ入力に同じ処理をしているケースが、想像以上に多くあります。

key = "llm:" + sha256(f"{feature}|{normalize(prompt)}".encode()).hexdigest()
if hit := cache.get(key):
    return hit
result = call_llm(prompt)
cache.set(key, result, 60 * 60 * 24)

normalize() で空白や大小文字を揃えると、ヒット率が上がります。
分類やタグ付けのような決まった処理では、これだけで大幅に減りました。

2. モデルを使い分ける(効果:大)

すべてを高性能モデルで処理する必要はありません。

処理 モデル
分類・抽出・整形 軽量モデル
要約 中間
複雑な推論・文章生成 高性能モデル

軽量モデルで済む処理を高性能モデルに投げているのが、
最もよくある無駄でした。

3. 入力を短くする(効果:中〜大)

長い文脈を毎回渡すのは高くつきます。

  • 会話履歴は直近N件に絞る
  • 検索結果は上位k件だけ渡す(k を増やしても精度は頭打ちになりやすい)
  • 定型の指示は簡潔に
messages = messages[-6:]        # 直近3往復だけ
context = "\n".join(hits[:4])   # 上位4件だけ

実測すると、渡す件数を8→4にしても精度がほぼ変わらないことがよくあります。

4. 出力を短くする(効果:中)

出力トークンは入力より単価が高いのが一般的です。

max_tokens = 300        # 上限を設ける

「簡潔に」と指示するだけでなく、上限を機械的にかけるのが確実です。

5. 前処理で呼ばずに済ませる(効果:状況次第)

そもそも呼ばなくてよいケースを弾きます。

if len(text) < 20:
    return None            # 短すぎる入力は処理しない
if is_duplicate(text):
    return previous_result

LLM を呼ばないのが、最も安いという当たり前の結論です。

上限を設ける

対策を入れても、想定外の使われ方で跳ねることがあります。
日次・月次の上限を設け、超えたら止めます。

if today_cost() > DAILY_LIMIT:
    raise BudgetExceeded("本日の上限に達しました")

まとめ

  1. まず機能別に計測する(大半は1〜2機能が占めている)
  2. キャッシュ(正規化してヒット率を上げる)
  3. 処理の難易度でモデルを使い分ける
  4. 入力の件数・履歴を実測して削る
  5. 出力に上限をかける
  6. 日次の上限で暴走を止める