spicyapiドキュメント
テキストとチャット
本文

OpenAI・OpenRouter からの移行

既存の OpenAI ライブラリのコードを SpicyAPI に接続します。ベース URL、API キー、モデル ID の変更、リクエスト項目の違い、OpenRouter から移行する際に削除する項目を解説します。

公式の OpenAI ライブラリ、または「OpenAI 互換」の設定を持つツールでチャットモデルを呼び出している場合、SpicyAPI への移行で変更するのはベース URL、API キー、モデル ID の 3 点です。このページではその変更と動作の違いを説明し、移行後に予期しない 400 が発生するのを防ぎます。プログラム全体の例は チャットの実装例をご覧ください。

変更する 3 点

設定SpicyAPI での値
ベース URLhttps://api.spicyapi.ai/v1
API キーSpicyAPI のキー(sk-spicy- で始まるもの)を Authorization: Bearer として送信
モデルライブカタログに掲載されているテキストモデル ID を正確に指定。例えば SPICY_MODEL に設定(モデル IDを参照)
import os
from openai import OpenAI

# Before: client = OpenAI()   (reads OPENAI_API_KEY, calls api.openai.com)
client = OpenAI(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai/v1")

reply = client.chat.completions.create(
    model=os.environ["SPICY_MODEL"],
    messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
print(reply.choices[0].message.content)

OpenAI ライブラリは環境変数 OPENAI_BASE_URL と OPENAI_API_KEY も読み取ります。この 2 つを設定すれば既存のコードを変更せずに実行できますが、同じプロセス内のほかの OpenAI 呼び出しもすべて接続先が変わります。そのため、上の例のように値を明示的に渡す方法を推奨します。

モデル ID

テキストモデル ID の形式は publisher/model/chat です。別のサービスで知っている名前から推測せず、ライブカタログからコピーしてください。

  • GET /api/v1/models?modality=text は価格付きのテキストモデル一覧を返します。includeSchema=1 を指定すると各モデルの inputSchema も含まれます。モデルカタログとスキーマを参照してください。
  • GET /v1/models はツールが読み取る OpenAI 形式の一覧です。フィルターを指定しないと、/v1/chat/completions では呼び出せない画像・動画・音声モデルも含まれます。テキストモデルだけを取得するには ?modality=text を追加します。各項目には OpenAI の 4 フィールドに加えて modality と task があり、チャットモデルには context_length、max_output_tokens、supported_parameters(そのモデルが受け付けるリクエストフィールド)も含まれます。OpenAI ライブラリはこれらの追加フィールドを無視します。

存在しないモデル ID では、404 と code: "model_not_found"、param: "model" が返ります。メッセージには ID の形式と、該当するものがあれば呼び出せる最も近いチャットモデルが表示されます。チャットエンドポイントに画像・動画モデルを指定すると、チャットモデルではないことを示す 400 が返ります。他サービスの ID は似ていても互換ではありません。開発元の表記が異なり、:free などの接尾辞はここにはなく、テキストモデルの末尾は /chat です。

リクエストフィールド

各モデルが受け付けるフィールドは inputSchema で公開しています。/v1/models の supported_parameters にもフィールド名が掲載されます。

  • モデルが掲載していないサンプリング項目は無視されます。 temperature、top_p、top_k、min_p、top_a、frequency_penalty、presence_penalty、repetition_penalty は、回答のランダム性や反復傾向だけを変える項目です。多くのクライアントがデフォルトで送信するため、対応していないモデルではこれらを使わずに生成します。n: 1、空の stop、停止シーケンスに対応していないモデルでの stop、推論レベルのないモデルでの reasoning_effort も同様です。無視された項目はレスポンスの X-Spicy-Ignored-Params ヘッダーで通知されます。例:frequency_penalty, presence_penalty。
  • モデルの受付範囲外の値は、最も近い対応値に変更されます。 範囲外のサンプリング値は境界値に収められます。stop リストがモデルの上限より長ければ先頭の項目だけが残ります。モデルに存在しない reasoning_effort は最も近いレベルに変更され、距離が同じなら低い方を選びます。各変更は X-Spicy-Adjusted-Params ヘッダーに name=value の形式で示されます。例:reasoning_effort=low, temperature=1。
  • それ以外の未掲載フィールドは拒否されます。 400 と code: "unsupported_parameter" が返り、param は該当フィールド名になります。メッセージにも項目名が示されます。例:logprobs: is not a supported field。これらは返される内容自体を変える項目なので、送ったつもりの機能が使われていない回答に料金を払うことはありません。
フィールドSpicyAPI での動作
messages形式は同じです。ロールは system、developer、user、assistant、tool。テキスト内容はすべてのモデルで使えます。image_url 部分はスキーマに記載されているモデルだけで使えます
max_tokens, max_completion_tokensどちらも受け付け、意味は同じです。推論トークンもこの数に含まれます。デフォルト値と上限はモデルごとに異なります
stream, stream_options対応しています。使用量の扱いは OpenAI と同じで、ストリームに使用量が含まれるのは include_usage が true の場合だけです(レスポンスとストリームを参照)
tools, tool_choiceすべてのテキストモデルで対応しています。一部のモデルは tool_choice に auto と none しか受け付けないため、そのモデルで名前付きツールを強制すると 400 が返ります
parallel_tool_calls掲載されているモデルのみ
reasoning_effortほとんどのモデルにありますが、許可される値はモデルごとに異なります。none、minimal、xhigh、max があるモデルや、medium がないモデルもあります。存在しない値は最も近い対応値に変更されるため、正確なレベルが重要ならモデルの enum を確認してください
temperature, top_p, top_k, frequency_penalty, presence_penalty掲載されているモデルでは使用し、モデルの範囲内に収めます。それ以外では無視され、X-Spicy-Ignored-Params に項目名が示されます。サンプリング項目を一切受け付けないモデルも複数あります
seed掲載されているモデルのみ
stop掲載されているモデルでは使用し、モデルの最大件数までに切り詰めます。停止シーケンスのないモデルや空の場合は無視されます
response_formatフィールドが掲載されているモデルで json_object を指定できます。それ以外では利用できません
user, metadata, store受け付けますが無視されます
n1 は無視されます。それ以外の値は、項目を掲載していないモデルでは 400 で拒否されます
logprobs, top_logprobs, logit_bias、およびモデルが掲載していないその他のフィールド400 で拒否されます

送信前にモデルのフィールドと許可値を確認するには、チャットの実装例:始める前にのコマンドを実行してください。

レスポンスとストリーム

非ストリーミングの回答は通常の chat.completion オブジェクトなので、reply.choices[0].message.content と reply.usage はこれまでどおり使えます。違いは次のとおりです。

  • reasoning_content。 回答前に推論するモデルでは、そのテキストを message.reasoning_content(ストリーミング時は delta.reasoning_content)として content と並べて返します。この分はすでに usage.completion_tokens に含まれています。
  • ID はリクエスト ID です。 id は chatcmpl-req_... の形式で、レスポンスの x-request-id ヘッダーにも同じ req_... が入ります。サポートへの問い合わせ時にこの値を伝えてください。別のヘッダー X-Spicy-Task-Id は課金を記録するタスクの ID です(費用と使用量を参照)。
  • system_fingerprint と logprobs はありません。

ストリームは同じ chat.completion.chunk イベントを使い、data: [DONE] で終了します。使用量も OpenAI の規則に従います。

  • 使用量は指定した場合だけ返ります。 stream_options.include_usage を指定しない場合、どのチャンクにも usage はなく、すべてのチャンクの choices リストは空ではありません。"include_usage": true を指定すると、[DONE] の直前に、空の choices リストと最終 usage を持つ追加チャンクが 1 つ届きます。それ以外のチャンクは usage: null です。OpenAI のストリーミング例に沿ったコードはそのまま動きます。choices[0] を読む前に choices を確認すれば、最後のチャンクも安全に扱えます。
  • 各選択肢の finish_reason は 1 回だけです。 どのモデルでも、各選択肢は finish_reason を持つチャンクちょうど 1 つで終了します。
  • 接続維持用の行。 モデルが推論している間、接続を保つために : keep-alive のコメント行がストリームに流れます。OpenAI ライブラリはこれを読み飛ばします。独自のパーサーでも読み飛ばす必要があります。

長い回答や高い reasoning_effort ではストリーミングを使ってください。約 30 秒を超える非ストリーミングリクエストでは、JSON 本文の前に空白を送って接続を維持し、X-Spicy-Keepalive: 1 ヘッダーを付けます。OpenAI ライブラリは引き続き JSON を解析できますが、その段階以降に失敗した場合、エラーオブジェクトは 200 レスポンスの本文に届くため、ライブラリは例外を発生させません。タイムアウトとストリーミングを参照してください。

エラー

エラーは OpenAI と同じ {"error": {"message", "type", "param", "code"}} の形と通常の HTTP ステータスで返るため、OpenAI ライブラリは通常どおり例外を発生させます。param はエラーの対象フィールドで、temperature や messages.1.content.0 のようなドット区切りのパスです。code は同じステータスのエラーを区別します。unsupported_parameter はモデルが受け付けないフィールドやメッセージ部分、invalid_request は必須項目の不足や受付範囲外の値、content_rejected はコンテンツポリシーによる拒否(これも 400)、model_not_found は 404 です。OpenAI と異なる点として、残高や支出上限がリクエストに必要な金額をカバーしない場合は 402 を使います。各コードへの対応を含む一覧は チャットの実装例:問題が起きたときにあります。

OpenAI ライブラリは 429 と 5xx を自動で再試行します。失敗したリクエストは課金されないため、ここでも安全です。Idempotency-Key を送信する場合は呼び出しごとに渡し、先に テキストエンドポイントの冪等性キーを読んでください。

費用と使用量

回答の usage が示すのはトークン数で、価格ではありません。入力・キャッシュ入力・出力の 100 万トークンあたりの価格は各モデルページとカタログに掲載しています。実際の呼び出し費用は、ストリーミングかどうかを問わず受理されたリクエストに付く X-Spicy-Task-Id レスポンスヘッダー(job_…)を読み、GET /api/v1/jobs/recordInfo?taskId=<job_…> でタスクを取得して確認します。settled が true になった時点で cost が確定します。GET /api/v1/jobs の各項目にも、作成元の呼び出しの requestId(X-Request-Id ヘッダー)があり、コンソール → ログ にも同じタスクが表示されます。OpenAI ライブラリからヘッダーを読む方法は チャットの実装例:呼び出し費用を調べるにあります。見積もりと課金も参照してください。

実行前に、メッセージと max_tokens 全量の出力をカバーする金額を残高から仮押さえします。省略した場合はモデルのデフォルト値を使います。支払うのは実際に使用した分だけですが、まず仮押さえ分を確保する必要があるため、小さな残高で大きな max_tokens を指定すると 402 になります。現実的な max_tokens を設定するか、キーに支出上限を設定してください。課金を参照してください。

ここにないエンドポイント

テキスト生成は OpenAI Chat Completions、OpenAI Responses、Anthropic Messages、Google Gemini の形式で利用できます。詳細は テキストとストリーミングにあります。次の OpenAI エンドポイントは存在せず、404 を返します:/v1/embeddings、旧 /v1/completions、/v1/images/*、/v1/audio/*、/v1/moderations、/v1/files、/v1/batches、/v1/assistants、ファインチューニング。画像・動画・音声モデルは代わりに タスク APIで呼び出します。動画は /v1/videos からも利用できます。

OpenRouter から移行する場合

OpenRouter も OpenAI 形式を使うため、上の 3 点を変更します。ベース URL https://openrouter.ai/api/v1 を https://api.spicyapi.ai/v1 に置き換え、キーとモデル ID を変更します。その後、OpenRouter にしかないフィールドを削除してください。ここでは 400 になります。

OpenRouter のフィールド対応
models(フォールバックのリスト)と route削除します。モデル ID は 1 つ送信します。処理方法は SpicyAPI が選びます
provider削除します。リクエストごとの接続先選択はありません
reasoning({"effort": "high"} のようなオブジェクト)トップレベルの reasoning_effort に変換します。enabled: false はモデルに none がある場合にその値へ変換し、内部のその他のキーは無視します。reasoning_effort を直接送る方が明確です
usage({"include": true})削除します。非ストリーミング回答には常に usage が含まれます。ストリームには stream_options: {"include_usage": true} を送信します
transforms, plugins削除します

HTTP-Referer と X-Title ヘッダーは問題なく、無視されます。

チェックリスト

  1. ベース URL は https://api.spicyapi.ai/v1、キーは SpicyAPI のキーにします。
  2. ライブカタログからコピーした、末尾が /chat のモデル ID を使います。
  3. モデルの inputSchema とリクエスト項目を照合し、未対応のサンプリング以外の項目を削除します。レスポンスの X-Spicy-Ignored-Params と X-Spicy-Adjusted-Params も一度確認し、変更された内容を把握します。
  4. stream_options.include_usage を送る場合、ストリームのループで choices が空の最終チャンクを読み飛ばします。
  5. max_tokens を現実的な値に設定するか、モデルのデフォルトに任せ、キーに支出上限を設けます。
  6. 長いリクエストや高い推論レベルでは stream: true を使います。

続けて読む

チャットの実装例 · テキストとストリーミング · モデルカタログとスキーマ · 認証 · 課金

このページの内容