T-CREATOR

<div />

LlamaIndex × OpenAI/Claude/Gemini 設定手順:モデル切替とコスト最適化

LlamaIndex × OpenAI/Claude/Gemini 設定手順:モデル切替とコスト最適化

LlamaIndex を使った開発で、OpenAI、Claude、Gemini といった複数の LLM プロバイダーを自在に切り替えられたら、開発効率とコスト最適化の両立が実現できます。この記事では、LlamaIndex で複数の LLM を設定し、用途に応じてモデルを切り替える実践的な手順を解説いたします。

プロジェクトの規模が大きくなるにつれて、API コストは無視できない問題になりますよね。タスクの種類によって最適なモデルを選択できれば、品質を保ちながらコストを大幅に削減できるでしょう。

背景

LlamaIndex とは

LlamaIndex は、LLM(大規模言語モデル)を活用したアプリケーション開発を支援するデータフレームワークです。RAG(Retrieval-Augmented Generation)パターンの実装を簡単にし、データのインデックス化、検索、LLM へのクエリを統合的に扱えます。

複数 LLM プロバイダーのサポート

LlamaIndex は、OpenAI、Anthropic(Claude)、Google(Gemini)など、主要な LLM プロバイダーに対応しています。各プロバイダーは異なる特徴を持っており、用途に応じて使い分けることで最適な結果が得られるでしょう。

以下の図で、LlamaIndex がどのように LLM プロバイダーと連携するかを示します。

mermaidflowchart TB
  app["アプリケーション"] -->|データ| llamaindex["LlamaIndex"]
  llamaindex -->|インデックス化| index["Vector Index"]
  llamaindex -->|クエリ| llm_router["LLM Router"]
  llm_router -->|タスクA| openai["OpenAI<br/>GPT-4/GPT-3.5"]
  llm_router -->|タスクB| claude["Anthropic<br/>Claude 3"]
  llm_router -->|タスクC| gemini["Google<br/>Gemini Pro"]
  openai -->|応答| llamaindex
  claude -->|応答| llamaindex
  gemini -->|応答| llamaindex
  llamaindex -->|結果| app

図で理解できる要点

  • LlamaIndex が複数の LLM プロバイダーを統一的に扱う
  • タスクの種類に応じて最適なモデルにルーティング可能
  • すべての応答が LlamaIndex を通じてアプリケーションに返される

各プロバイダーの特徴

#プロバイダー主要モデル強み料金目安(入力/百万トークン)
1OpenAIGPT-4、GPT-3.5-turbo高精度、豊富な事例$0.50〜$30
2AnthropicClaude 3 Opus/Sonnet/Haiku長文理解、安全性$0.25〜$15
3GoogleGemini Pro/Flashコスト効率、マルチモーダル$0.075〜$7

課題

モデル選択の複雑さ

プロジェクトで複数の LLM を使う場合、以下のような課題に直面します。

コストと品質のトレードオフ:高性能な GPT-4 は優れた結果を出しますが、コストが高くなります。一方、GPT-3.5-turbo や Gemini Flash は安価ですが、複雑なタスクでは精度が落ちることもあるでしょう。

プロバイダー固有の設定:各プロバイダーは API キー、エンドポイント、パラメータ形式が異なるため、切り替えのたびにコードを修正する必要がありました。

運用時のモデル切り替え:開発環境では高性能モデル、本番環境ではコスト効率の良いモデルを使いたいケースがありますが、環境ごとに設定を管理するのは煩雑ですね。

エラーハンドリングの問題

複数のプロバイダーを使うと、それぞれ異なるエラーが発生します。

よくあるエラーコード

#エラーコードプロバイダー発生条件
1Error 401: Unauthorized全プロバイダーAPI キーが無効または未設定
2Error 429: Rate limit exceededOpenAI、Anthropicリクエスト制限超過
3Error 500: Internal Server Error全プロバイダープロバイダー側の一時的障害
4InvalidRequestErrorOpenAIトークン数制限超過

以下の図で、エラー発生時のフローを示します。

mermaidflowchart TD
  start["APIリクエスト"] --> check["APIキー<br/>検証"]
  check -->|有効| send["LLMへ送信"]
  check -->|無効| err401["Error 401:<br/>Unauthorized"]
  send -->|成功| response["応答取得"]
  send -->|失敗| check_err["エラー種別<br/>判定"]
  check_err -->|429| err429["Rate Limit<br/>リトライ待機"]
  check_err -->|500| err500["Server Error<br/>別モデル切替"]
  check_err -->|その他| err_other["エラー<br/>ログ記録"]
  err429 --> retry["リトライ"]
  retry --> send
  err500 --> fallback["フォールバック<br/>モデル使用"]
  fallback --> response

図で理解できる要点

  • API キー検証が最初のチェックポイント
  • エラー種別によって異なるリカバリー戦略を適用
  • Rate Limit はリトライ、Server Error は別モデルへフォールバック

解決策

LlamaIndex による統一的な LLM 管理

LlamaIndex は、複数の LLM プロバイダーを統一的なインターフェースで扱えるように設計されています。これにより、モデルの切り替えが簡単になり、コードの保守性が向上するでしょう。

モデル切替の戦略

以下の 3 つの戦略でモデルを使い分けます。

タスクベース切替:タスクの種類に応じてモデルを選択します。例えば、要約タスクには高性能な GPT-4、簡単な分類タスクには Gemini Flash を使用します。

コストベース切替:予算制約に応じて、コスト効率の良いモデルを優先的に使用します。

フォールバック戦略:メインのモデルでエラーが発生した場合、自動的に別のモデルに切り替える仕組みを実装します。

以下の図で、タスクベースの切替フローを示します。

mermaidflowchart LR
  task["タスク入力"] --> classify["タスク分類"]
  classify -->|要約・分析| gpt4["GPT-4<br/>高精度"]
  classify -->|質問応答| claude["Claude Sonnet<br/>バランス型"]
  classify -->|分類・抽出| gemini["Gemini Flash<br/>高速・低コスト"]
  gpt4 --> result["結果出力"]
  claude --> result
  gemini --> result

環境変数による設定管理

API キーやモデル設定を環境変数で管理することで、環境ごとに異なる設定を簡単に切り替えられます。

具体例

環境構築

まず、必要なパッケージをインストールします。

bash# Yarn を使ってパッケージをインストール
yarn add llamaindex openai @anthropic-ai/sdk @google/generative-ai
yarn add -D @types/node dotenv

次に、TypeScript の設定ファイルを用意します。

json{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

環境変数の設定

.envファイルを作成し、各プロバイダーの API キーを設定します。

bash# OpenAI設定
OPENAI_API_KEY=sk-your-openai-api-key

# Anthropic (Claude) 設定
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key

# Google (Gemini) 設定
GOOGLE_API_KEY=your-google-api-key

# デフォルトモデル設定
DEFAULT_LLM_PROVIDER=openai
DEFAULT_MODEL_NAME=gpt-3.5-turbo

基本的な LLM 設定(OpenAI)

LlamaIndex で OpenAI のモデルを使用する基本的な設定です。

typescriptimport { OpenAI, Settings } from 'llamaindex';
import * as dotenv from 'dotenv';

// 環境変数を読み込み
dotenv.config();

次に、OpenAI の LLM インスタンスを作成します。

typescript// OpenAI LLMの初期化
const openaiLLM = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  model: 'gpt-4', // または 'gpt-3.5-turbo'
  temperature: 0.7,
  maxTokens: 1000,
});

// グローバル設定に適用
Settings.llm = openaiLLM;

このコードでは、OpenAI の GPT-4 モデルを初期化し、グローバル設定に適用しています。temperatureは応答のランダム性を制御し、maxTokensは最大トークン数を制限します。

Claude(Anthropic)の設定

Claude モデルを使用する場合の設定です。LlamaIndex では Anthropic のモデルもサポートされています。

typescriptimport { Anthropic } from 'llamaindex';

// Claude LLMの初期化
const claudeLLM = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
  model: 'claude-3-sonnet-20240229', // または haiku, opus
  temperature: 0.7,
  maxTokens: 1000,
});

Claude モデルには、Haiku(高速・低コスト)、Sonnet(バランス型)、Opus(高精度)の 3 つのバリエーションがあります。タスクの要件に応じて選択しましょう。

Gemini(Google)の設定

Google の Gemini モデルを使用する設定です。

typescriptimport { Gemini } from 'llamaindex';

// Gemini LLMの初期化
const geminiLLM = new Gemini({
  apiKey: process.env.GOOGLE_API_KEY,
  model: 'gemini-pro', // または gemini-pro-vision
  temperature: 0.7,
  maxTokens: 1000,
});

Gemini は、テキストのみのgemini-proとマルチモーダルなgemini-pro-visionがあります。画像を扱う場合は後者を選択してください。

モデル切替の実装

タスクの種類に応じてモデルを動的に切り替える関数を実装します。

typescript/**
 * タスクの種類を定義
 */
type TaskType =
  | 'summary'
  | 'qa'
  | 'classification'
  | 'analysis';

/**
 * タスクに応じて最適なLLMを選択
 */
function selectLLM(taskType: TaskType) {
  switch (taskType) {
    case 'summary':
    case 'analysis':
      // 高精度が必要なタスクはGPT-4
      return openaiLLM;
    case 'qa':
      // 質問応答はClaude Sonnetでバランス良く
      return claudeLLM;
    case 'classification':
      // 分類タスクはGemini Flashで高速・低コスト
      return geminiLLM;
    default:
      return openaiLLM;
  }
}

この関数では、タスクの種類に応じて最適な LLM を返します。要約や分析には高精度な GPT-4、質問応答にはバランスの良い Claude、分類には高速で低コストな Gemini を使用しています。

実践的な使用例

実際に LlamaIndex を使ってドキュメントをインデックス化し、質問に答える例を示します。

typescriptimport {
  VectorStoreIndex,
  Document,
  Settings
} from 'llamaindex';

/**
 * ドキュメントのインデックス化と検索
 */
async function processDocument(
  text: string,
  query: string,
  taskType: TaskType
) {
  try {
    // タスクに応じたLLMを選択
    const llm = selectLLM(taskType);
    Settings.llm = llm;

次に、ドキュメントを作成してインデックス化します。

typescript// ドキュメントの作成
const document = new Document({ text });

// インデックスの作成
const index = await VectorStoreIndex.fromDocuments([
  document,
]);

console.log(`使用モデル: ${llm.model}`);

最後に、クエリエンジンを使って質問に答えます。

typescript    // クエリエンジンの作成と実行
    const queryEngine = index.asQueryEngine();
    const response = await queryEngine.query(query);

    return {
      answer: response.toString(),
      model: llm.model,
    };
  } catch (error) {
    console.error('処理エラー:', error);
    throw error;
  }
}

このコードでは、テキストをインデックス化し、選択された LLM を使ってクエリに応答しています。エラーが発生した場合は、適切にログを記録して例外を再スローします。

フォールバック機能の実装

メインのモデルでエラーが発生した場合、自動的に別のモデルに切り替える仕組みを実装します。

typescript/**
 * フォールバック付きLLM実行
 */
async function executeWithFallback(
  text: string,
  query: string,
  taskType: TaskType
) {
  // 優先順位付きのLLMリスト
  const llmPriority = [
    selectLLM(taskType),
    claudeLLM,
    geminiLLM,
  ];

次に、各 LLM を順番に試行します。

typescript  for (let i = 0; i < llmPriority.length; i++) {
    try {
      Settings.llm = llmPriority[i];

      const document = new Document({ text });
      const index = await VectorStoreIndex.fromDocuments([document]);
      const queryEngine = index.asQueryEngine();
      const response = await queryEngine.query(query);

      console.log(`成功: ${llmPriority[i].model}を使用`);
      return response.toString();
    } catch (error: any) {
      console.error(`エラー: ${llmPriority[i].model}`, error.message);

エラーハンドリングと次のモデルへの切り替えを行います。

typescript      // 最後のLLMでもエラーの場合は例外をスロー
      if (i === llmPriority.length - 1) {
        throw new Error('すべてのLLMで処理に失敗しました');
      }

      // 次のLLMを試行
      console.log(`次のモデルを試行: ${llmPriority[i + 1].model}`);
    }
  }
}

この実装では、優先順位付きの LLM リストを順番に試行し、1 つが失敗しても次のモデルで処理を続行します。すべてのモデルで失敗した場合のみエラーを返すでしょう。

コスト最適化の実装

API コールのコストを追跡し、予算内で運用するための仕組みを実装します。

typescript/**
 * モデルごとのコスト設定(入力/百万トークンあたりのドル)
 */
const MODEL_COSTS = {
  'gpt-4': { input: 30, output: 60 },
  'gpt-3.5-turbo': { input: 0.5, output: 1.5 },
  'claude-3-opus-20240229': { input: 15, output: 75 },
  'claude-3-sonnet-20240229': { input: 3, output: 15 },
  'claude-3-haiku-20240307': { input: 0.25, output: 1.25 },
  'gemini-pro': { input: 0.5, output: 1.5 },
  'gemini-1.5-flash': { input: 0.075, output: 0.3 },
};

次に、コストを計算する関数を実装します。

typescript/**
 * 推定コストの計算
 */
function estimateCost(
  model: string,
  inputTokens: number,
  outputTokens: number
): number {
  const costs =
    MODEL_COSTS[model as keyof typeof MODEL_COSTS];
  if (!costs) {
    console.warn(
      `モデル ${model} のコスト情報がありません`
    );
    return 0;
  }

  const inputCost = (inputTokens / 1_000_000) * costs.input;
  const outputCost =
    (outputTokens / 1_000_000) * costs.output;

  return inputCost + outputCost;
}

コスト追跡機能付きの実行関数を作成します。

typescript/**
 * コスト追跡付き実行
 */
async function executeWithCostTracking(
  text: string,
  query: string,
  taskType: TaskType,
  maxCostUSD: number = 0.01 // デフォルト予算: 1セント
) {
  const llm = selectLLM(taskType);
  Settings.llm = llm;

  // 入力トークン数の推定(簡易計算: 4文字=1トークン)
  const estimatedInputTokens = (text.length + query.length) / 4;
  const estimatedOutputTokens = 500; // 出力の推定値

  const estimatedCost = estimateCost(
    llm.model,
    estimatedInputTokens,
    estimatedOutputTokens
  );

予算チェックと実行を行います。

typescript  // 予算チェック
  if (estimatedCost > maxCostUSD) {
    console.warn(
      `推定コスト $${estimatedCost.toFixed(4)} が予算 $${maxCostUSD} を超過`
    );
    // より安価なモデルに切り替え
    Settings.llm = geminiLLM;
    console.log(`低コストモデルに切替: ${geminiLLM.model}`);
  }

  // 実行
  const document = new Document({ text });
  const index = await VectorStoreIndex.fromDocuments([document]);
  const queryEngine = index.asQueryEngine();
  const response = await queryEngine.query(query);

  return {
    answer: response.toString(),
    model: Settings.llm.model,
    estimatedCost: estimatedCost.toFixed(4),
  };
}

このコスト追跡機能により、予算を超過する場合は自動的に低コストなモデルに切り替えることができます。

エラーハンドリングの実装

各プロバイダーで発生する可能性のあるエラーを適切に処理します。

typescript/**
 * エラー種別の判定
 */
function identifyErrorType(error: any): string {
  const message = error.message || '';

  if (
    error.status === 401 ||
    message.includes('Unauthorized')
  ) {
    return 'AUTH_ERROR';
  }
  if (
    error.status === 429 ||
    message.includes('rate limit')
  ) {
    return 'RATE_LIMIT';
  }
  if (
    error.status === 500 ||
    message.includes('Internal Server')
  ) {
    return 'SERVER_ERROR';
  }
  if (message.includes('maximum context length')) {
    return 'TOKEN_LIMIT';
  }
  return 'UNKNOWN_ERROR';
}

エラータイプに応じた処理を実装します。

typescript/**
 * エラー処理付き実行
 */
async function executeWithErrorHandling(
  text: string,
  query: string,
  taskType: TaskType
) {
  const maxRetries = 3;
  let retryCount = 0;

  while (retryCount < maxRetries) {
    try {
      const result = await executeWithFallback(text, query, taskType);
      return result;
    } catch (error: any) {
      const errorType = identifyErrorType(error);

エラータイプごとに異なる対応を行います。

typescript      switch (errorType) {
        case 'AUTH_ERROR':
          console.error('Error 401: APIキーが無効です');
          throw new Error('認証エラー: APIキーを確認してください');

        case 'RATE_LIMIT':
          console.warn('Error 429: レート制限超過、リトライ待機中...');
          retryCount++;
          // 指数バックオフで待機(2秒、4秒、8秒)
          await new Promise(resolve =>
            setTimeout(resolve, Math.pow(2, retryCount) * 1000)
          );
          break;

        case 'SERVER_ERROR':
          console.error('Error 500: サーバーエラー、フォールバック実行');
          retryCount++;
          break;

トークン制限エラーとその他のエラーを処理します。

typescript        case 'TOKEN_LIMIT':
          console.error('トークン数超過: テキストを分割して再試行');
          // テキストを半分に分割して再試行
          const halfLength = Math.floor(text.length / 2);
          const firstHalf = text.substring(0, halfLength);
          return await executeWithErrorHandling(
            firstHalf,
            query,
            taskType
          );

        default:
          console.error('予期しないエラー:', error.message);
          throw error;
      }
    }
  }

  throw new Error('最大リトライ回数に達しました');
}

このエラーハンドリング実装により、各種エラーに対して適切なリカバリー戦略を適用できますね。

実行例とテストコード

実際に複数のタスクを実行してモデルの切り替えを確認します。

typescript/**
 * メイン実行関数
 */
async function main() {
  const sampleText = `
    LlamaIndexは、LLMアプリケーション開発のためのデータフレームワークです。
    RAGパターンを簡単に実装でき、複数のLLMプロバイダーをサポートしています。
    OpenAI、Anthropic、Googleなどのモデルを統一的に扱うことができます。
  `;

  console.log('=== タスク1: 要約(GPT-4使用)===');
  const summary = await executeWithCostTracking(
    sampleText,
    'このテキストを3行で要約してください',
    'summary'
  );
  console.log(summary);

別のタスクタイプで実行します。

typescript  console.log('\n=== タスク2: 質問応答(Claude使用)===');
  const qa = await executeWithCostTracking(
    sampleText,
    'LlamaIndexの主な機能は何ですか?',
    'qa'
  );
  console.log(qa);

  console.log('\n=== タスク3: 分類(Gemini使用)===');
  const classification = await executeWithCostTracking(
    sampleText,
    'このテキストのカテゴリを判定してください',
    'classification'
  );
  console.log(classification);
}

最後に、エラーハンドリングを含めた実行を行います。

typescript// 実行
main()
  .then(() => {
    console.log('\n処理が正常に完了しました');
  })
  .catch((error) => {
    console.error('エラーが発生しました:', error.message);
    process.exit(1);
  });

このテストコードを実行すると、各タスクで異なるモデルが使用され、コストが表示されることが確認できるでしょう。

コスト比較とモデル選択の指針

実際の使用例をもとに、各モデルのコストパフォーマンスを比較します。

#タスク種別推奨モデル理由100 万トークンあたりコスト
1要約・分析GPT-4 / Claude Opus高精度が必要$30〜$45
2質問応答Claude Sonnetバランス型$3〜$9
3分類・抽出Gemini Flash高速・低コスト$0.075〜$0.3
4大量処理GPT-3.5-turboコスト重視$0.5〜$1.5

選択の指針

  • プロトタイプ開発では高性能モデルで品質を確認
  • 本番環境では処理量に応じてコスト効率の良いモデルを選択
  • クリティカルな処理のみ高性能モデル、それ以外は低コストモデルで運用

まとめ

LlamaIndex を使った複数 LLM プロバイダーの設定と切り替え手順について解説しました。OpenAI、Claude、Gemini の 3 つの主要プロバイダーを統一的に扱い、タスクに応じて最適なモデルを選択する方法を実装できましたね。

コスト最適化は、モデルの特性を理解し、タスクの要件に応じて使い分けることで実現できます。フォールバック機能を実装することで、エラーが発生しても処理を継続でき、システムの信頼性が向上するでしょう。

環境変数による設定管理、エラーハンドリング、コスト追跡機能を組み合わせることで、本番環境でも安心して運用できるシステムが構築できます。今回紹介した実装パターンを活用して、効率的な LLM アプリケーション開発を進めてください。

関連リンク

;