この記事でやること
OpenAI や Anthropic の API 向けに書いたコードが手元にあるとき、それを別の基盤に載せ替えるのはどれくらい大変なのか。
さくらのAI Engine は OpenAI/Anthropic 互換をうたっています。ただ「互換」と書かれていても、実際にどこまで動くかは触ってみないと分かりません。tool use は? streaming は? JSON出力は?
そこでこの記事では、検証のルールを1つ決めて確かめました。
base_url と APIキーだけを差し替える。アプリ側のコードは触らない。
この縛りで、実務でよく使う機能を一つずつ叩いています。
あわせて、さくらのAI Engine の課金がトークン量ではなくリクエスト回数である点についても整理しました。ここはトークン課金の常識と設計の勘所が変わるので、無償枠を使い切る前に知っておきたいです。
本記事は「さくらのAI Engine 3,000リクエスト使い切りチャレンジ」への参加記事です。
結論
差し替えだけでどこまで動いたか
| 機能 | 形式 | 結果 | 観測 |
|---|---|---|---|
| 通常応答 | OpenAI | ✅ | finish_reason: stop |
| streaming | OpenAI | ✅ | SSE を終端まで正常に受信 |
| tool use(function calling) | OpenAI | ✅ |
tool_calls 返却 → 結果返しの往復が成立 |
| JSON出力(json_object) | OpenAI | ✅ | マークダウンで包まれない生JSON |
| メッセージ | Anthropic | ✅ |
stop_reason: end_turn、thinking と text が分離 |
全部そのまま動きました。 しかも1つのアカウントトークンで、OpenAI 形式(/v1/chat/completions)と Anthropic 形式(/v1/messages)の両方を、それぞれの作法のまま叩けます。
課金設計で押さえておくこと
| 内容 | |
|---|---|
| 課金単位 | トークン量ではなくリクエスト回数 |
| 無償枠 | chat completions は月3,000リクエスト |
| 超過時 | 自動課金されず、レートリミットで止まる |
回数課金なので、1リクエストにまとめる方が有利です。トークン課金だとコンテキストを削りたくなりますが、ここでは逆になります。
詰まったのは互換性ではなく手元の環境
2箇所で止まりましたが、どちらもさくら側ではなく自分の環境の問題で、回避はいずれも一行でした。詳細は後述します。
準備:トークン発行まで
さくらのクラウド会員でログインし、AI Engine のコントロールパネル から進みます。
- 利用規約に同意 → 基盤モデル無償プランを選択
- 左メニュー
アカウントトークン→アカウントトークンを作成 - 発行された
<UUID>:<シークレット>形式の文字列を保管(再表示されません)
先に共有しておきたい点が1つあります。
トークンの形式が独特です。 UUID:シークレット のペア全体で1つのAPIキーになります。OpenAI の sk-... とは形が違うので、Authorization: Bearer <UUID:シークレット> に丸ごと入れます。
無償プランの枠は公式マニュアル記載で以下のとおりです。
| 種別 | 無償枠(月) |
|---|---|
| チャット補完(chat completions) | 3,000 リクエスト |
| ベクトル埋め込み(embeddings) | 10,000 リクエスト |
| 音声の文字起こし(audio transcriptions) | 50 リクエスト |
| 音声合成(audio speech) | 50 リクエスト |
疎通確認は公式の curl そのままで通ります。モデルはすべて gpt-oss-120b を使いました。
curl 'https://api.ai.sakura.ad.jp/v1/chat/completions' \
-H 'Authorization: Bearer <UUID:シークレット>' \
-H 'Content-Type: application/json' \
-d '{
"model": "gpt-oss-120b",
"messages": [{"role":"user","content":"ping"}],
"max_tokens": 500,
"stream": false
}'
使うエンドポイントは2つです。
- OpenAI 互換:
https://api.ai.sakura.ad.jp/v1/chat/completions - Anthropic 互換:
https://api.ai.sakura.ad.jp/v1/messages
差し替えだけでどこまで載るか
ここからが本題です。冒頭のルールどおり、アプリ側のコードには一切手を入れていません。
tool use(function calling)
gpt-oss 系で function calling が通るかは事前に読めませんでしたが、結果は OpenAI 仕様どおりでした。
1回目のリクエストで tools を渡すと、本文ではなく tool_calls が返り、finish_reason は tool_calls になります。
[m3-1] status=200 finish=tool_calls content=NONE
その tool_calls に対してツールの実行結果(role: "tool")を返して2回目を叩くと、最終応答が返ります。
[m3-2] status=200 finish=stop content=yes
5回試して毎回同じパターンでした。「初回でツール呼び出し → 結果を返して最終応答」という OpenAI エージェントの基本フローがそのまま動きます。
JSON出力(response_format)
response_format: {"type":"json_object"} を付けて叩いた実レスポンスです。
{
"choices": [
{
"message": {
"role": "assistant",
"content": "{\n \"name\": \"太郎\",\n \"age\": 30\n}",
"reasoning": "The user says ... output a JSON object with name and age ..."
},
"finish_reason": "stop"
}
]
}
見どころが2つあります。
content が生JSONで返ります。 マークダウンのコードブロックで包まれないので、そのまま json.loads() できます。「JSONで返して」と指示したときの整形処理が要りません。
reasoning が content に混ざりません。 別フィールド reasoning に分離されているので、OpenAI SDK の message.content をそのままパースするコードが壊れません。
1つのトークンで Anthropic 形式も叩ける
個人的に一番効いたのがここです。同じアカウントトークン・同じモデル名のまま、Anthropic の Messages API 形式(/v1/messages)を叩いてみました。
curl -s 'https://api.ai.sakura.ad.jp/v1/messages' \
-H "Authorization: Bearer $SAKURA_AI_TOKEN" \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
-d '{
"model":"gpt-oss-120b",
"max_tokens":1000,
"messages":[{"role":"user","content":"ping"}]
}'
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": "The user just says \"ping\". ... Respond with \"pong\".",
"signature": "db88f0f2530d465db463c320ad1d9c72"
},
{ "type": "text", "text": "pong" }
],
"model": "gpt-oss-120b",
"stop_reason": "end_turn",
"usage": { "input_tokens": 68, "output_tokens": 37 }
}
Anthropic Messages API の構造そのままです。
つまり OpenAI 形式で書いたコードも、Anthropic 形式で書いたコードも、どちらもエンドポイントを差し替えるだけで載ります。 片方の資産しか無くても移行できますし、両方が混在するプロジェクトでも1つのトークンで足ります。
per-request 課金をどう設計に落とすか
さくらのAI Engine の課金は、トークン量ではなくリクエスト回数で数えます。ここはトークン課金と設計の勘所が変わるので、実務目線で整理しておきます。
1回にどれだけ詰めても1リクエスト
「短いリクエストを何度も投げる」より「1リクエストにまとめる」方が回数効率が良くなります。
トークン課金だとコンテキストを削りたくなりますが、回数課金ではまとめる方が有利です。分類やタグ付けのような小タスクをループで叩いているなら、複数件を1プロンプトにバッチングするだけで消費回数が大きく減ります。
往復が増える機能は回数も増える
tool use は「初回のツール呼び出し」と「結果を返す2回目」で最低2リクエスト、マルチターンはターン数ぶんリクエストが積み上がります。
エージェント的に何度も往復する設計は、それだけ無償枠3,000回を早く消費します。回数を意識するなら、往復数そのものを減らす設計(不要なツールコールを削る、1ターンで済ませる)が良さそう。
利用量カウンタはリアルタイムではない
コントロールパネルの「利用量」で消費リクエスト数を確認できますが、叩いた直後は反映されず、集計にラグがあります(反映途中に部分的な数値が見える瞬間もありました)。
枠の消費をモニタリングしたい場合、コンソールの数字を即時の残量表示として当てにしない方がよさそうです。正確な管理が要るなら、アプリ側で自分のリクエスト数をカウントしておくのが確実です。
まとめ
- OpenAI/Anthropic 向けに書いたコードは、
base_urlと APIキーの差し替えだけで載る。通常応答・streaming・tool use・JSON出力・Anthropic Messages 形式まで、手元では全部動いた - 1つのアカウントトークンで両形式が叩けるので、資産が混在していても移行できる
- 課金はリクエスト回数。1回にまとめる方が有利で、往復の多いエージェント設計は枠の消費が早い
- 無償枠を超えても自動課金されずレートリミットで止まるので、コスト面で事故りにくい
- 詰まりどころは互換性ではなく手元の環境(TLS、
max_tokens)。どちらも回避は一行
「まず動かしてみる」までの距離が短いサービスでした。手元にコード資産がある人ほど、載せ替えの手軽さを実感できると思います。
検証環境
- モデル:
gpt-oss-120b - 各機能パターンを複数回ずつ実行し、レスポンスの再現性を確認
- OpenAI 互換
/v1/chat/completions、Anthropic 互換/v1/messagesの両方を同一トークンで検証