OpenAI SDK 互換レイヤーで Claude を試す

Claude.ai エンジニアリング 6分で読めます

すでに OpenAI SDK で書かれたコードがある状態で Claude を試したいとき、Anthropic は OpenAI SDK 互換レイヤーを用意しています。base_url・APIキー・モデル名の3か所を変えるだけで、既存の chat.completions.create がそのまま Claude を呼びます。ただし公式は「これはモデルの能力を試して比べるための機能で、ほとんどの用途において長期的・本番向けの解ではない」と明記しています。この記事では、動かし方と、黙って無視されるフィールド、そして本番で native な Claude API に戻すべき判断点を整理します。

互換レイヤーは何のためにあるか

この互換レイヤーの目的は明確で、公式ドキュメントは「OpenAI SDK を使って Claude API をテストできるようにする互換レイヤー」と説明しています。数行の変更で Anthropic のモデルの能力を素早く評価できる、という位置づけです。

同時に、次の注意が添えられています。この互換レイヤーは主にモデルの能力をテスト・比較するためのもので、ほとんどの用途において長期的・本番向けの解決策とは考えられていません。完全に機能する状態を保ち、破壊的変更を入れない意図はあるものの、優先されるのは Claude API 本体の信頼性と有効性である、と書かれています。

つまり、使いどころは次のような場面です。既存の OpenAI ベースのコードで Claude の出力品質を確かめたい。複数モデルを同じコードで並べて比べたい。移行するかどうかを決める前に、手元で少し動かしてみたい。逆に、PDF 処理・引用(citations)・thinking・プロンプトキャッシュといった Claude 固有の機能を使いたいなら、最初から native な Claude API を使うよう公式が勧めています。

3か所を変えるだけで動く

変更するのは次の3点だけです。

公式のクイックスタート例は次のとおりです。

import os

from openai import OpenAI

client = OpenAI(
    api_key=os.environ.get("ANTHROPIC_API_KEY"),  # Claude API キー
    base_url="https://api.anthropic.com/v1/",  # Claude API のエンドポイント
)

response = client.chat.completions.create(
    model="claude-opus-5",  # Claude のモデル名
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Who are you?"},
    ],
)

print(response.choices[0].message.content)

TypeScript でも同じ考え方で、baseURLhttps://api.anthropic.com/v1/ を渡し、model に Claude のモデル名を指定します。

import OpenAI from "openai";

const openai = new OpenAI({
  apiKey: "ANTHROPIC_API_KEY", // Claude API キー
  baseURL: "https://api.anthropic.com/v1/" // Claude API のエンドポイント
});

const response = await openai.chat.completions.create({
  messages: [{ role: "user", content: "Who are you?" }],
  model: "claude-opus-5" // Claude のモデル名
});

console.log(response.choices[0].message.content);

レート制限は /v1/messages エンドポイントに対する Anthropic の通常の制限に従います。互換レイヤー用の別枠があるわけではありません。

黙って無視されるフィールドを知る

この機能でいちばん重要なのは、対応していないフィールドの多くがエラーにならず黙って無視されることです。公式も「ほとんどの非対応フィールドはエラーを出すのではなく静かに無視される」と明記しています。指定したつもりの設定が効いていないのに、レスポンスは正常に返ります。

とくに意図が変わってしまうものを挙げます。

値の範囲が変わるものもあります。temperature は 0 以上 1 以下で、1 を超える値は 1 に丸められますn1 でなければなりませんstop は空白文字以外の停止シーケンスなら動作します。

一方、modelmax_tokensmax_completion_tokensstreamstream_optionstop_pparallel_tool_calls は完全にサポートされます。ツール定義側では namedescriptionparameters が完全対応です。

レスポンス側では、choices[]長さが常に 1 になります。usage.completion_tokens_detailsusage.prompt_tokens_detailslogprobsservice_tiersystem_fingerprintchoices[].message.refusalchoices[].message.audio常に空です。ヘッダーでは openai-version が常に 2020-10-01openai-processing-ms は常に空になります。レート制限系ヘッダー(x-ratelimit-*)と retry-afterrequest-id は完全対応です。

エラーの形式は OpenAI API と揃えられていますが、詳細なメッセージは同等ではありません。エラーメッセージはログと調査の用途だけに使い、内容で分岐しないようにします。

system/developer メッセージは先頭にまとめられる

入力の大半は Anthropic の API パラメータへ素直に対応しますが、system / developer メッセージの扱いだけは明確に違います

OpenAI では system / developer メッセージを会話の途中に何度でも置けます。ところが Anthropic の API は先頭の system メッセージ1つだけを受け付けます。そこで互換レイヤーは、渡された system / developer メッセージをすべて集め、間に改行1つ(\n)を挟んで連結し、1つの system メッセージとして messages の先頭に置きます

この挙動を「hoisting(先頭への持ち上げ)」と呼んでいます。実務上の意味は2つあります。

第一に、会話の途中に置いた指示が、その位置での指示として効かなくなります。「3ターン目以降はこの形式で答えて」という設計を system メッセージの位置で表現していた場合、その位置情報は失われ、最初からの指示として扱われます。

第二に、連結の順序に依存した書き方は壊れます。後から置いた system メッセージで前の指示を上書きするつもりだった場合、上書きではなく単に連結された2つの指示が並ぶことになります。矛盾する指示が同時に渡っている状態なので、出力は安定しません。

なお messages[n].name は system / developer / user / assistant / tool のいずれの role でも無視されます。role ごとの対応は、user の content が文字列・type == "text"type == "image_url"url まで対応(detail は無視、input_audiofile は無視)、assistant は contenttool_callsfunction_call が対応(refusalaudio は無視)、tool は contenttool_call_idtool_choice が対応です。

thinking は指定できるが思考過程は返らない

thinking は thinking パラメータを足すことで有効にできます。OpenAI SDK には無いパラメータなので、Python では extra_body 経由で渡します。

response = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[{"role": "user", "content": "Who are you?"}],
    extra_body={"thinking": {"type": "enabled", "budget_tokens": 2000}},
)

TypeScript では型に無いフィールドなので @ts-expect-error を付けて渡す形が公式例に示されています。

const response = await openai.chat.completions.create({
  messages: [{ role: "user", content: "Who are you?" }],
  model: "claude-sonnet-4-6",
  // @ts-expect-error
  thinking: { type: "enabled", budget_tokens: 2000 }
});

ここで前提を押さえておく必要があります。現行モデルの thinking は適応的(adaptive)で、いつ・どれだけ考えるかは Claude が決めます。Claude 5 系では既定で有効です。手動で設定する extended thinking はレガシーモードという扱いになっています。つまり上の例は「思考を有効にする」というより「レガシーな手動設定を明示的に使う」書き方です。

そしてもう1つ、OpenAI SDK 経由では Claude の詳細な思考過程が返りません。thinking は複雑なタスクでの推論を改善しますが、その途中経過を取り出したいなら native な Claude API を使う必要があります。思考のログを保存したい、思考ブロックを次のターンに渡したい、といった用途は互換レイヤーでは満たせません。

本番で native API に戻す判断

移行の判断は、次の2つを分けて考えると決まります。

1つ目は、失われている機能があるかどうかです。PDF 処理・引用(citations)・thinking の完全な機能・プロンプトキャッシュを使いたいなら、互換レイヤーでは足りません。JSON スキーマへの準拠が必要な場合も、response_formatstrict が無視されるため、native API の Structured Outputs に移る必要があります。

2つ目は、プロンプトの調整先です。公式が明示的に注意しているとおり、OpenAI 向けに念入りに調整したプロンプトは、OpenAI 向けに最適化されている可能性が高いため、Claude 向けに作り直すことを検討すべきだとされています。互換レイヤーで出力が期待どおりでなかったとしても、それがモデルの限界なのかプロンプトの相性なのかは、プロンプトを Claude 向けに書き直してみるまで分かりません。

したがって現実的な進め方はこうなります。まず互換レイヤーで動くことと大まかな出力品質を確かめる。次に、本気で評価する段階でプロンプトを Claude 向けに書き直す。ここで native SDK に移れば、同時にプロンプトキャッシュや Structured Outputs も使えるようになります。本番投入の前には native API へ移す、というのが公式の位置づけに沿った判断です。

互換性の既知の制限について不具合を見つけた場合は、公式が用意しているフィードバックフォームから報告できます。