0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenAI・Claude・GeminiのAPIを同じ条件で比較する:回答品質・速度・料金の測定方法

0
Last updated at Posted at 2026-11-02

生成AIのAPIを選ぶとき、「どのモデルが一番賢いか」だけで判断すると、実際のシステムでは期待と異なる結果になりがちです。回答品質はプロンプトとの相性に左右され、速度はネットワークや出力トークン数に影響され、料金は入力・出力トークンの比率で変わるためです。

私が3つのAPIを比較するときは、同じ質問を投げるだけでなく、モデル、プロンプト、生成条件、測定回数、採点基準を固定します。結論として、単純な総合順位を作るよりも、「品質」「レイテンシ」「1回答あたりの料金」を分けて測定し、用途ごとに判断するのが再現性の高い方法です。

1. まず比較条件を固定する

API比較の準備から結果の集計までの流れを図にすると次の通りです。

最初に比較対象のモデルIDを明示します。「最新モデル」のような曖昧な指定は避け、実行時点のモデルIDを記録します。モデルのエイリアスは更新される可能性があるため、検証日も一緒に保存します。

比較用の入力は、JSON Lines形式で管理すると扱いやすくなります。

{"id":"summary_001","prompt":"次の文章を100文字以内で要約してください。対象読者はソフトウェア開発者です。"}
{"id":"code_001","prompt":"PythonでCSVを読み込み、空行を除外して別ファイルに保存するコードを書いてください。"}
{"id":"reasoning_001","prompt":"在庫管理APIの設計で、同時更新による在庫数の不整合を防ぐ方法を説明してください。"}

最低限、次の条件を統一します。

  • システムプロンプト
  • temperature
  • 最大出力トークン数
  • ツール、Web検索、ファイル検索の有無
  • 入力データと文字コード
  • 実行回数
  • タイムアウトとリトライ方針

今回は外部ツールを使わず、temperatureを0、最大出力トークン数を512にしました。ただし、temperatureを0にしても完全に同じ回答になるとは限りません。各問題を複数回実行し、中央値を見る必要があります。

2. 3つのAPIを同じスクリプトから呼び出す

SDKをインストールします。

pip install openai anthropic google-genai

APIキーとモデルIDは環境変数から渡します。モデルIDをコードに直接書かないことで、同じベンチマークを別のモデルにも流用できます。

$env:OPENAI_API_KEY = "..."
$env:ANTHROPIC_API_KEY = "..."
$env:GEMINI_API_KEY = "..."

$env:OPENAI_MODEL = "比較対象のOpenAIモデルID"
$env:ANTHROPIC_MODEL = "比較対象のClaudeモデルID"
$env:GEMINI_MODEL = "比較対象のGeminiモデルID"

python benchmark.py

benchmark.pyの最小例です。

import csv
import json
import os
import time

from openai import OpenAI
import anthropic
from google import genai
from google.genai import types

openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
anthropic_client = anthropic.Anthropic(
    api_key=os.environ["ANTHROPIC_API_KEY"]
)
gemini_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])

def call_openai(prompt):
    started = time.perf_counter()
    response = openai_client.chat.completions.create(
        model=os.environ["OPENAI_MODEL"],
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        max_tokens=512,
    )
    elapsed = (time.perf_counter() - started) * 1000
    usage = response.usage
    return (
        response.choices[0].message.content or "",
        usage.prompt_tokens,
        usage.completion_tokens,
        elapsed,
    )

def call_anthropic(prompt):
    started = time.perf_counter()
    response = anthropic_client.messages.create(
        model=os.environ["ANTHROPIC_MODEL"],
        max_tokens=512,
        temperature=0,
        messages=[{"role": "user", "content": prompt}],
    )
    elapsed = (time.perf_counter() - started) * 1000
    return (
        response.content[0].text,
        response.usage.input_tokens,
        response.usage.output_tokens,
        elapsed,
    )

def call_gemini(prompt):
    started = time.perf_counter()
    response = gemini_client.models.generate_content(
        model=os.environ["GEMINI_MODEL"],
        contents=prompt,
        config=types.GenerateContentConfig(
            temperature=0,
            max_output_tokens=512,
        ),
    )
    elapsed = (time.perf_counter() - started) * 1000
    usage = response.usage_metadata
    return (
        response.text or "",
        usage.prompt_token_count,
        usage.candidates_token_count,
        elapsed,
    )

providers = {
    "openai": call_openai,
    "anthropic": call_anthropic,
    "gemini": call_gemini,
}

with open("cases.jsonl", encoding="utf-8") as source, \
     open("results.csv", "w", newline="", encoding="utf-8") as output:

    writer = csv.writer(output)
    writer.writerow([
        "case_id", "provider", "input_tokens",
        "output_tokens", "elapsed_ms", "answer"
    ])

    for line in source:
        case = json.loads(line)

        for name, call in providers.items():
            answer, input_tokens, output_tokens, elapsed = call(
                case["prompt"]
            )
            writer.writerow([
                case["id"],
                name,
                input_tokens,
                output_tokens,
                round(elapsed, 1),
                answer,
            ])

実運用では、429や5xxに対する指数バックオフを追加します。ただし、リトライ時間まで通常の応答速度に含めると、モデル性能とサービス混雑が混ざります。通常リクエストのレイテンシと、リトライ発生率は別々に記録するのが安全です。

3. 速度は平均値だけで判断しない

APIの速度は、少なくとも次の2種類に分けて測定します。

  • TTFT(Time To First Token):最初のトークンが届くまでの時間
  • E2Eレイテンシ:リクエストから全文が返るまでの時間

ストリーミングを使わない場合、測定できるのは基本的にE2Eレイテンシです。チャットUIのように体感速度を重視するなら、ストリーミングAPIでTTFTも測ります。

平均値だけでは、一時的な遅延に引っ張られます。各問題を5回以上実行し、ウォームアップの1回を除外したうえで、p50とp95を集計します。

import statistics

latencies = [820, 790, 910, 1400, 860]

print("p50:", statistics.median(latencies))
print("min:", min(latencies))
print("max:", max(latencies))

長い回答を返すモデルは、最初のトークンが速くてもE2Eでは遅く見えます。そのため、速度比較では出力トークン数も必ず併記します。

4. 料金はトークン数から計算する

料金は「1回いくら」ではなく、入力トークンと出力トークンを分けて計算します。

1回の料金 =
入力トークン数 / 1,000,000 × 入力単価
+
出力トークン数 / 1,000,000 × 出力単価

例えば入力8,000トークン、出力1,000トークンの場合は、次のように計算できます。

def estimate_cost(input_tokens, output_tokens,
                  input_price, output_price):
    return (
        input_tokens / 1_000_000 * input_price
        + output_tokens / 1_000_000 * output_price
    )

cost = estimate_cost(
    input_tokens=8000,
    output_tokens=1000,
    input_price=1.0,
    output_price=5.0,
)

print(f"${cost:.6f}")

単価は検証日ごとに設定ファイルへ保存します。OpenAIは公式料金表、Claudeは公式の料金ドキュメント、Geminiは公式料金表を参照しました。

キャッシュ入力、長いコンテキスト、Batch API、推論トークン、Web検索などを使う場合は、通常の入力・出力単価だけでは計算できません。請求額とスクリプトの推定額が一致しないときは、まずキャッシュや追加ツールの利用を確認します。

5. 回答品質は盲検で採点する

品質比較では、回答を見た瞬間の印象を点数にしないようにします。回答にプロバイダー名を付けず、順番もランダムにして採点します。

私がよく使う採点項目は次の4つです。

  • 正確性:事実やコードに誤りがないか
  • 指示遵守:文字数、形式、対象読者などを守ったか
  • 完全性:必要な観点を漏らしていないか
  • 可読性:日本語として自然で理解しやすいか

各項目を0〜2点で採点し、合計点だけでなく項目別の点数も保存します。コード生成では、文章の印象よりもテストを優先します。生成コードを実行し、構文エラー、境界値、例外処理を確認します。

LLMに採点させる方法もありますが、採点者自身の好みに影響されます。まず人手の基準回答を作り、LLM判定を使う場合もサンプルを人間が再確認します。

まとめ

OpenAI、Claude、Geminiを公平に比較するには、同じプロンプトを送るだけでは不十分です。モデルID、生成条件、入力データ、実行回数を固定し、品質・速度・料金を別の指標として記録します。

実際に測定してみると、品質が高いモデルほど常に高価とは限らず、短い回答では出力トークン数の差が料金に大きく影響します。また、平均レイテンシだけではユーザー体験を説明できません。

比較結果には検証日、モデルID、単価、入力データ、集計方法を添えます。そうすれば、モデルや料金が更新された後も同じ条件で再測定でき、用途に合ったAPIを継続的に選べます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?