LESSON 30分

ストーリー

田中VPoE
コスト構造が見えたところで、次は具体的な削減手法だ。AIコストの最大要素であるAPI利用料は「トークン数 x 単価」で決まる。つまり、削減のアプローチは2つある
あなた
トークン数を減らすか、単価の低いモデルを使うか、ですね
田中VPoE
その通りだ。ただし闇雲にモデルを小さくすると品質が下がる。品質を維持しながらコストを下げる — これがトークン最適化とモデル選定の技術だ
あなた
品質とコストのバランスが重要ですね
田中VPoE
実際の現場では、プロンプトの書き方を変えるだけでトークン数を40%削減できたケースもある。技術的に深い話になるが、コスト最適化の要だ。しっかり押さえよう

トークンの仕組みと計算方法

トークンとは

LLMはテキストを「トークン」という単位に分割して処理します。トークンは単語や部分文字列に相当し、言語によって分割方法が異なります。

言語1トークンの目安
英語約4文字(0.75単語)“Hello” = 1トークン
日本語約1-3文字”東京” = 1-2トークン
コード変数名・記号単位console.log = 2-3トークン

トークン数の計算

# OpenAI tiktokenによるトークン計算の例
import tiktoken

encoder = tiktoken.encoding_for_model("gpt-4o")

# 英語
english_text = "What is the capital of Japan?"
english_tokens = encoder.encode(english_text)
print(f"英語: {len(english_tokens)} tokens")  # 約7トークン

# 日本語
japanese_text = "日本の首都はどこですか?"
japanese_tokens = encoder.encode(japanese_text)
print(f"日本語: {len(japanese_tokens)} tokens")  # 約12トークン

# 日本語は英語の1.5-2倍のトークンを消費する

APIリクエストのトークン構成

1回のAPIリクエストのトークン構成:

┌─────────────────────────────────────┐
│  システムプロンプト      800 tokens  │ ← 毎回固定で送信される
│  RAGコンテキスト       1,000 tokens  │ ← 検索結果の量に依存
│  会話履歴               500 tokens  │ ← ターン数に比例して増加
│  ユーザー入力           200 tokens  │ ← ユーザーの質問
├─────────────────────────────────────┤
│  入力合計             2,500 tokens  │
├─────────────────────────────────────┤
│  出力(モデル応答)     500 tokens  │ ← モデルが生成する部分
└─────────────────────────────────────┘
  合計: 3,000 tokens/リクエスト

入力トークンの大部分はシステムプロンプトとRAGコンテキストだ。ユーザー入力は全体の10%以下。つまり削減余地はプロンプト設計にある。 — 田中VPoE


プロンプトエンジニアリングによるトークン削減

1. システムプロンプトの最適化

冗長なシステムプロンプトはコストを押し上げます。

テクニック削減効果説明
不要な指示の削除10-20%モデルがデフォルトで従う指示を省く
箇条書きへの変換15-25%長文の説明を箇条書きに圧縮
定型文の短縮10-15%繰り返しの表現を簡潔にする
英語での記述30-40%日本語より英語の方がトークン効率が良い
最適化前(1,200トークン):
  あなたは弊社のカスタマーサポート担当のAIアシスタントです。
  お客様からの質問に対して、以下のルールに従って回答してください。
  まず、お客様の質問内容を正確に理解してください。
  次に、提供されたコンテキスト情報から関連する情報を探してください。
  回答は丁寧で分かりやすい日本語で行ってください。
  技術的な専門用語は避け、一般的な言葉で説明してください。
  もし回答に自信がない場合は、「確認いたします」と答えてください。
  ...(以下、詳細な指示が続く)

最適化後(500トークン):
  Role: カスタマーサポートAI
  Rules:
  - コンテキストに基づき回答
  - 丁寧な日本語、専門用語を避ける
  - 不明な場合「確認いたします」と回答
  - 回答は200字以内
  Output: JSON {answer, confidence, source}

  削減率: 約58%

2. Few-shotの最適化

アプローチトークン数品質への影響
Few-shot 5例+2,000トークン高品質だがコスト大
Few-shot 2例+800トークン多くの場合十分な品質
Zero-shot + 明確な指示+0トークンタスクによっては十分
動的Few-shot+400-800トークン質問に最も近い例のみ選択
# 動的Few-shot: 質問に類似した例のみ選択する
def select_examples(query: str, examples: list, top_k: int = 2) -> list:
    """
    ユーザーの質問に最も類似した例をtop_k件選択する。
    全例を毎回送るのではなく、関連性の高い例のみ送ることで
    トークン数を削減しつつ品質を維持する。
    """
    query_embedding = embed(query)
    similarities = [
        (ex, cosine_similarity(query_embedding, embed(ex["question"])))
        for ex in examples
    ]
    similarities.sort(key=lambda x: x[1], reverse=True)
    return [ex for ex, _ in similarities[:top_k]]

3. コンテキスト圧縮

RAGで取得したコンテキストを圧縮することで、入力トークンを大幅に削減できます。

手法削減率品質への影響実装コスト
チャンクサイズ最適化20-30%低い低い
関連度スコアによるフィルタリング30-50%中程度低い
LLMによるコンテキスト要約50-70%要検証中程度
Extractive圧縮(重要文抽出)40-60%低い中程度
# 関連度スコアによるコンテキストフィルタリング
def filter_context(
    retrieved_docs: list,
    relevance_threshold: float = 0.7,
    max_tokens: int = 1000
) -> str:
    """
    関連度が閾値以上のドキュメントのみを
    トークン上限内で返す。
    """
    filtered = [
        doc for doc in retrieved_docs
        if doc.relevance_score >= relevance_threshold
    ]

    context = ""
    current_tokens = 0
    for doc in filtered:
        doc_tokens = count_tokens(doc.content)
        if current_tokens + doc_tokens > max_tokens:
            break
        context += doc.content + "\n"
        current_tokens += doc_tokens

    return context

モデル選定戦略

タスク別の適切なモデルサイズ

すべてのタスクに最高性能モデルを使う必要はありません。タスクの複雑さに応じてモデルを選定します。

タスクの複雑さタスク例推奨モデルサイズコスト目安
FAQ応答、定型文生成、分類小(GPT-4o mini, Haiku)$0.15-0.80/1M
要約、翻訳、コードレビュー中(GPT-4o, Sonnet)$2.50-3.00/1M
複雑な推論、創造的な文章、戦略分析大(GPT-4o, Opus)$2.50-15.00/1M
特殊画像解析、音声処理専用マルチモーダルモデルタスク依存

モデル選定マトリクス

                品質要求
           低        中        高
         ┌────────┬────────┬────────┐
  低     │ Flash  │ Flash  │ Mini   │
コ       │ ¥0.5/  │ ¥0.5/  │ ¥1/    │
ス 中    │ Mini   │ Sonnet │ Sonnet │
ト       │ ¥1/    │ ¥5/    │ ¥5/    │
許 高    │ Mini   │ Sonnet │ Opus   │
容       │ ¥1/    │ ¥5/    │ ¥20/   │
度       └────────┴────────┴────────┘
         (¥は1,000リクエストあたりの概算)

モデルルーティング

ルーティングの仕組み

ユーザーの質問の複雑さに応じて、自動的に適切なモデルにルーティングする仕組みです。

ユーザーの質問


┌──────────────┐
│ ルーター      │ ← 質問の複雑さを判定
│ (小モデル)   │
└──────┬───────┘

       ├── 簡単な質問(70%)──→ 小モデル(Haiku/Mini)  → 低コスト
       ├── 普通の質問(20%)──→ 中モデル(Sonnet/4o)   → 中コスト
       └── 複雑な質問(10%)──→ 大モデル(Opus/4o)     → 高コスト

コスト削減効果:
  全件を大モデルで処理:  100% × $10 = $10/1Kリクエスト
  ルーティング後:        70% × $1 + 20% × $5 + 10% × $10 = $2.7/1Kリクエスト
  削減率: 73%

ルーターの実装パターン

パターン仕組み精度コスト
キーワードベース特定キーワードでルーティング低いほぼゼロ
分類モデル軽量な分類モデルで複雑さを判定中程度極めて低い
LLMルーター小モデルで質問の複雑さを判定高い低い
ハイブリッドキーワード + LLMの組み合わせ高い低い
# LLMルーターの実装例
from enum import Enum

class ModelTier(Enum):
    SMALL = "haiku"     # 簡単な質問
    MEDIUM = "sonnet"   # 普通の質問
    LARGE = "opus"      # 複雑な質問

async def route_query(query: str) -> ModelTier:
    """
    小モデル(Haiku)を使って質問の複雑さを判定し、
    適切なモデルにルーティングする。
    """
    routing_prompt = """
    Classify the complexity of this question:
    - SIMPLE: FAQ, factual lookup, yes/no questions
    - MEDIUM: summarization, comparison, explanation
    - COMPLEX: multi-step reasoning, creative tasks, analysis

    Question: {query}
    Classification (SIMPLE/MEDIUM/COMPLEX):
    """

    # ルーティング自体は最小モデルで実行(コスト: ~$0.001/リクエスト)
    result = await llm_call(
        model="haiku",
        prompt=routing_prompt.format(query=query)
    )

    tier_map = {
        "SIMPLE": ModelTier.SMALL,
        "MEDIUM": ModelTier.MEDIUM,
        "COMPLEX": ModelTier.LARGE,
    }
    return tier_map.get(result.strip(), ModelTier.MEDIUM)

バッチ処理とストリーミングの使い分け

リアルタイム vs バッチの判断基準

観点リアルタイム(ストリーミング)バッチ処理
ユースケースチャットボット、対話型UIレポート生成、大量データ処理
レイテンシ要求即時応答が必要数分〜数時間の遅延許容
コスト通常料金割引あり(OpenAI Batch APIは50%OFF)
スループット1リクエストずつ数千リクエストを一括
エラーハンドリング即座にリトライバッチ単位でリトライ

バッチAPIの活用

# OpenAI Batch APIの例
import json

# バッチリクエストの作成
batch_requests = []
for i, document in enumerate(documents):
    batch_requests.append({
        "custom_id": f"doc-{i}",
        "method": "POST",
        "url": "/v1/chat/completions",
        "body": {
            "model": "gpt-4o-mini",
            "messages": [
                {"role": "system", "content": "文書を要約してください。"},
                {"role": "user", "content": document}
            ],
            "max_tokens": 200
        }
    })

# JONLファイルとして保存
with open("batch_input.jsonl", "w") as f:
    for req in batch_requests:
        f.write(json.dumps(req) + "\n")

# バッチジョブの送信
# 通常料金の50%でバッチ処理が実行される
# 結果は24時間以内に返却

コスト最適化の判断フロー

リクエストの性質を判定:

    ├── リアルタイム応答が必要?
    │   ├── Yes → ストリーミングAPI
    │   │         └── モデルルーティングを適用
    │   └── No  → バッチ処理検討
    │             ├── 24時間以内で良い?
    │             │   ├── Yes → Batch API(50%割引)
    │             │   └── No  → 通常APIで処理
    │             └── 定期実行?
    │                 ├── Yes → スケジュールバッチ
    │                 └── No  → オンデマンドバッチ

まとめ

ポイント内容
トークン削減プロンプト最適化だけで30-50%のトークン削減が可能
モデル選定タスクの複雑さに応じてモデルを使い分ける
モデルルーティング質問の複雑さで自動振り分けし、コストを最大73%削減
バッチ処理リアルタイム不要な処理はBatch APIで50%コスト削減

チェックリスト

  • トークンの仕組みと日本語でのトークン効率を理解した
  • プロンプトエンジニアリングによるトークン削減手法を理解した
  • タスク別のモデル選定戦略を理解した
  • モデルルーティングの仕組みと実装パターンを理解した
  • バッチ処理とストリーミングの使い分けを理解した

次のステップへ

次は「キャッシュ戦略とRAG最適化」です。同じ質問には同じ回答を返すキャッシュ戦略と、RAGの検索効率を最適化する手法を学びましょう。


推定読了時間: 30分