LESSON 30分

ストーリー

田中VPoE
コスト最適化ができた。最後のStepは継続的改善だ。まずはA/Bテストから。NetShop社のチャットボットのプロンプトを改善したいが、本当に改善になるか確信が持てない
あなた
新しいプロンプトを一部のユーザーに試して、効果を検証するんですね
田中VPoE
その通り。LLMシステムのA/Bテストには従来のWebとは異なる課題がある。非決定的な出力、評価指標の設計、統計的有意性の確保など、LLM特有の考慮点を押さえよう
あなた
データに基づいた意思決定の方法を学びたいです

LLMシステムのA/Bテスト

従来のA/Bテストとの違い

項目Web A/BテストLLM A/Bテスト
出力決定的(同じ入力→同じ出力)非決定的(同じ入力でも異なる出力)
評価指標CTR、CVR等の明確な数値品質スコア、満足度等の主観的指標
サンプルサイズ計算しやすい出力のバリエーションにより多く必要
テスト期間数日〜数週間1-4週間(十分なバリエーションを観測)
リスクUIの変更程度不適切な回答による顧客影響

テスト可能な変数

変数影響範囲
プロンプトシステムプロンプトの改善品質、トーン
モデルGPT-4o vs Claude Sonnet品質、コスト、レイテンシ
パラメータtemperature、max_tokens出力の多様性、長さ
RAG設定チャンクサイズ、Top-K回答の正確性
後処理フィルタリングルール安全性、品質

A/Bテストの設計

テスト設計テンプレート

from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class ABTestConfig:
    """A/Bテスト設定"""
    test_id: str
    name: str
    hypothesis: str
    control: dict  # 現行設定
    treatment: dict  # 新設定
    traffic_split: float = 0.5  # treatment へのトラフィック割合
    min_sample_size: int = 1000
    max_duration_days: int = 14
    primary_metric: str = "quality_score"
    secondary_metrics: list[str] = field(default_factory=lambda: ["latency", "cost", "csat"])
    guardrails: dict = field(default_factory=lambda: {"min_quality": 0.6, "max_error_rate": 0.1})
    start_date: datetime | None = None
    end_date: datetime | None = None

# テスト例
test = ABTestConfig(
    test_id="ab_chatbot_prompt_v2",
    name="チャットボット プロンプト改善テスト",
    hypothesis="簡潔なシステムプロンプトにより品質を維持しつつレイテンシ20%改善",
    control={
        "model": "gpt-4o",
        "system_prompt": "current_prompt_v1",
        "temperature": 0.7
    },
    treatment={
        "model": "gpt-4o",
        "system_prompt": "optimized_prompt_v2",
        "temperature": 0.5
    },
    traffic_split=0.2,  # まず20%で開始
    min_sample_size=500,
    primary_metric="quality_score"
)

トラフィック分割の安全な運用

フェーズトラフィック割合期間判断基準
カナリア5%1-2日エラー率が許容範囲内か
初期テスト20%3-5日品質スコアが悪化しないか
本テスト50%7-14日統計的有意差の検出
全量展開100%-テスト結果が有意に改善

統計的有意性の検証

LLMテスト結果の分析

from scipy import stats
import numpy as np

def analyze_ab_test(
    control_scores: list[float],
    treatment_scores: list[float],
    significance_level: float = 0.05
) -> dict:
    """A/Bテスト結果の統計分析"""
    control = np.array(control_scores)
    treatment = np.array(treatment_scores)

    # 基本統計量
    control_mean = np.mean(control)
    treatment_mean = np.mean(treatment)
    lift = (treatment_mean - control_mean) / control_mean

    # Welchのt検定(分散が異なる場合に対応)
    t_stat, p_value = stats.ttest_ind(control, treatment, equal_var=False)

    # 効果量(Cohen's d)
    pooled_std = np.sqrt(
        (np.var(control) + np.var(treatment)) / 2
    )
    cohens_d = (treatment_mean - control_mean) / pooled_std if pooled_std > 0 else 0

    return {
        "control_mean": round(control_mean, 4),
        "treatment_mean": round(treatment_mean, 4),
        "lift": f"{lift*100:.2f}%",
        "p_value": round(p_value, 4),
        "statistically_significant": p_value < significance_level,
        "cohens_d": round(cohens_d, 4),
        "effect_size": (
            "小" if abs(cohens_d) < 0.2 else
            "中" if abs(cohens_d) < 0.8 else
            "大"
        ),
        "recommendation": (
            "Treatment採用" if p_value < significance_level and lift > 0 else
            "Control維持" if p_value < significance_level and lift <= 0 else
            "テスト延長(有意差なし)"
        )
    }

ガードレール付きテスト

安全停止条件

条件閾値アクション
エラー率treatment > control + 5%即時停止
品質スコアtreatment < 0.6即時停止
ユーザークレーム3件以上テスト一時停止+調査
レイテンシP95 > 10秒アラート+調査

まとめ

要素ポイント
テスト設計仮説→メトリクス→サンプルサイズ→期間の順に設計
トラフィック分割カナリア→初期→本テスト→全量の段階的展開
統計分析p値と効果量の両方で判断
安全装置ガードレールによる自動停止

チェックリスト

  • LLMシステムのA/Bテスト特有の課題を理解した
  • テスト設計の手順を把握した
  • 統計的有意性の検証方法を学んだ
  • ガードレール付きテストの設計ができる

次のステップへ

次はフィードバックループの設計を学びます。


推定読了時間: 30分