AI Engineering Academy · レッスン

バッチ処理、モデルルーティング、コストダッシュボード

単純なリクエストはGPT-4o-miniのような安価なモデルに、複雑なリクエストはGPT-4oに振り分け、緊急でないリクエストをバッチ処理し、機能別の支出を追跡するコストダッシュボードを構築します。

レッスン 4/413 ステップ

「バッチ処理、モデルルーティング、コストダッシュボード」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。

コスト最適化のための追加の3つの手段

キャッシュに加えて、LLMの運用コストを大幅に削減する方法が3つあります。バッチ処理(緊急性のないリクエストを遅延させ、低いAPI料金でまとめて送信する)、モデルルーティング(単純なクエリは安価なモデルに、複雑なクエリは高性能なモデルに振り分ける)、コストダッシュボード(機能ごとの支出を追跡し、最もROIの高い最適化箇所を特定する)です。これらを組み合わせると、キャッシュによる削減に加えて、さらに40~60パーセントのコスト削減が可能です。

OpenAI Batch API:非同期ワークロードを50パーセント割引

OpenAIのBatch APIは、最大50,000件のリクエストを含むJSONLファイルを受け付け、標準料金の50パーセントで24時間以内に非同期処理します。大量のドキュメントコーパスの埋め込み、商品説明の生成、夜間の評価実行、トレーニングデータの前処理など、インタラクティブではないワークロードに適しています。トレードオフはレイテンシで、結果はすぐには得られず、数時間後に利用可能になります。

import json
from openai import OpenAI

client = OpenAI()

# Prepare batch file
requests = [
    {
        'custom_id': f'req_{i}',
        'method': 'POST',
        'url': '/v1/chat/completions',
        'body': {
            'model': 'gpt-4o-mini',
            'messages': [
                {'role': 'user', 'content': f'Summarize: {document}'}
            ],
            'max_tokens': 150,
        }
    }
    for i, document in enumerate(documents_to_process)
]

# Write JSONL batch file
with open('/tmp/batch_requests.jsonl', 'w') as f:
    for req in requests:
        f.write(json.dumps(req) + '\n')

# Upload and submit batch
with open('/tmp/batch_requests.jsonl', 'rb') as f:
    batch_file = client.files.create(file=f, purpose='batch')

batch = client.batches.create(
    input_file_id=batch_file.id,
    endpoint='/v1/chat/completions',
    completion_window='24h',
)
print(f'Batch {batch.id} submitted, status: {batch.status}')

バッチ結果のポーリング

バッチを送信した後は、完了するまでステータスをポーリングします(ステータスがin_progressからcompletedに変わります)。完了したら、すべてのリクエストの結果を含む出力ファイルをダウンロードします。各出力行は、リクエストのcustom_idと、responseまたはerrorフィールドのいずれかを含むJSONオブジェクトです。バッチ内の個々のリクエストは独立して失敗する可能性があるため、必ず両方に対応してください。

import time

def wait_for_batch(batch_id: str, poll_interval: int = 60) -> str:
    while True:
        batch = client.batches.retrieve(batch_id)
        print(f'Status: {batch.status}, completed: {batch.request_counts.completed}')

        if batch.status == 'completed':
            return batch.output_file_id
        elif batch.status == 'failed':
            raise RuntimeError(f'Batch failed: {batch.errors}')

        time.sleep(poll_interval)

def download_batch_results(output_file_id: str) -> list[dict]:
    content = client.files.content(output_file_id)
    results = []
    for line in content.text.strip().split('\n'):
        results.append(json.loads(line))
    return results

output_file_id = wait_for_batch(batch.id)
results = download_batch_results(output_file_id)
for result in results[:3]:
    print(result['custom_id'], result.get('response', {}).get('body', {}).get('choices', [{}])[0])

モデルルーティング:複雑さに応じてモデルサイズを選ぶ

モデルルーティングでは、各リクエストを適切に処理できる最も安価なモデルに割り当てます。GPT-4o-miniはGPT-4oより約30分の1のコストで利用できますが、単純な分類、抽出、短いQ&Aタスクでは同等の性能を発揮します。単純で構造化されたタスクは小型で安価なモデルに、複雑な推論、長いコンテキストの統合、ニュアンスを伴う生成は大規模で高性能なモデルにルーティングします。トラフィックの60パーセントを安価なモデルにルーティングするだけでも、大幅なコスト削減になります。

CHEAP_MODEL = 'gpt-4o-mini'
POWERFUL_MODEL = 'gpt-4o'

def classify_query_complexity(query: str) -> str:
    # Heuristic-based routing (replace with ML classifier for production)
    words = query.split()
    has_code = any(c in query for c in ['```', 'def ', 'class ', 'SELECT ', 'function '])
    is_multi_step = any(w in query.lower() for w in ['compare', 'analyze', 'explain why', 'evaluate'])
    is_long = len(words) > 50

    if has_code or is_multi_step or is_long:
        return POWERFUL_MODEL
    return CHEAP_MODEL

def routed_completion(messages: list[dict]) -> str:
    user_query = messages[-1].get('content', '')
    model = classify_query_complexity(user_query)
    print(f'Routing to: {model}')
    response = client.chat.completions.create(model=model, messages=messages)
    return response.choices[0].message.content

LLMベースのルーティングによる精度向上

ヒューリスティックなルーティングは高速ですが、壊れやすいという欠点があります。より正確な方法は、小型で安価な分類モデルを使って、どのモデルにルーティングするかを判断させることです。対象分野の単純なクエリと複雑なクエリの例で小型モデルをファインチューニングするか、GPT-4o-mini自体を使ってfew-shotプロンプティングを行います。分類器の呼び出しにかかるコストは数百入力トークン分にすぎません。これは、複雑なクエリを誤って安価なモデルにルーティングし、誤答を生成させるコストよりはるかに小さいものです。

CLASSIFIER_SYSTEM = '''You are a query complexity classifier.
Classify the user query as SIMPLE or COMPLEX.
SIMPLE: factual lookup, extraction, classification with clear answer.
COMPLEX: multi-step reasoning, synthesis, comparison, code generation, long-form writing.
Reply with just SIMPLE or COMPLEX.'''

def llm_classify_complexity(query: str) -> str:
    response = client.chat.completions.create(
        model='gpt-4o-mini',  # use cheap model for routing
        messages=[
            {'role': 'system', 'content': CLASSIFIER_SYSTEM},
            {'role': 'user', 'content': query},
        ],
        max_tokens=10,
        temperature=0,
    )
    label = response.choices[0].message.content.strip()
    return POWERFUL_MODEL if label == 'COMPLEX' else CHEAP_MODEL

機能ごとのコスト追跡

最適化に注力すべき場所を把握するには、総支出だけでなく、アプリケーションの機能ごとのコストを追跡する必要があります。すべてのLLM呼び出しに機能タグを付け、タグごとにトークンコストを累積します。'search_summarization'はトラフィックの5パーセントにしか使われていないのに予算の40パーセントを消費している可能性があり、その場合は優先度の高い最適化対象になります。'user_onboarding'は高コストでも、価値の高いフローに使われているため、性能を低下させたくない場合があります。

from collections import defaultdict

cost_tracker = defaultdict(lambda: {'prompt_tokens': 0, 'completion_tokens': 0, 'cost_usd': 0.0})

MODEL_PRICING = {
    'gpt-4o-mini': {'input': 0.15 / 1e6, 'output': 0.60 / 1e6},
    'gpt-4o':      {'input': 2.50 / 1e6, 'output': 10.00 / 1e6},
}

def tracked_completion(feature: str, messages: list[dict], model: str = 'gpt-4o-mini') -> str:
    response = client.chat.completions.create(model=model, messages=messages)
    usage = response.usage
    pricing = MODEL_PRICING.get(model, {'input': 0, 'output': 0})
    cost = usage.prompt_tokens * pricing['input'] + usage.completion_tokens * pricing['output']

    cost_tracker[feature]['prompt_tokens'] += usage.prompt_tokens
    cost_tracker[feature]['completion_tokens'] += usage.completion_tokens
    cost_tracker[feature]['cost_usd'] += cost

    return response.choices[0].message.content

def print_cost_report():
    print(f'{"Feature":<30} {"Prompt":<10} {"Completion":<12} {"Cost USD":<12}')
    for feature, stats in sorted(cost_tracker.items(), key=lambda x: -x[1]['cost_usd']):
        print(f'{feature:<30} {stats["prompt_tokens"]:<10} {stats["completion_tokens"]:<12} ${stats["cost_usd"]:.4f}')

シンプルなコストダッシュボードの構築

実用的なコストダッシュボードでは、機能レベルの支出データを集計し、シンプルなHTTPエンドポイントを通じて公開します。累積コストをRedisに日次のロールアップキーとともに保存すると、時間の経過に伴う支出の推移を確認できます。このダッシュボードを社内の開発者向けツールに追加すれば、チームは機能リリースがコストに与える影響をほぼリアルタイムで把握でき、支出が膨れ上がって大きな請求になる前に検知できます。

from fastapi import FastAPI
import datetime

app = FastAPI()

async def record_cost(feature: str, model: str, prompt_tokens: int, completion_tokens: int):
    pricing = MODEL_PRICING.get(model, {'input': 0, 'output': 0})
    cost = prompt_tokens * pricing['input'] + completion_tokens * pricing['output']
    today = datetime.date.today().isoformat()
    key = f'cost:{today}:{feature}:{model}'
    await async_r.incrbyfloat(key, cost)
    await async_r.expire(key, 86400 * 30)  # keep 30 days

@app.get('/dashboard/costs')
async def cost_dashboard():
    today = datetime.date.today().isoformat()
    pattern = f'cost:{today}:*'
    costs = {}
    async for key in async_r.scan_iter(match=pattern):
        value = await async_r.get(key)
        parts = key.split(':')
        feature_model = ':'.join(parts[2:])
        costs[feature_model] = float(value or 0)
    return {'date': today, 'costs': costs, 'total': sum(costs.values())}

月次予算アラート

月次予算アラートを設定すると、予想外のコスト急増が大きな請求につながる前に検知できます。コストトラッカーから日次支出の移動値を計算し、月末までの支出を予測します。そして、予測値が予算のしきい値を超えたときにSlackアラートを発報します。daily_spend * days_remainingという単純な予測でも、実際のパターンが非線形であっても、急増するリクエストを早期に検知できます。

import datetime
import httpx

SLACK_WEBHOOK = 'https://hooks.slack.com/services/YOUR/WEBHOOK'
MONTHLY_BUDGET_USD = 500.0

async def check_budget_alert():
    today = datetime.date.today()
    days_in_month = 30
    day_of_month = today.day
    days_remaining = days_in_month - day_of_month

    # Sum today's costs
    today_total = sum(cost_tracker[f]['cost_usd'] for f in cost_tracker)
    avg_daily = today_total  # simplified: just today's spend
    projected_month = avg_daily * days_in_month

    if projected_month > MONTHLY_BUDGET_USD:
        message = (
            f'LLM Budget Alert: Projected monthly spend ${projected_month:.2f} '
            f'exceeds budget ${MONTHLY_BUDGET_USD:.2f}. '
            f'Today spend: ${today_total:.2f}'
        )
        async with httpx.AsyncClient() as client:
            await client.post(SLACK_WEBHOOK, json={'text': message})

レート制限を管理するリクエストキュー

トラフィックが急増すると、リクエストがOpenAIのレート制限に達し、429 Too Many Requestsで失敗します。リクエストキューは受信したリクエストを一時的に蓄え、制御されたレートで送信することで、トラフィックのピークを平準化します。本番環境ではRedisやRabbitMQのようなメッセージブローカーを基盤とする非同期キューを使用し、一時的な429エラーに対して指数バックオフによるリトライ処理を実装します。

import asyncio
from asyncio import Queue

class RateLimitedLLMClient:
    def __init__(self, requests_per_minute: int = 500):
        self.rpm = requests_per_minute
        self.queue: Queue = Queue(maxsize=1000)
        self.interval = 60.0 / requests_per_minute

    async def start(self):
        asyncio.create_task(self._worker())

    async def _worker(self):
        while True:
            request_fn, future = await self.queue.get()
            try:
                result = await request_fn()
                future.set_result(result)
            except Exception as e:
                future.set_exception(e)
            await asyncio.sleep(self.interval)

    async def submit(self, request_fn) -> str:
        loop = asyncio.get_event_loop()
        future = loop.create_future()
        await self.queue.put((request_fn, future))
        return await future

総まとめ:コスト最適化スタック

完全なLLMコスト最適化スタックは、複数の層で構成されます。完全一致キャッシュは同一のクエリに対する重複した呼び出しをなくし、セマンティックキャッシュは類似したクエリに対する呼び出しをなくします。プレフィックスキャッシュは残りすべての呼び出しの入力コストを削減し、モデルルーティングは単純なクエリに安価なモデルを使用します。バッチ処理は緊急性のない処理を延期して50パーセントの割引を適用し、ダッシュボードとアラートはコストを可視化し、上限内に抑えます。対象アプリケーションへの効果が大きい順に、段階的に導入してください。

# Decision framework for cost optimization priority:
#
# 1. Enable prefix caching (free, zero effort, automatic)
# 2. Add exact caching (high hit rate for FAQ/support bots)
# 3. Add model routing (simple heuristics first, ML classifier later)
# 4. Add semantic caching (complex, high ROI for paraphrase-heavy use cases)
# 5. Enable batch API (only for non-real-time pipelines)
# 6. Build cost dashboard (essential for ongoing monitoring)
#
# Typical combined result in a customer support bot:
# Before: $1,000/month
# After step 1-2: $400/month (-60%)
# After step 3-4: $200/month (-50% of remaining)
# After step 5-6: $150/month and visible

安価なモデルの失敗時におけるカスケードフォールバック

安価なモデルにルーティングする場合、そのモデルが満足できない回答を生成するケースに対処する必要があります。安価なモデルの出力に対して品質チェックを実装します。たとえば、回答の長さや必須フィールドの有無を確認したり、高速なLLM-as-judgeによるスコアリングを実行したりします。そして品質が不十分な場合は、強力なモデルに自動的にフォールバックさせます。この安全策により、ユーザー体験を低下させるリスクを抑えながら、安価なモデルへのルーティングを積極的に行えます。

async def routing_with_fallback(messages: list[dict], min_length: int = 50) -> str:
    # Try cheap model first
    cheap_response = await async_client.chat.completions.create(
        model=CHEAP_MODEL, messages=messages, temperature=0.0
    )
    answer = cheap_response.choices[0].message.content

    # Quality check: response too short indicates poor answer
    if len(answer.strip()) < min_length:
        print(f'Cheap model answer too short ({len(answer)} chars), escalating...')
        powerful_response = await async_client.chat.completions.create(
            model=POWERFUL_MODEL, messages=messages, temperature=0.0
        )
        return powerful_response.choices[0].message.content

    return answer

クイックチェック

このレッスンで学んだバッチ処理、モデルルーティング、コストダッシュボードについての理解度を確認します。

レッスンのまとめ

このレッスンでは、OpenAI Batch APIが非同期の非リアルタイムワークロードに50パーセントの割引を提供すること、モデルルーティングが単純なタスクにはGPT-4o-miniのような安価なモデルを、複雑なタスクには高価なモデルを使用すること、そして機能ごとのコスト追跡によってアプリケーションのどの部分が予算を最も消費しているかを把握し、最適化の優先順位を効果的に決められることを学びました。前のレッスンで学んだキャッシュ戦略と組み合わせることで、これらの技術はLLMインフラストラクチャのコストを60~80パーセント削減できます。これでLLMのキャッシュとコスト最適化のコースは完了です。

無料で開始

AI チューターと学ぶ Python — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
30
レッスン
120

よくある質問

「バッチ処理、モデルルーティング、コストダッシュボード」レッスンは無料ですか?

はい。「バッチ処理、モデルルーティング、コストダッシュボード」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。

「バッチ処理、モデルルーティング、コストダッシュボード」で何を学びますか?

単純なリクエストはGPT-4o-miniのような安価なモデルに、複雑なリクエストはGPT-4oに振り分け、緊急でないリクエストをバッチ処理し、機能別の支出を追跡するコストダッシュボードを構築します。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Engineering Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「バッチ処理、モデルルーティング、コストダッシュボード」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Engineering Academyレッスンでコードを書いて実行できますか?

はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Redisによる完全一致キャッシュ
  2. 埋め込みによる意味的キャッシュ
  3. OpenAIのプロンプトプレフィックスキャッシュ
  4. バッチ処理、モデルルーティング、コストダッシュボード
← AI Engineering Academyに戻る