大規模言語モデル(LLM)を本番環境で運用する際、成本最適化は避けて通れない課題です。GPT-4.1が$8/MTok、Claude Sonnet 4.5が$15/MTokという価格は、小規模なプロトタイプであれば問題ありませんが、毎日数万リクエストを処理する本番システムでは深刻な財務的負担となります。

本稿では、HolySheep AIを活用した模型降級アーキテクチャの設計と実装について詳しく解説します。HolySheep AIは¥1=$1という業界最安水準のレートを提供しており、DeepSeek V3.2仅为$0.42/MTokという破格のコストで高品質な推論を利用可能です。

なぜ模型降級戦略が必要か

多くのLLMアプリケーションでは、すべてのリクエストに最高性能のモデルが必要なわけではありません。ユーザーのクエリ complexity は大きく異なり�

そこで私たちは「リクエストの特性に基づいて適切なモデルを自動選択し、不要なコストを削減する」システムを設計しました。

アーキテクチャ設計

全体構成

我々の降級システムは3層構造を採用しています:

"""
Model Router Architecture
├── Tier 1: Request Classifier (軽量モデルでクエリを分析)
├── Tier 2: Cost Estimator (処理コストを見積もり)
└── Tier 3: Model Selector (最終的なモデル選択を実行)
"""

import asyncio
from enum import Enum
from dataclasses import dataclass
from typing import Optional, Dict, List
from abc import ABC, abstractmethod

class ModelTier(Enum):
    """モデルのコスト層"""
    PREMIUM = "premium"      # GPT-4.1, Claude Sonnet 4.5
    STANDARD = "standard"    # Gemini 2.5 Flash
    ECONOMY = "economy"      # DeepSeek V3.2

@dataclass
class ModelConfig:
    """モデル設定"""
    name: str
    tier: ModelTier
    cost_per_mtok: float  # USD per million tokens
    max_tokens: int
    capabilities: List[str]
    avg_latency_ms: float

class ModelRegistry:
    """利用可能なモデルを登録・管理"""
    
    MODELS = {
        "gpt-4.1": ModelConfig(
            name="gpt-4.1",
            tier=ModelTier.PREMIUM,
            cost_per_mtok=8.0,
            max_tokens=128000,
            capabilities=["reasoning", "coding", "creative", "analysis"],
            avg_latency_ms=2500
        ),
        "claude-sonnet-4.5": ModelConfig(
            name="claude-sonnet-4.5",
            tier=ModelTier.PREMIUM,
            cost_per_mtok=15.0,
            max_tokens=200000,
            capabilities=["reasoning", "coding", "creative", "long_context"],
            avg_latency_ms=3000
        ),
        "gemini-2.5-flash": ModelConfig(
            name="gemini-2.5-flash",
            tier=ModelTier.STANDARD,
            cost_per_mtok=2.50,
            max_tokens=1000000,
            capabilities=["reasoning", "fast", "multimodal"],
            avg_latency_ms=800
        ),
        "deepseek-v3.2": ModelConfig(
            name="deepseek-v3.2",
            tier=ModelTier.ECONOMY,
            cost_per_mtok=0.42,
            max_tokens=64000,
            capabilities=["reasoning", "coding", "cost_efficient"],
            avg_latency_ms=600
        )
    }
    
    @classmethod
    def get_model(cls, model_name: str) -> Optional[ModelConfig]:
        return cls.MODELS.get(model_name)
    
    @classmethod
    def get_models_by_tier(cls, tier: ModelTier) -> List[ModelConfig]:
        return [m for m in cls.MODELS.values() if m.tier == tier]

リクエスト分類システム

ユーザーのプロンプトを分析し、どの程度の complexity があるかを判定します。簡易的なキーワードベースのアプローチから、NLP による意味分析まで複数の手法を組み合わせて使用します。

"""
リクエスト分類器 - クエリの複雑度を評価
"""

import re
import hashlib
from typing import Tuple
from enum import IntEnum

class ComplexityScore(IntEnum):
    """複雑度スコア"""
    TRIVIAL = 1      # 単純な質問・変換
    SIMPLE = 2       # 基本的なタスク
    MODERATE = 3     # 通常の処理
    COMPLEX = 4      # 複雑な推論が必要
    EXPERT = 5       # 最高性能モデル推奨

class RequestClassifier:
    """リクエスト複雑度分類器"""
    
    # 複雑なタスクを示すキーワード
    COMPLEX_KEYWORDS = [
        "分析", "比較", "評価", "考察", "設計", "開発",
        "why", "how", "explain", "analyze", "compare", "design",
        "レビュー", "最適化", "実装", "アルゴリズム"
    ]
    
    # 簡単なタスクを示すキーワード
    SIMPLE_KEYWORDS = [
        "翻訳", "要約", "リスト", "翻訳", "変換", "フォーマット",
        "translate", "summarize", "list", "convert", "format",
        "何時", "どこ", "誰", "what", "when", "where", "who"
    ]
    
    # コード関連のキーワード
    CODE_KEYWORDS = [
        "コード", "関数", "クラス", "デバッグ", "リファクタ",
        "code", "function", "class", "debug", "refactor",
        "Python", "JavaScript", "API", "bug", "error"
    ]
    
    def __init__(self):
        self.complexity_weights = {
            "length_score": 0.2,
            "keyword_score": 0.4,
            "pattern_score": 0.4
        }
    
    def classify(self, prompt: str, context: Optional[Dict] = None) -> Tuple[ComplexityScore, Dict]:
        """
        プロンプトの複雑度を分類
        
        Returns:
            (ComplexityScore, metadata): 複雑度と分析メタデータ
        """
        metadata = {
            "prompt_length": len(prompt),
            "has_code": False,
            "complexity_factors": [],
            "keyword_matches": {"complex": 0, "simple": 0, "code": 0}
        }
        
        # 1. 長さスコア
        length_score = self._calculate_length_score(prompt)
        
        # 2. キーワードスコア
        keyword_score = self._calculate_keyword_score(prompt, metadata)
        
        # 3. パターンスコア(質問構造を分析)
        pattern_score = self._calculate_pattern_score(prompt, metadata)
        
        # 重み付け合計
        total_score = (
            length_score * self.complexity_weights["length_score"] +
            keyword_score * self.complexity_weights["keyword_score"] +
            pattern_score * self.complexity_weights["pattern_score"]
        )
        
        # 最終スコアをComplexityScoreに変換
        if total_score < 1.5:
            complexity = ComplexityScore.TRIVIAL
        elif total_score < 2.0:
            complexity = ComplexityScore.SIMPLE
        elif total_score < 3.0:
            complexity = ComplexityScore.MODERATE
        elif total_score < 4.0:
            complexity = ComplexityScore.COMPLEX
        else:
            complexity = ComplexityScore.EXPERT
        
        metadata["final_score"] = total_score
        return complexity, metadata
    
    def _calculate_length_score(self, prompt: str) -> float:
        """プロンプト長に基づくスコア(長いほど複雑な傾向)"""
        length = len(prompt)
        if length < 100:
            return 1.0
        elif length < 500:
            return 1.5
        elif length < 1000:
            return 2.0
        elif length < 3000:
            return 3.0
        else:
            return 4.0
    
    def _calculate_keyword_score(self, prompt: str, metadata: Dict) -> float:
        """キーワードマッチングによるスコア"""
        prompt_lower = prompt.lower()
        score = 2.0  # ベーススコア
        
        for keyword in self.COMPLEX_KEYWORDS:
            if keyword.lower() in prompt_lower:
                metadata["keyword_matches"]["complex"] += 1
                score += 0.5
        
        for keyword in self.SIMPLE_KEYWORDS:
            if keyword.lower() in prompt_lower:
                metadata["keyword_matches"]["simple"] += 1
                score -= 0.3
        
        for keyword in self.CODE_KEYWORDS:
            if keyword.lower() in prompt_lower:
                metadata["keyword_matches"]["code"] += 1
                metadata["has_code"] = True
                score += 0.4
        
        return max(1.0, min(5.0, score))
    
    def _calculate_pattern_score(self, prompt: str, metadata: Dict) -> float:
        """プロンプトの構造パターン来分析"""
        score = 2.5
        
        # 複数質問がある場合
        question_count = len(re.findall(r'[??]', prompt))
        if question_count > 1:
            score += 0.5 * (question_count - 1)
            metadata["complexity_factors"].append(f"multi_question:{question_count}")
        
        # 「なぜ」「どのように」などの深い思考を促す質問
        deep_thinking_patterns = [
            r'なぜ.*のか', r'なぜ.*か', r'どう.*のか',
            r'why.*\?', r'how.*\?', r'explain.*why'
        ]
        for pattern in deep_thinking_patterns:
            if re.search(pattern, prompt, re.IGNORECASE):
                score += 0.7
                metadata["complexity_factors"].append("deep_thinking")
                break
        
        # 比較要求(複雑な分析的思考が必要)
        if re.search(r'比較|compare|vs\.?| versus ', prompt, re.IGNORECASE):
            score += 0.6
            metadata["complexity_factors"].append("comparison")
        
        return max(1.0, min(5.0, score))

モデル選択エンジン

分類結果とシステムの状態を考慮して、最適なモデルを選択するロジックを実装します。

"""
Smart Model Selector - コスト・品質・レイテンシを総合的に判断
"""

import time
from typing import Optional, Tuple
from dataclasses import dataclass
from datetime import datetime
import asyncio

@dataclass
class SystemState:
    """システムの状態"""
    current_load: float          # 0.0-1.0
    queue_depth: int             # 待機リクエスト数
    avg_response_time_ms: float  # 平均応答時間
    budget_remaining: float      # 残りの予算 (USD)
    budget_daily_limit: float    # 日次予算上限

@dataclass
class SelectionContext:
    """モデル選択のコンテキスト"""
    complexity: ComplexityScore
    user_tier: str               # "free", "basic", "pro", "enterprise"
    required_capabilities: List[str]
    max_latency_ms: Optional[float]
    quality_floor: float         # 最低品質要件 (0.0-1.0)

class ModelSelector:
    """インテリジェントなモデル選択"""
    
    # 複雑度に対するデフォルトモデルのマッピング
    COMPLEXITY_MODEL_MAP = {
        ComplexityScore.TRIVIAL: ModelTier.ECONOMY,
        ComplexityScore.SIMPLE: ModelTier.ECONOMY,
        ComplexityScore.MODERATE: ModelTier.STANDARD,
        ComplexityScore.COMPLEX: ModelTier.STANDARD,
        ComplexityScore.EXPERT: ModelTier.PREMIUM
    }
    
    # レイテンシ要件によるモデル制限
    LATENCY_TIER_MAP = {
        500: ModelTier.ECONOMY,
        1000: ModelTier.STANDARD,
        5000: ModelTier.PREMIUM
    }
    
    def __init__(self, system_state: SystemState):
        self.system_state = system_state
    
    def select(
        self,
        context: SelectionContext,
        cost_ceiling: Optional[float] = None
    ) -> ModelConfig:
        """
        最適なモデルを選択
        
        Args:
            context: 選択コンテキスト
            cost_ceiling: 最大コスト上限
        
        Returns:
            ModelConfig: 選択されたモデル設定
        """
        # Step 1: 複雑度からベースモデルを推定
        base_tier = self.COMPLEXITY_MODEL_MAP[context.complexity]
        
        # Step 2: レイテンシ要件でフィルター
        if context.max_latency_ms:
            latency_limit_tier = self._get_latency_tier(context.max_latency_ms)
            base_tier = min(base_tier, latency_limit_tier)
        
        # Step 3: 必要な capability を満たすモデルを探す
        candidates = self._find_candidates(base_tier, context.required_capabilities)
        
        # Step 4: コスト上限でフィルター
        if cost_ceiling:
            candidates = [m for m in candidates if m.cost_per_mtok <= cost_ceiling]
        
        # Step 5: システム負荷で動的に調整
        adjusted_candidates = self._apply_load_balancing(candidates)
        
        # Step 6: 予算状況を確認
        final_candidates = self._check_budget_constraints(adjusted_candidates)
        
        if not final_candidates:
            # フォールバック: 利用可能な最も安いモデル
            return ModelRegistry.get_models_by_tier(ModelTier.ECONOMY)[0]
        
        # 品質とコストのトレードオフで最終選択
        return self._optimize_selection(final_candidates, context)
    
    def _get_latency_tier(self, max_latency_ms: float) -> ModelTier:
        """レイテンシ要件から利用可能な最上位モデルを決定"""
        for threshold, tier in sorted(self.LATENCY_TIER_MAP.items(), reverse=True):
            if max_latency_ms >= threshold:
                return tier
        return ModelTier.ECONOMY
    
    def _find_candidates(
        self,
        base_tier: ModelTier,
        required_capabilities: List[str]
    ) -> List[ModelConfig]:
        """必要な capability を満たすモデルを検索"""
        candidates = []
        
        # 指定Tier以上を提案
        tier_order = [ModelTier.ECONOMY, ModelTier.STANDARD, ModelTier.PREMIUM]
        base_idx = tier_order.index(base_tier)
        
        for tier in tier_order[base_idx:]:
            for model in ModelRegistry.get_models_by_tier(tier):
                if all(cap in model.capabilities for cap in required_capabilities):
                    candidates.append(model)
        
        return candidates
    
    def _apply_load_balancing(self, candidates: List[ModelConfig]) -> List[ModelConfig]:
        """システム負荷に基づいてモデルを制限"""
        load = self.system_state.current_load
        
        # 高負荷時は Economy モデルを優先
        if load > 0.8:
            # 最もコスト効率の良いモデルを選択
            return sorted(candidates, key=lambda m: m.cost_per_mtok)[:1]
        elif load > 0.6:
            # STANDARD 以上の Economy, STANDARD のみ
            return [m for m in candidates if m.tier in [ModelTier.ECONOMY, ModelTier.STANDARD]]
        
        return candidates
    
    def _check_budget_constraints(self, candidates: List[ModelConfig]) -> List[ModelConfig]:
        """予算制約をチェック"""
        if self.system_state.budget_remaining <= 0:
            # 予算切れ: Economy モデルのみ
            return [m for m in candidates if m.tier == ModelTier.ECONOMY]
        
        # 日次予算の消化率をチェック
        budget_usage_ratio = 1 - (self.system_state.budget_remaining / self.system_state.budget_daily_limit)
        
        if budget_usage_ratio > 0.9:
            # 予算の90%消化: 最安モデル強制
            return [m for m in candidates if m.tier == ModelTier.ECONOMY]
        elif budget_usage_ratio > 0.7:
            # 予算の70%消化: Economy + Standard のみ
            return [m for m in candidates if m.tier in [ModelTier.ECONOMY, ModelTier.STANDARD]]
        
        return candidates
    
    def _optimize_selection(
        self,
        candidates: List[ModelConfig],
        context: SelectionContext
    ) -> ModelConfig:
        """品質とコストのトレードオフを最適化"""
        if len(candidates) == 1:
            return candidates[0]
        
        # スコアリング: 品質スコア - コストペナルティ
        scores = []
        for model in candidates:
            quality_score = self._estimate_quality(model, context.complexity)
            cost_penalty = model.cost_per_mtok / 10  # 正規化
            latency_penalty = model.avg_latency_ms / 1000
            
            final_score = quality_score - cost_penalty - latency_penalty
            scores.append(final_score)
        
        best_idx = scores.index(max(scores