spicyapiドキュメント
本文

テキストとストリーミング

OpenAI、Anthropic、Google Gemini の公式フォーマットでテキストモデルを呼び出し、会話、ツール、ストリーム、料金を正しく扱います。

利用可能なテキストモデルを選ぶ

認証付きの /api/v1/models?modality=text&includeSchema=1 からモデルを取得し、enabled と available がどちらも true のモデルにだけ送信します。ツール、マルチモーダルメッセージ、推論フィールド、構造化出力は、そのモデルの inputSchema も対応している場合に限り使えます。プロトコルに互換性があっても、すべてのモデルがすべての機能を備えているわけではありません。

同じモデル ID は、下記のどのプロトコルでも呼び出せます。モデルの開発元は問いません。Anthropic 形式で他社のモデルを呼び出すことも、Gemini 形式で Google 以外のモデルを呼び出すこともできます。

対応プロトコル

4 つのテキストプロトコルは、同じ API キー、同じモデルカタログ、同じレート制限・パラメータ検証・課金を共有しています。違うのはリクエストと応答の形式だけです。既存のコードや SDK ですでに使っている形式を選んでください。

プロトコルエンドポイントキーを送るヘッダー
OpenAI Chat CompletionsPOST /v1/chat/completionsAuthorization: Bearer
OpenAI ResponsesPOST /v1/responsesAuthorization: Bearer
Anthropic MessagesPOST /v1/messagesx-api-key または Authorization: Bearer
Google GeminiPOST /v1beta/models/{model}:generateContentx-goog-api-key または Authorization: Bearer
Google Gemini ストリーミングPOST /v1beta/models/{model}:streamGenerateContentx-goog-api-key または Authorization: Bearer
ネイティブストリームPOST /api/v1/jobs/streamAuthorization: Bearer

/v1 と /v1beta の応答とエラーはそれぞれのプロトコルの形式に従い、ネイティブ API の {code,msg,data,request_id} 外枠は付きません。body.data だけを読むクライアントで解析しないでください。Gemini 形式ではモデル ID を URL のパスに含めます。ID 内のスラッシュはそのままでよく、エンコードは不要です。

キーはリクエストヘッダーにだけ入れます。x-api-keyx-goog-api-key が有効なのは /v1 と /v1beta だけで、ネイティブの /api/v1 は Authorization: Bearer しか受け付けません。URL のクエリパラメータ ?key= は一切受け付けません。キーが URL に入ると、ブラウザーの履歴やプロキシサーバー、アクセスログに残ってしまうからです。公式 SDK はどれもリクエストヘッダーでキーを送るため、影響はありません。

公式 SDK で接続する

変えるのはベース URL とキーだけです。それ以外は各 SDK の通常の使い方どおりで構いません。

SDKベース URL
OpenAI(openaihttps://api.spicyapi.ai/v1
Anthropic(anthropichttps://api.spicyapi.ai
Google GenAI(google-genaihttps://api.spicyapi.ai

Anthropic と Google の SDK は /v1 や /v1beta を自動で付け足すため、ベース URL にバージョンのパスは含めません。OpenAI SDK のベース URL には /v1 が必要です。以下の 3 つの Python の例は、いずれも SPICY_API_KEY と、最新カタログから選んだ SPICY_MODEL を環境変数から読み込みます。

OpenAI
import os
from openai import OpenAI

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": "Explain a rainbow in one sentence."}],
)
print(reply.choices[0].message.content)
Anthropic
import os
import anthropic

client = anthropic.Anthropic(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai")
message = client.messages.create(
    model=os.environ["SPICY_MODEL"],
    max_tokens=256,
    messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(message.content[0].text)
Google GenAI
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["SPICY_API_KEY"],
    http_options=types.HttpOptions(base_url="https://api.spicyapi.ai"),
)
response = client.models.generate_content(
    model=os.environ["SPICY_MODEL"],
    contents="Explain a rainbow in one sentence.",
)
print(response.text)

ネットワークの再試行による二重課金を防ぐには、ユーザー操作ごとに Idempotency-Key を 1 つ生成して永続的に保存し、SDK のカスタムヘッダー用オプションで送ってください(OpenAI と Anthropic の SDK では extra_headers)。

プロトコルごとのパラメータ対応

プロトコル側のフィールドは、まずプラットフォーム共通のパラメータ名に変換され、そのうえで選択したモデルの inputSchema で検証されます。モデルが対応していないパラメータには 400 を返します。黙って無視したまま通常どおり課金することはありません。下の表は対応関係だけを示すもので、「—」はそのプロトコルに対応するフィールドがないことを表します。モデルがどのパラメータを受け付け、どの範囲の値を取れるかは、そのモデルの inputSchema に従います。

共通パラメータChat CompletionsResponsesMessagesGemini
messagesmessagesinstructionsinputsystemmessagessystemInstructioncontents
max_tokensmax_tokensmax_completion_tokensmax_output_tokensmax_tokensgenerationConfig.maxOutputTokens
temperaturetemperaturetemperaturetemperaturegenerationConfig.temperature
top_ptop_ptop_ptop_pgenerationConfig.topP
toolstoolstoolsfunction 型)tools(カスタムツール)tools[].functionDeclarations
tool_choicetool_choicetool_choicetool_choicetoolConfig.functionCallingConfig
response_formatresponse_formattext.formatgenerationConfig.responseMimeType と、responseSchema または responseJsonSchema
reasoning_effortreasoning_effortreasoning.effortgenerationConfig.thinkingConfig

表にないフィールドも同じように共通パラメータへ変換してからモデルで検証され、inputSchema にないものは 400 になります。たとえばモデルが stop を受け付けない場合、Chat の stop、Messages の stop_sequences、Gemini の generationConfig.stopSequences はいずれも 400 を返します。現時点ではどのテキストモデルの inputSchema にも seed はないため、Chat の seed と Gemini の generationConfig.seed も 400 になります。

例外は次のとおりです。

  • 各プロトコル本来のプラットフォームでしか意味を持たないフィールドは無視され、生成には影響しません。Chat の usermetadatastore、Responses の usermetadatastoreinclude、Messages の metadata が該当します。
  • Responses は previous_response_id に対応していません。input に全履歴を含めてください。
  • Gemini の safetySettings は受け付けますが、効果はありません。cachedContent と、googleSearchcodeExecution などサーバー側で実行されるツールには対応しておらず、400 を返します。画像は inlineData(base64)または fileData.fileUri(https の URL)で渡せます。モデルが画像を受け付けるかどうかは inputSchema に従います。

Chat のストリームを送る

サーバー側で SPICY_API_KEY、最新カタログから取得した SPICY_MODEL、今回の操作用に保存した SPICY_IDEMPOTENCY_KEY を設定します。例には jq が必要です。プロンプトは変更できますが、入力項目は送信前に Schema と照合してください。

jq -n --arg model "$SPICY_MODEL" '{
  model: $model,
  messages: [{role: "user", content: "Explain a rainbow in one sentence."}],
  stream: true
}' > chat-request.json

curl --fail-with-body --no-buffer --max-time 120 \
  https://api.spicyapi.ai/v1/chat/completions \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  --data-binary @chat-request.json

Gemini のリクエストを送る

環境変数は前の節と同じです。モデル ID は URL に書くので、リクエストボディには model を含めません。URL はダブルクォートで囲み、${SPICY_MODEL} の形で書いてください。そうしないと、シェルが直後のコロンを変数の修飾子として解釈してしまいます。

jq -n '{
  systemInstruction: {parts: [{text: "Answer in one sentence."}]},
  contents: [{role: "user", parts: [{text: "Explain a rainbow."}]}],
  generationConfig: {temperature: 0.7, maxOutputTokens: 256}
}' > gemini-request.json

curl --fail-with-body --no-buffer --max-time 120 \
  "https://api.spicyapi.ai/v1beta/models/${SPICY_MODEL}:streamGenerateContent?alt=sse" \
  -H "x-goog-api-key: $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  --data-binary @gemini-request.json

ストリーミングが不要な場合は :streamGenerateContent?alt=sse:generateContent に置き換え、回答を candidates[0].content.parts[].text から読み取ります。使用量は usageMetadata にあります。ストリーミングのリクエストに alt=sse を付けないと、応答は少しずつ書き出される 1 つの JSON 配列になります。公式 SDK は alt=sse を自動で付けます。

会話とツール呼び出し

Chat の非ストリーミング応答では choices[0].message を読みます。ストリーミングでは choices[].delta をチャンクごとに読みます。TCP の分割単位で JSON を解析しないでください。ツールの引数は複数のチャンクに分かれて届くことがあるため、index ごとに組み立てて tool_calls が完成してから検証・実行します。assistant メッセージは残したまま、対応する tool_call_id を付けて結果を返します。Responses は input に全履歴を含めて送り、previous_response_id には対応していません。関数呼び出しの結果は、対応する call_id を付けた function_call_output で返します。Messages は content 内の text、tool_use、tool_result ブロックを使うため、Chat のメッセージ形式をそのまま当てはめることはできません。

Gemini の contents は、user と model の 2 つのロールが交互に並んで構成されます。モデルはツールを呼び出すとき functionCall パートを返します。実行後は結果を functionResponse パートにまとめ、次の user コンテンツに入れて返します。name は呼び出し時と同じ値にしてください。

終了・エラー・切断

プロトコル正常終了使用量
Chat Completionsdata: [DONE]最後のチャンクの usage
Responsesresponse.completed イベントイベント内の response.usage
Messagesmessage_delta の後の message_stopmessage_deltausage
GeminifinishReason を含む最後のイベント([DONE] は送られません)usageMetadata

ストリームが始まる前に HTTP ステータスと Content-Type を確認してください。エラーは各プロトコル固有の JSON で返ります。

プロトコルエラーボディ
Chat Completions、Responses{"error": {"message", "type", "param", "code"}}
Messages{"type": "error", "error": {"type", "message"}}
Gemini{"error": {"code", "message", "status"}}

HTTP ステータスコードは 4 つのプロトコルで共通です。Gemini の status には Google のステータス名が入ります。400 は INVALID_ARGUMENT、401 は UNAUTHENTICATED、402 は FAILED_PRECONDITION、403 は PERMISSION_DENIED、404 は NOT_FOUND、429 は RESOURCE_EXHAUSTED、503 は UNAVAILABLE です。プログラムで判定するときはステータスコードとエラーの種類を使い、message の文字列を照合しないでください。

ストリーム開始後にエラーが起きると、Chat と Gemini は error オブジェクトを含むイベントを 1 つ送り、Responses は error イベントを、Messages は event: error を送ります。クライアントはこれらのエラーイベントに加え、タイムアウトや途中での切断も処理する必要があります。プロキシでは応答のバッファリングを無効にします。ブラウザーを閉じたり AbortSignal で中断したりしても受信が止まるだけで、キャンセルや返金の約束にはなりません。サーバー側に信頼できる使用量がある場合、クライアントから切断しても、実際に発生した使用量で精算されます。使用量が得られない場合も、接続が閉じたことを無料での完了として扱うことはできません。

見積もりと精算

正確な見積額と上限をユーザーに先に確認してもらうには、ネイティブの jobs/quote → jobs/stream を使い、同じ model/input に quoteId と expectedCost を付けます。/v1 と /v1beta はこのネイティブの見積もり情報を受け付けず、受理時の料金で実行します。最終費用はタスクの記録と請求履歴で確認してください。テキストの終了マーカーは精算の根拠になりません。reasoning_tokens は completion_tokens に含まれるため、二重に加算しないでください。

ほかのリクエスト形式

以下はプロトコルの外枠だけを示す例です。MODEL_ID_FROM_CATALOG は、これらのフィールドに対応する最新カタログのモデル ID に置き換えてください。プレースホルダーのまま送信しないでください。

POST /v1/responses
{
  "model": "MODEL_ID_FROM_CATALOG",
  "input": "Explain a rainbow in one sentence.",
  "stream": false
}
POST /v1/messages
{
  "model": "MODEL_ID_FROM_CATALOG",
  "max_tokens": 256,
  "messages": [{"role": "user", "content": "Explain a rainbow in one sentence."}],
  "stream": false
}

関連ドキュメント

見積もりとプロトコル互換 · 課金 · 本番環境への組み込み · トラブルシューティングと復旧

このページの内容