ClaudeへOpenAI SDKのまま移行する — 互換レイヤーの対応表と落とし穴
OpenAI SDKのコードをほぼそのままClaudeへ向ける互換レイヤーの使い方。3つの変更点、無視されるフィールド、移行後の症状と原因の対応、検証コードを扱います。
OpenAI SDKのままClaudeを試せる互換レイヤーとは
Anthropicは、公式OpenAI SDKのコードをほぼそのまま使ってClaude APIを呼び出せる互換レイヤーを提供しています。ベースURLとAPIキー、モデル名の3か所を書き換えるだけで、既存のOpenAI SDKの資産を使ってClaudeのモデル性能を試せます。
ただしこれは評価・比較用の機能です。公式ドキュメントは、互換レイヤーを「多くの用途では長期利用や本番運用を想定したものではない」としています。壊れない・動き続けることは意図されていますが、優先されるのはネイティブのClaude APIの信頼性と有効性です。
PDF処理、Citations、thinking、プロンプトキャッシュといったClaude APIの機能一式を使いたい場合も、ネイティブAPIが前提です。評価で問題が無かったからといって本番の設計を互換レイヤー前提で固定すると、後から機能不足に気づいたときの手戻りが大きくなります。
3つの変更だけでClaudeに向き先を変える
OpenAI SDKの向き先を変える3点
- 1
ベースURLを差し替える
https://api.anthropic.com/v1/に変更します。 - 2
APIキーを差し替える
OpenAIのキーをClaude APIキーに置き換えます。
- 3
モデル名を差し替える
Claudeのモデル名(
claude-opus-5-5など)に変更します。
Pythonなら、変更後のコードは次のとおりです。
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ.get("ANTHROPIC_API_KEY"),
base_url="https://api.anthropic.com/v1/",
)
response = client.chat.completions.create(
model="claude-opus-5-5",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Who are you?"},
],
)
print(response.choices[0].message.content)TypeScriptでも変更点は同じ3つです。互換レイヤーでなくネイティブのTypeScript SDK実装へ移行する手順は別記事にまとめています。
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.ANTHROPIC_API_KEY,
baseURL: "https://api.anthropic.com/v1/"
});
const response = await openai.chat.completions.create({
messages: [{ role: "user", content: "Who are you?" }],
model: "claude-opus-5-5"
});公式のクイックスタートには、Python・TypeScriptのほかC#・Go・Java・Rubyの例が載っています。PHPには公式のOpenAI SDKが無いため例がなく、ネイティブのClaude API呼び出しを使う案内になっています。
複数ワークスペースのキーはヘッダーも必要
個人キーやサービスアカウントキーのうち、複数のワークスペースにアクセスできるものを使う場合は、リクエストごとに anthropic-workspace-id ヘッダーも送ります。1つのワークスペースに絞って作ったキーなら省略できます。
ヘッダーはPythonでは default_headers、TypeScriptでは defaultHeaders で一度だけ指定できます。
client = OpenAI(
api_key=os.environ.get("ANTHROPIC_API_KEY"),
base_url="https://api.anthropic.com/v1/",
default_headers={"anthropic-workspace-id": "wrkspc_..."},
)ワークスペースIDはConsoleの設定画面(Settings → Workspaces)のID列で見られます。List Workspacesエンドポイントでも取得できますが、既定のワークスペースは include_default=true を付けたときだけ一覧に含まれます。
どのワークスペースで処理されたかは、レスポンスの anthropic-workspace-id ヘッダーでも分かります。キーを使い回している組織が、請求や利用量の振り分けを後から突き合わせるときの手がかりになります。
移行後に起きる症状と原因の対応
互換レイヤーは、サポートしていないフィールドの大半をエラーにせず無視します。症状から引ける形にすると次のようになります。
| 症状 | 原因 | 対処 |
|---|---|---|
| ツール呼び出しの引数がスキーマに沿わない | 原因strict が無視される | 対処ネイティブAPIのStructured Outputsを使う |
response_format を指定してもJSONで返らない | 原因response_format が無視される | 対処同上。互換レイヤー側では固定できない |
n=2 で複数候補が取れない | 原因n は1のみ。レスポンスの choices[] も常に1要素 | 対処並列に複数回リクエストする |
usage.prompt_tokens_details が空でキャッシュ分が見えない | 原因内訳フィールドは常に空 | 対処usage.total_tokens ベースで集計する |
| 音声を渡しても反応が無い | 原因音声入力は無視され、入力から取り除かれる | 対処音声を使う機能は互換レイヤーに載せない |
| 同じプロンプトで回答の傾向が変わる | 原因OpenAI向けに調整されたプロンプトはClaudeで同じ効果になるとは限らない | 対処プロンプトを書き直す |
| リクエストが増えるほどコストが重い | 原因プロンプトキャッシュに非対応 | 対処ネイティブSDKへ移行する |
プロンプトキャッシュを使いたいコスト重視の本番実装は、Prompt Cachingをネイティブに使えるAnthropic SDKへ移行してから組み込みます。
対応フィールドと無視されるフィールドの一覧
リクエストパラメータの扱いを、公式の対応表から主要なものに絞ってまとめました。
| パラメータ | 互換レイヤーでの扱い |
|---|---|
max_tokens / max_completion_tokens / stream / stream_options / top_p / parallel_tool_calls | 互換レイヤーでの扱い完全対応 |
stop | 互換レイヤーでの扱い空白以外の停止シーケンスは動作する |
temperature | 互換レイヤーでの扱い0〜1(両端を含む)。1を超える値は1に丸められる |
n | 互換レイヤーでの扱いちょうど1にする必要がある |
tools[].function.strict | 互換レイヤーでの扱い無視される |
response_format | 互換レイヤーでの扱い無視される。JSON出力にはネイティブAPIのStructured Outputsを使う |
presence_penalty / frequency_penalty / seed / logit_bias / service_tier | 互換レイヤーでの扱い無視される |
logprobs / top_logprobs / metadata / prediction / store / user / modalities / reasoning_effort / audio | 互換レイヤーでの扱い無視される |
n の欄は「ちょうど1」とだけ書かれており、2以上を渡したときの挙動は対応表にありません。複数候補を取っているコードは、移行前に1度流して挙動を見ます。
logprobs や user のように、監視やトレーシング用途で送っているだけのフィールドも黙って無視されます。外部の監視基盤と連携している実装は、そのフィールドに依存した処理が実際に届いているかを個別に点検します。
tools[].function の name・description・parameters は完全対応で、既存のツール定義がそのまま使えます。差が出るのは strict だけです。tool ロールと function ロールのメッセージでは、tool_choice も完全対応として載っています。OpenAIが非推奨にした functions フィールドも同じ扱いで、strict は無視されます。
メッセージロールとレスポンスの違い
messages 配列の各ロールは、フィールド単位で対応が分かれます。
| ロール | 主要フィールドの扱い |
|---|---|
system / developer | 主要フィールドの扱いcontent は対応するが会話冒頭へ移される。name は無視 |
user | 主要フィールドの扱いテキストと画像URL(image_url)は対応。detail・音声入力(input_audio)・ファイル添付は無視 |
assistant | 主要フィールドの扱いテキスト・tool_calls・function_call は対応。refusal 型のcontentと audio フィールドは無視 |
tool / function | 主要フィールドの扱いcontent は対応。name は無視 |
レスポンス側は大半が完全対応ですが、choices[] は常に1要素です。choices[].message.refusal・choices[].message.audio・logprobs・service_tier・system_fingerprint は常に空になります。usage.completion_tokens・usage.prompt_tokens・usage.total_tokens は完全対応です。内訳の usage.completion_tokens_details と usage.prompt_tokens_details は常に空です。
エラーはOpenAI形式を保ちますが、文言までは同じではありません。ユーザー向け表示には使わず、ログとデバッグに限ります。Claude APIのエラー形式とSDK例外クラスの対応はClaude APIのエラー形式とSDK例外クラスの言語別対応表にあります。
ヘッダーでは、x-ratelimit-*・retry-after・request-id・authorization が完全対応です。openai-version は常に 2020-10-01 で、openai-processing-ms は常に空です。独自にリトライ処理を書いている場合や、問い合わせ用に request-id を残す運用では、この差を踏まえます。request-id の取り方はClaude APIのrequest-idをサポート問い合わせで使う方法で扱っています。
systemメッセージはどう1本にまとまるか
OpenAI SDKでは、systemやdeveloperのメッセージを会話の途中に何度でも挟めます。Claude APIが受け付けるのは、会話冒頭の単一のシステムメッセージだけです。
互換レイヤーは、すべてのsystem / developerメッセージを改行(\n)1つでつなぎ、会話の先頭に差し込みます。
OpenAI SDKで送った形とClaude側の形
送る側
system →「回答は日本語で」、user →「要約して」、developer →「箇条書きで」という順で送ります。
Claude側
システムメッセージは「回答は日本語で\n箇条書きで」の1本になり、会話の先頭に置かれます。user の発話はその後ろです。
途中に置いた指示が、元の位置で効くわけではなくなります。プロンプトの意味が出現順に依存している実装では、結合後の順序で意味が変わらないかを確かめます。
thinkingはSDKごとに渡し方が違う
thinkingは thinking パラメータを加えれば有効にできます。現行モデルではthinkingがアダプティブで、深さはClaudeが判断します。Claude 5系では既定でオンで、手動の拡張思考はレガシー方式です。
互換レイヤー経由でも、OpenAI SDKはClaudeの思考過程を返しません。要約された思考(summarized thinking)を含む、thinkingの全機能が必要な実装は、ネイティブのClaude APIに戻ります。思考の深さを制御する reasoning_effort も、前掲の表のとおり無視されます。OpenAIの推論モデル向けにこのパラメータで調整していたコードは、Claude側では効果が出ません。
OpenAI SDKには thinking を表す引数が無いため、SDKごとに追加ボディの渡し方が変わります。
| SDK | 渡し方 |
|---|---|
| Python | 渡し方extra_body={"thinking": {...}} |
| TypeScript | 渡し方呼び出しに thinking を直接書き、// @ts-expect-error で型エラーを抑える |
| Go | 渡し方option.WithJSONSet("thinking", ...) |
| Java | 渡し方putAdditionalBodyProperty("thinking", ...) |
| Ruby | 渡し方request_options: {extra_body: {thinking: ...}} |
| C# | 渡し方extra_body に相当する引数が無いため、生のJSONボディを BinaryContent で送る |
次の例は、公式ドキュメントにあるレガシー方式(type: enabled と budget_tokens の手動指定)のPython版です。
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}},
)移行前に流しておく検証コード
互換性の対応表は更新で変わり得ます。本番投入の前に、手元のキーで次の観点を実際に流して差を見ておくと安全です。以下は公式の対応表に載っている挙動を検査する書き方の一例です。
resp = client.chat.completions.create(
model="claude-opus-5-5",
messages=[
{"role": "system", "content": "回答は日本語で"},
{"role": "user", "content": "自己紹介して"},
{"role": "developer", "content": "箇条書きで"},
],
)
# choices[] は常に1要素
assert len(resp.choices) == 1
# 内訳フィールドは常に空(None か空オブジェクト)
assert not resp.usage.prompt_tokens_details
# 合計は使える
print(resp.usage.total_tokens)
# system / developer の結合が意図どおり効いたか
print(resp.choices[0].message.content)最後の出力で、結合後の指示(日本語で、箇条書き)がどちらも反映されているかを読みます。strict 付きのツール定義を使っている場合は、既存のテストケースをそのまま流してパース失敗が出ないかも見ます。ツール呼び出しと画像入力は、パラメータ単位で対応と無視が細かく分かれているので、レスポンスの差分を見るのが最短です。
評価では向き先を環境変数で切り替える
互換レイヤーの使い道は比較です。コード側にURL・キー・モデル名を直書きせず、環境変数から読む形にしておくと、同じ評価コードをOpenAIとClaudeの両方へ向けられます。
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ.get("LLM_BASE_URL"), # 未設定ならOpenAI既定
)
MODEL = os.environ["LLM_MODEL"]Claudeに向けるときは、LLM_BASE_URL に https://api.anthropic.com/v1/、LLM_API_KEY にClaude APIキーを入れます。LLM_MODEL にはClaudeのモデル名を入れます。評価の差分がモデル由来なのか、無視されたパラメータ由来なのかを切り分けやすくなります。
無視されるフィールドを事前にコードから洗い出す
無視されるフィールドは実行時に警告されません。移行前に、リクエストを組み立てているコードを検索して、対応表の「無視される」側のパラメータを使っている箇所を一覧にしておきます。
grep -rnE "response_format|strict|logprobs|seed|presence_penalty|frequency_penalty|logit_bias|reasoning_effort|service_tier" src/ヒットした箇所ごとに、「結果がその値に依存しているか」を見ます。seed で再現性を取っているテストや、response_format でJSONを前提にしているパーサーは、互換レイヤーに向けた瞬間に前提が崩れます。依存していないものは、そのまま残しても動作は変わりません。
互換レイヤーが向く場面・向かない場面
| 用途 | 向き不向き | 理由 |
|---|---|---|
| モデル性能の評価・A/Bテスト | 向き不向き◎ | 理由変更が3点で済み、既存のOpenAI向け評価パイプラインを流用できる |
| 音声入力を使うプロダクト | 向き不向き× | 理由音声を扱う部分は互換レイヤーの外に出す必要がある |
| スキーマ厳密なツール呼び出しが必須の本番API | 向き不向き△ | 理由strict が無視されるため、パース失敗を前提にした防御的な実装が別途要る |
| 高頻度のバッチ処理でコストを抑えたい | 向き不向き× | 理由同じ長いプロンプトを繰り返す構成では、キャッシュ前提の設計をネイティブAPI側で組む必要がある |
| 社内PoC・技術検証の初期段階 | 向き不向き◎ | 理由本番要件を固める前に、最小の変更でClaudeの応答品質を試せる |
×や△が付く要件があるなら、評価もネイティブのClaude API経由で行ったほうが、後の移行し直しを避けられます。互換性の実装漏れや想定外の挙動は、公式が用意するフィードバックフォームで報告できます。送るときは、差分が出たリクエストと応答を添えると状況が伝わります。
まとめ
互換レイヤーは、書き換えを最小にしてClaudeの出力を比べる入口です。
strict・プロンプトキャッシュ・thinkingの出力のように無視または制限される項目に本番が依存するなら、ネイティブのClaude APIで評価し直します。構造化出力を安定して取りたい場合は、Agent SDKの構造化出力も選択肢です。