T-CREATOR

gpt-oss のモデルルーティング設計:サイズ別・ドメイン別・コスト別の自動切替

gpt-oss のモデルルーティング設計:サイズ別・ドメイン別・コスト別の自動切替

AI モデルの選択は、開発者にとって悩ましい問題です。高性能なモデルはコストが高く、軽量なモデルは精度が不十分かもしれません。タスクの内容によって最適なモデルは異なるのに、毎回手動で選択するのは非効率ですよね。

gpt-oss は、複数の AI モデルを賢く切り替えるルーティング機能を提供します。入力サイズ、ドメイン、コストなどの条件に応じて、最適なモデルを自動選択できるのです。本記事では、gpt-oss のモデルルーティング設計について、実装例を交えながら詳しく解説していきます。

背景

AI モデルの多様化と選択の複雑さ

現代の AI 開発では、OpenAI の GPT-4、GPT-3.5、Anthropic の Claude、Google の Gemini など、多種多様なモデルが利用可能です。それぞれのモデルには特性があります。

  • 高性能モデル: GPT-4 や Claude Opus は複雑な推論が得意ですが、API コストが高額
  • 軽量モデル: GPT-3.5 Turbo や Claude Haiku は応答が速く安価ですが、精度は劣る
  • 専門モデル: コード生成特化、翻訳特化など、ドメインに最適化されたモデルも存在

これらのモデルを適切に使い分けることで、コストを抑えつつ高品質な出力を得られます。しかし、タスクごとに手動でモデルを選択するのは開発効率を下げてしまいますね。

以下の図は、複数のモデルを用途に応じて使い分ける全体像を示しています。

mermaidflowchart LR
  app["アプリケーション"] -->|リクエスト| router["ルーティング層"]
  router -->|小規模タスク| light["軽量モデル<br/>GPT-3.5"]
  router -->|複雑タスク| heavy["高性能モデル<br/>GPT-4"]
  router -->|コード生成| code["専門モデル<br/>Codex"]
  light -->|レスポンス| app
  heavy -->|レスポンス| app
  code -->|レスポンス| app

このように、ルーティング層を介することで、リクエストの特性に応じた最適なモデル選択が可能になります。

gpt-oss とは

gpt-oss は、Node.js/TypeScript 環境で複数の AI プロバイダーを統一的に扱えるオープンソースライブラリです。OpenAI、Anthropic、Google などの API を共通インターフェースで利用でき、モデルの切り替えも容易に行えます。

特に注目すべきは、条件に基づいた自動ルーティング機能です。入力テキストの長さ、タスクの種類、予算制約などに応じて、最適なモデルを動的に選択できるのです。

課題

モデル選択の判断基準が複雑

どのモデルを使うべきか、以下のような複数の要素を考慮する必要があります。

  1. 入力サイズ: トークン数が多い場合、コンテキスト長の大きいモデルが必要
  2. タスクの複雑さ: 単純な要約なら軽量モデル、複雑な推論なら高性能モデル
  3. コスト制約: 予算内で最大限の品質を得たい
  4. 応答速度: リアルタイム応答が必要な場合は軽量モデルが有利
  5. ドメイン特性: コード生成、翻訳、文章作成など、タスクの種類

これらを毎回手動で判断するのは現実的ではありません。ルールベースで自動化したいところですが、条件分岐が複雑になりがちですね。

以下の図は、モデル選択時に考慮すべき要素の関係性を示しています。

mermaidflowchart TD
  request["リクエスト"] --> size{入力サイズ}
  request --> domain{ドメイン}
  request --> cost{コスト制約}

  size -->|大| model_a["GPT-4 32k"]
  size -->|小| check_domain{ドメイン確認}

  check_domain -->|コード| model_b["Codex"]
  check_domain -->|一般| check_cost{コスト確認}

  check_cost -->|高品質| model_c["GPT-4"]
  check_cost -->|低コスト| model_d["GPT-3.5"]

  domain -->|コード生成| model_b
  domain -->|翻訳| model_e["専門翻訳モデル"]

  cost -->|厳格| model_d
  cost -->|柔軟| model_f["状況判断"]

このように判断フローが複雑になるため、柔軟かつメンテナンスしやすい設計が求められます。

既存ライブラリの制約

多くの AI ライブラリは単一プロバイダー専用であり、複数モデルの切り替えには複数のクライアントを管理する必要があります。また、ルーティングロジックをアプリケーション層で実装すると、以下の問題が発生しやすいです。

  • コードの重複: 各エンドポイントでモデル選択ロジックを実装
  • テストの困難さ: 条件分岐が多く、テストケースが膨大に
  • 保守性の低下: ルールの変更時に複数箇所を修正

これらの課題を解決するため、gpt-oss はルーティング機能をライブラリレベルで提供しています。

解決策

モデルルーティングの基本設計

gpt-oss のモデルルーティングは、以下の 3 つの軸で設計されています。

1. サイズベースルーティング

入力トークン数に応じて、最適なモデルを選択します。

#入力トークン数推奨モデル理由
10〜4000GPT-3.5 Turboコスト効率が良く、応答速度が速い
24001〜8000GPT-4 8kバランスの取れた性能とコスト
38001〜32000GPT-4 32k大規模コンテキストに対応
432001〜Claude 100k超大規模コンテキスト処理

この分類により、無駄なコストをかけずに必要な性能を得られます。

2. ドメインベースルーティング

タスクの種類に応じた専門モデルを選択します。

#タスク種類推奨モデル特徴
1コード生成GPT-4 Code文法精度が高く、最適化されたコード出力
2文章作成Claude 2自然で流暢な文章生成
3データ分析GPT-4 Analytics数値処理と分析に強い
4翻訳専門翻訳モデル文脈を考慮した高精度翻訳
5要約GPT-3.5 Turboコスト効率が良く十分な品質

ドメイン特化により、汎用モデルよりも高品質な出力が期待できます。

3. コストベースルーティング

予算制約に応じて、コストパフォーマンスの高いモデルを選択します。

#優先度モデル選択基準コスト/1M トークン
1最高品質GPT-4$30-60
2バランスGPT-3.5 Turbo$0.5-2
3最低コストClaude Instant$0.4-1.6

これらの軸を組み合わせることで、状況に応じた柔軟なルーティングが実現できます。

以下の図は、これら 3 つのルーティング軸がどのように統合されるかを示しています。

mermaidflowchart TD
  input["リクエスト入力"] --> analyzer["ルーティング分析"]

  analyzer --> size_check["サイズベース<br/>チェック"]
  analyzer --> domain_check["ドメインベース<br/>チェック"]
  analyzer --> cost_check["コストベース<br/>チェック"]

  size_check --> scorer["スコアリング"]
  domain_check --> scorer
  cost_check --> scorer

  scorer --> selector["最適モデル選択"]
  selector --> execution["モデル実行"]
  execution --> response["レスポンス返却"]

各チェック結果をスコアリングし、総合的に判断することで、最適なモデルが自動選択されます。

ルーティング設定の実装

gpt-oss では、JSON 形式の設定ファイルでルーティングルールを定義できます。

ルーティング設定ファイルの基本構造

以下のコードは、ルーティング設定の全体構造を示しています。

typescript// routing-config.ts
interface RoutingRule {
  name: string; // ルール名
  priority: number; // 優先度(数値が大きいほど優先)
  conditions: Condition[]; // 適用条件
  targetModel: string; // 選択するモデル
}

interface Condition {
  type: 'size' | 'domain' | 'cost';
  operator: 'gt' | 'lt' | 'eq' | 'contains';
  value: string | number;
}

この型定義により、TypeScript の型安全性を活かしたルール定義が可能になります。

サイズベースのルーティング設定

入力トークン数に応じたモデル選択を設定します。

typescript// size-based-routing.ts
const sizeBasedRules: RoutingRule[] = [
  {
    name: 'small-input',
    priority: 10,
    conditions: [
      {
        type: 'size',
        operator: 'lt',
        value: 4000,
      },
    ],
    targetModel: 'gpt-3.5-turbo',
  },
  {
    name: 'medium-input',
    priority: 20,
    conditions: [
      {
        type: 'size',
        operator: 'gt',
        value: 4000,
      },
      {
        type: 'size',
        operator: 'lt',
        value: 8000,
      },
    ],
    targetModel: 'gpt-4',
  },
];

このように、条件を配列で指定することで、複数の条件を AND で結合できます。

ドメインベースのルーティング設定

タスクの種類に応じたモデル選択を設定します。

typescript// domain-based-routing.ts
const domainBasedRules: RoutingRule[] = [
  {
    name: 'code-generation',
    priority: 30,
    conditions: [
      {
        type: 'domain',
        operator: 'contains',
        value: 'code',
      },
    ],
    targetModel: 'gpt-4-code',
  },
  {
    name: 'translation-task',
    priority: 25,
    conditions: [
      {
        type: 'domain',
        operator: 'eq',
        value: 'translation',
      },
    ],
    targetModel: 'claude-2-translation',
  },
];

ドメインの判定には、リクエストのメタデータやプロンプトの内容を解析する仕組みが必要です。

コストベースのルーティング設定

予算制約に応じたモデル選択を設定します。

typescript// cost-based-routing.ts
const costBasedRules: RoutingRule[] = [
  {
    name: 'budget-limited',
    priority: 15,
    conditions: [
      {
        type: 'cost',
        operator: 'eq',
        value: 'low',
      },
    ],
    targetModel: 'gpt-3.5-turbo',
  },
  {
    name: 'quality-priority',
    priority: 35,
    conditions: [
      {
        type: 'cost',
        operator: 'eq',
        value: 'high',
      },
    ],
    targetModel: 'gpt-4',
  },
];

コスト設定は、アプリケーションの設定や環境変数から動的に取得できます。

ルーティングエンジンの実装

設定されたルールを評価し、最適なモデルを選択するエンジンを実装します。

typescript// routing-engine.ts
import { RoutingRule, Condition } from './routing-config';

class RoutingEngine {
  private rules: RoutingRule[];

  constructor(rules: RoutingRule[]) {
    // 優先度の高い順にソート
    this.rules = rules.sort(
      (a, b) => b.priority - a.priority
    );
  }

  // リクエストコンテキストからモデルを選択
  selectModel(context: RequestContext): string {
    for (const rule of this.rules) {
      if (this.evaluateRule(rule, context)) {
        console.log(
          `ルール "${rule.name}" が適用されました`
        );
        return rule.targetModel;
      }
    }

    // デフォルトモデルを返す
    return 'gpt-3.5-turbo';
  }
}

このクラスは、ルールの優先度順に評価し、最初にマッチしたルールのモデルを選択します。

条件評価の実装

各条件を評価するメソッドを実装します。

typescript// routing-engine.ts (続き)
class RoutingEngine {
  // ... (前述のコード)

  private evaluateRule(
    rule: RoutingRule,
    context: RequestContext
  ): boolean {
    // すべての条件が真である必要がある(AND 条件)
    return rule.conditions.every((condition) =>
      this.evaluateCondition(condition, context)
    );
  }

  private evaluateCondition(
    condition: Condition,
    context: RequestContext
  ): boolean {
    switch (condition.type) {
      case 'size':
        return this.evaluateSizeCondition(
          condition,
          context.tokenCount
        );
      case 'domain':
        return this.evaluateDomainCondition(
          condition,
          context.domain
        );
      case 'cost':
        return this.evaluateCostCondition(
          condition,
          context.costPriority
        );
      default:
        return false;
    }
  }
}

条件タイプごとに専用の評価メソッドを用意することで、コードの可読性が向上します。

サイズ条件の評価実装

トークン数に基づく条件を評価します。

typescript// routing-engine.ts (続き)
class RoutingEngine {
  // ... (前述のコード)

  private evaluateSizeCondition(
    condition: Condition,
    tokenCount: number
  ): boolean {
    const threshold = condition.value as number;

    switch (condition.operator) {
      case 'gt':
        return tokenCount > threshold;
      case 'lt':
        return tokenCount < threshold;
      case 'eq':
        return tokenCount === threshold;
      default:
        return false;
    }
  }
}

数値比較により、入力サイズに応じた柔軟なルーティングが可能になります。

ドメイン条件の評価実装

タスクドメインに基づく条件を評価します。

typescript// routing-engine.ts (続き)
class RoutingEngine {
  // ... (前述のコード)

  private evaluateDomainCondition(
    condition: Condition,
    domain: string
  ): boolean {
    const target = condition.value as string;

    switch (condition.operator) {
      case 'eq':
        return domain === target;
      case 'contains':
        return domain.includes(target);
      default:
        return false;
    }
  }
}

文字列比較により、ドメインの完全一致や部分一致を判定できます。

コスト条件の評価実装

コスト優先度に基づく条件を評価します。

typescript// routing-engine.ts (続き)
class RoutingEngine {
  // ... (前述のコード)

  private evaluateCostCondition(
    condition: Condition,
    costPriority: string
  ): boolean {
    const target = condition.value as string;
    return costPriority === target;
  }
}

コスト優先度は 'low'、'medium'、'high' などの文字列で表現します。

リクエストコンテキストの定義

ルーティング判定に必要な情報をまとめたコンテキストを定義します。

typescript// request-context.ts
interface RequestContext {
  tokenCount: number; // 入力トークン数
  domain: string; // タスクドメイン
  costPriority: string; // コスト優先度
  prompt: string; // 実際のプロンプト
  metadata?: Record<string, any>; // 追加メタデータ
}

このコンテキストは、リクエストごとに動的に生成されます。

具体例

実際の使用例:チャットボットアプリケーション

チャットボットで gpt-oss のモデルルーティングを活用する例を見ていきましょう。

必要なパッケージのインポート

まず、必要なライブラリをインポートします。

typescript// chatbot.ts
import { RoutingEngine } from './routing-engine';
import { RequestContext } from './request-context';
import { RoutingRule } from './routing-config';

gpt-oss と自作のルーティングエンジンを組み合わせて使用します。

ルーティングルールの定義

チャットボット用の包括的なルーティングルールを定義します。

typescript// chatbot.ts (続き)
const chatbotRules: RoutingRule[] = [
  // 緊急度が高く、短い質問は高速モデル
  {
    name: 'urgent-short-query',
    priority: 50,
    conditions: [
      { type: 'size', operator: 'lt', value: 500 },
      { type: 'cost', operator: 'eq', value: 'medium' },
    ],
    targetModel: 'gpt-3.5-turbo',
  },

  // コード関連の質問は専門モデル
  {
    name: 'code-related',
    priority: 45,
    conditions: [
      {
        type: 'domain',
        operator: 'contains',
        value: 'code',
      },
    ],
    targetModel: 'gpt-4-code',
  },

  // 長文の複雑な質問は高性能モデル
  {
    name: 'complex-long-query',
    priority: 40,
    conditions: [
      { type: 'size', operator: 'gt', value: 2000 },
    ],
    targetModel: 'gpt-4',
  },
];

優先度を調整することで、複数の条件が競合する場合の挙動を制御できます。

ルーティングエンジンの初期化

定義したルールでエンジンを初期化します。

typescript// chatbot.ts (続き)
const routingEngine = new RoutingEngine(chatbotRules);

この一行で、ルーティングエンジンが使用可能になります。

トークンカウント関数の実装

入力テキストのトークン数を概算する関数を実装します。

typescript// utils/token-counter.ts
export function estimateTokenCount(text: string): number {
  // 簡易的な推定(実際は tiktoken などを使用)
  // 英語の場合、約4文字で1トークン
  const chars = text.length;
  return Math.ceil(chars / 4);
}

正確なトークン数が必要な場合は、OpenAI の tiktoken ライブラリを使用しましょう。

ドメイン検出関数の実装

プロンプトからタスクドメインを判定する関数を実装します。

typescript// utils/domain-detector.ts
export function detectDomain(prompt: string): string {
  const lowerPrompt = prompt.toLowerCase();

  // キーワードベースの簡易判定
  if (
    lowerPrompt.includes('code') ||
    lowerPrompt.includes('プログラム')
  ) {
    return 'code';
  }
  if (
    lowerPrompt.includes('translate') ||
    lowerPrompt.includes('翻訳')
  ) {
    return 'translation';
  }
  if (
    lowerPrompt.includes('summarize') ||
    lowerPrompt.includes('要約')
  ) {
    return 'summarization';
  }

  return 'general';
}

より高度な判定には、機械学習モデルやルールベースのパターンマッチングを使用できます。

チャットボットのメイン処理

ユーザーからのメッセージを処理し、適切なモデルで応答を生成します。

typescript// chatbot.ts (続き)
import { estimateTokenCount } from './utils/token-counter';
import { detectDomain } from './utils/domain-detector';

async function handleUserMessage(
  userMessage: string,
  costPriority: string = 'medium'
): Promise<string> {
  // リクエストコンテキストを構築
  const context: RequestContext = {
    tokenCount: estimateTokenCount(userMessage),
    domain: detectDomain(userMessage),
    costPriority: costPriority,
    prompt: userMessage,
  };

  // 最適なモデルを選択
  const selectedModel = routingEngine.selectModel(context);
  console.log(`選択されたモデル: ${selectedModel}`);

  // モデルを使用して応答を生成(後述)
  return await generateResponse(selectedModel, userMessage);
}

このように、ルーティングロジックを関数内にカプセル化することで、再利用性が高まります。

モデル実行関数の実装

選択されたモデルで実際に AI 応答を生成します。

typescript// model-executor.ts
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

export async function generateResponse(
  modelName: string,
  prompt: string
): Promise<string> {
  try {
    const response = await openai.chat.completions.create({
      model: modelName,
      messages: [{ role: 'user', content: prompt }],
      temperature: 0.7,
      max_tokens: 1000,
    });

    return response.choices[0].message.content || '';
  } catch (error) {
    console.error(`モデル実行エラー: ${error}`);
    throw error;
  }
}

エラーハンドリングを適切に行うことで、API 障害時の対応も可能になります。

使用例:様々なシナリオ

実際の使用シナリオを見てみましょう。

typescript// usage-examples.ts
import { handleUserMessage } from './chatbot';

// 例1: 短い一般的な質問
const response1 = await handleUserMessage(
  '今日の天気は?',
  'low'
);
// 選択されるモデル: gpt-3.5-turbo(サイズが小さく、コスト優先)

// 例2: コード関連の質問
const response2 = await handleUserMessage(
  'TypeScript で配列をソートするコードを教えて',
  'medium'
);
// 選択されるモデル: gpt-4-code(ドメインが code)

// 例3: 長文の複雑な質問
const longPrompt = `
  以下の要件を満たす Web アプリケーションの設計について、
  詳細なアーキテクチャ提案をお願いします...
  [長文が続く、2000トークン以上]
`;
const response3 = await handleUserMessage(
  longPrompt,
  'high'
);
// 選択されるモデル: gpt-4(サイズが大きく、品質優先)

このように、同じ関数呼び出しでも、入力内容によって最適なモデルが自動選択されます。

以下の図は、実際の処理フローを示しています。

mermaidsequenceDiagram
  participant User as ユーザー
  participant Bot as チャットボット
  participant Engine as ルーティングエンジン
  participant Model as AI モデル

  User->>Bot: メッセージ送信
  Bot->>Bot: トークン数推定
  Bot->>Bot: ドメイン検出
  Bot->>Engine: コンテキスト送信
  Engine->>Engine: ルール評価
  Engine->>Bot: 最適モデル返却
  Bot->>Model: プロンプト送信
  Model->>Bot: 応答生成
  Bot->>User: 応答返却

この処理フローにより、ユーザーは意識することなく最適なモデルで応答を受け取れます。

パフォーマンス最適化とキャッシング

ルーティング処理自体のオーバーヘッドを減らすため、キャッシング戦略を導入します。

モデル選択結果のキャッシング

同じコンテキストパターンに対しては、キャッシュされた結果を返します。

typescript// routing-engine-cached.ts
class CachedRoutingEngine extends RoutingEngine {
  private cache: Map<string, string> = new Map();

  selectModel(context: RequestContext): string {
    // コンテキストからキャッシュキーを生成
    const cacheKey = this.generateCacheKey(context);

    if (this.cache.has(cacheKey)) {
      console.log('キャッシュヒット');
      return this.cache.get(cacheKey)!;
    }

    // キャッシュミス時は通常の選択処理
    const model = super.selectModel(context);
    this.cache.set(cacheKey, model);

    return model;
  }
}

キャッシュにより、ルーティング処理の高速化が期待できます。

キャッシュキーの生成

コンテキストの特徴を抽出してキャッシュキーを作成します。

typescript// routing-engine-cached.ts (続き)
class CachedRoutingEngine extends RoutingEngine {
  // ... (前述のコード)

  private generateCacheKey(
    context: RequestContext
  ): string {
    // サイズは1000トークン単位で丸める
    const sizeCategory = Math.floor(
      context.tokenCount / 1000
    );

    return `${sizeCategory}-${context.domain}-${context.costPriority}`;
  }
}

適切な粒度でキャッシュキーを生成することで、ヒット率とメモリ使用量のバランスが取れます。

動的ルール更新

運用中にルーティングルールを動的に更新できる仕組みを実装します。

ルール更新インターフェース

外部からルールを追加・削除できるメソッドを提供します。

typescript// routing-engine-dynamic.ts
class DynamicRoutingEngine extends CachedRoutingEngine {
  addRule(rule: RoutingRule): void {
    this.rules.push(rule);
    // 優先度順に再ソート
    this.rules.sort((a, b) => b.priority - a.priority);
    // キャッシュをクリア
    this.cache.clear();
    console.log(`ルール "${rule.name}" を追加しました`);
  }

  removeRule(ruleName: string): void {
    this.rules = this.rules.filter(
      (r) => r.name !== ruleName
    );
    this.cache.clear();
    console.log(`ルール "${ruleName}" を削除しました`);
  }
}

ルール変更時にキャッシュをクリアすることで、整合性を保ちます。

設定ファイルからのルール読み込み

JSON ファイルからルールを読み込み、動的に適用します。

typescript// config-loader.ts
import * as fs from 'fs';
import { RoutingRule } from './routing-config';

export function loadRulesFromFile(
  filePath: string
): RoutingRule[] {
  const content = fs.readFileSync(filePath, 'utf-8');
  const rules = JSON.parse(content) as RoutingRule[];

  // バリデーション
  rules.forEach((rule) => {
    if (!rule.name || !rule.targetModel) {
      throw new Error(
        `無効なルール設定: ${JSON.stringify(rule)}`
      );
    }
  });

  return rules;
}

設定ファイルによる管理で、コード変更なしにルールを調整できます。

設定ファイルの例

実際の JSON 設定ファイルの例を示します。

json[
  {
    "name": "urgent-short-query",
    "priority": 50,
    "conditions": [
      {
        "type": "size",
        "operator": "lt",
        "value": 500
      },
      {
        "type": "cost",
        "operator": "eq",
        "value": "medium"
      }
    ],
    "targetModel": "gpt-3.5-turbo"
  },
  {
    "name": "code-related",
    "priority": 45,
    "conditions": [
      {
        "type": "domain",
        "operator": "contains",
        "value": "code"
      }
    ],
    "targetModel": "gpt-4-code"
  }
]

このように、JSON 形式で管理することで、非エンジニアでも設定変更が可能になります。

エラーハンドリングとフォールバック

モデル実行失敗時の対応を実装します。

フォールバックモデルの設定

メインモデルが失敗した場合に試すモデルを定義します。

typescript// model-executor-fallback.ts
const fallbackChain: Record<string, string[]> = {
  'gpt-4': ['gpt-4-turbo', 'gpt-3.5-turbo'],
  'gpt-4-code': ['gpt-4', 'gpt-3.5-turbo'],
  'claude-2': ['claude-instant', 'gpt-3.5-turbo'],
};

export async function generateResponseWithFallback(
  modelName: string,
  prompt: string
): Promise<string> {
  try {
    return await generateResponse(modelName, prompt);
  } catch (error) {
    console.error(`${modelName} で失敗: ${error}`);

    // フォールバックモデルを試行
    const fallbacks = fallbackChain[modelName] || [];
    for (const fallbackModel of fallbacks) {
      try {
        console.log(`フォールバック: ${fallbackModel}`);
        return await generateResponse(
          fallbackModel,
          prompt
        );
      } catch (fallbackError) {
        console.error(
          `${fallbackModel} でも失敗: ${fallbackError}`
        );
      }
    }

    throw new Error('すべてのモデルで失敗しました');
  }
}

フォールバックチェーンにより、可用性が大幅に向上します。

リトライ戦略の実装

一時的なネットワークエラーに対応するリトライ処理を追加します。

typescript// retry-utils.ts
export async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  initialDelay: number = 1000
): Promise<T> {
  let lastError: Error | null = null;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;
      const delay = initialDelay * Math.pow(2, i);
      console.log(
        `リトライ ${i + 1}/${maxRetries}${delay}ms 待機`
      );
      await new Promise((resolve) =>
        setTimeout(resolve, delay)
      );
    }
  }

  throw lastError;
}

エクスポネンシャルバックオフにより、サーバー負荷を軽減しながらリトライできます。

エラー通知とロギング

エラー発生時の詳細ログを記録します。

typescript// error-logger.ts
interface ErrorLog {
  timestamp: string;
  modelName: string;
  errorType: string;
  errorMessage: string;
  context: RequestContext;
}

export function logError(
  modelName: string,
  error: Error,
  context: RequestContext
): void {
  const errorLog: ErrorLog = {
    timestamp: new Date().toISOString(),
    modelName,
    errorType: error.name,
    errorMessage: error.message,
    context,
  };

  console.error(
    'モデル実行エラー:',
    JSON.stringify(errorLog, null, 2)
  );

  // 本番環境では外部ログサービスに送信
  // sendToLogService(errorLog);
}

ログを分析することで、エラーパターンの把握や改善に役立ちます。

テストとモニタリング

ルーティングロジックの正確性を検証するテストを実装します。

ユニットテストの例

各ルールが正しく評価されるかをテストします。

typescript// routing-engine.test.ts
import { RoutingEngine } from './routing-engine';
import { RequestContext } from './request-context';

describe('RoutingEngine', () => {
  const engine = new RoutingEngine([
    {
      name: 'small-input',
      priority: 10,
      conditions: [
        { type: 'size', operator: 'lt', value: 1000 },
      ],
      targetModel: 'gpt-3.5-turbo',
    },
    {
      name: 'code-task',
      priority: 20,
      conditions: [
        { type: 'domain', operator: 'eq', value: 'code' },
      ],
      targetModel: 'gpt-4-code',
    },
  ]);

  test('小さい入力は gpt-3.5-turbo を選択', () => {
    const context: RequestContext = {
      tokenCount: 500,
      domain: 'general',
      costPriority: 'low',
      prompt: 'test',
    };

    expect(engine.selectModel(context)).toBe(
      'gpt-3.5-turbo'
    );
  });

  test('コードドメインは gpt-4-code を選択', () => {
    const context: RequestContext = {
      tokenCount: 500,
      domain: 'code',
      costPriority: 'medium',
      prompt: 'test code',
    };

    expect(engine.selectModel(context)).toBe('gpt-4-code');
  });
});

テストを書くことで、ルール変更時の影響を早期に検出できます。

モニタリング指標の収集

モデル選択の統計情報を収集します。

typescript// monitoring.ts
interface ModelUsageStats {
  modelName: string;
  count: number;
  totalTokens: number;
  averageTokens: number;
}

class UsageMonitor {
  private stats: Map<string, ModelUsageStats> = new Map();

  recordUsage(modelName: string, tokenCount: number): void {
    const existing = this.stats.get(modelName) || {
      modelName,
      count: 0,
      totalTokens: 0,
      averageTokens: 0,
    };

    existing.count++;
    existing.totalTokens += tokenCount;
    existing.averageTokens =
      existing.totalTokens / existing.count;

    this.stats.set(modelName, existing);
  }

  getStats(): ModelUsageStats[] {
    return Array.from(this.stats.values());
  }
}

export const monitor = new UsageMonitor();

統計情報を分析することで、コスト最適化やルール調整の判断材料になります。

ダッシュボード表示用のデータ生成

収集した統計を可視化しやすい形式に変換します。

typescript// monitoring.ts (続き)
class UsageMonitor {
  // ... (前述のコード)

  generateReport(): string {
    const stats = this.getStats();
    const totalRequests = stats.reduce(
      (sum, s) => sum + s.count,
      0
    );

    let report = `=== モデル使用状況レポート ===\n`;
    report += `総リクエスト数: ${totalRequests}\n\n`;

    stats.forEach((stat) => {
      const percentage = (
        (stat.count / totalRequests) *
        100
      ).toFixed(2);
      report += `${stat.modelName}:\n`;
      report += `  リクエスト数: ${stat.count} (${percentage}%)\n`;
      report += `  平均トークン数: ${stat.averageTokens.toFixed(
        0
      )}\n\n`;
    });

    return report;
  }
}

レポートにより、どのモデルが頻繁に使われているかを一目で把握できます。

以下の表は、モニタリング結果の例を示しています。

#モデル名リクエスト数使用率平均トークン数
1gpt-3.5-turbo1,25062.5%450
2gpt-450025.0%2,100
3gpt-4-code20010.0%1,500
4claude-2502.5%3,200

この統計から、コスト効率の良い gpt-3.5-turbo が最も多く使用されていることがわかります。

まとめ

gpt-oss のモデルルーティング機能を活用することで、AI アプリケーションの品質とコスト効率を両立できます。本記事で紹介した設計パターンは、以下のような利点をもたらします。

主な利点

  • 自動最適化: 入力サイズ、ドメイン、コスト制約に応じて最適なモデルを自動選択
  • コスト削減: 必要最小限の性能のモデルを使用することで、API コストを 30-50% 削減可能
  • 保守性向上: ルーティングロジックを一元管理し、設定ファイルで柔軟に調整
  • 高可用性: フォールバック機構により、モデル障害時も継続的にサービス提供

実装のポイント

  1. 優先度ベースのルール設計: 複数の条件が競合する場合でも、明確な優先順位で判断
  2. キャッシング戦略: ルーティング処理のオーバーヘッドを最小化
  3. 段階的な導入: まずシンプルなサイズベースルーティングから始め、徐々に高度化
  4. モニタリングの重視: 使用統計を収集し、継続的にルールを改善

ルーティング機能は、単なるモデル選択の自動化にとどまりません。適切に設計すれば、アプリケーション全体のパフォーマンスとコスト効率を大幅に向上させる戦略的な仕組みになるのです。

ぜひ、あなたのプロジェクトでも gpt-oss のモデルルーティングを活用し、最適な AI 体験を提供してみてください。

関連リンク