spicyapi文件
主要內容

文字模型與串流

用 OpenAI、Anthropic 或 Google Gemini 的官方格式呼叫文字模型,處理好對話、工具、串流與費用。

先選可呼叫的文字模型

從已認證 /api/v1/models?modality=text&includeSchema=1 讀取模型。只有 enabled 與 available 均為 true 才提交;工具、多模態訊息、推理欄位和結構化輸出必須同時被該模型的 inputSchema 支援。協定相容不意味著所有模型都具備所有能力。

同一個模型 ID 可以用下面任何一種協定呼叫,和模型出自哪家無關:用 Anthropic 格式呼叫其他廠商的模型、用 Gemini 格式呼叫非 Google 的模型都可以。

支援的協定

四種文字協定共用同一把 API Key、同一份模型目錄,以及同一套速率限制、參數驗證與計費,差別只在請求與回應的格式。選你現有程式碼或 SDK 已經在用的那一種即可。

協定入口放金鑰的標頭
OpenAI Chat CompletionsPOST /v1/chat/completionsAuthorization: Bearer
OpenAI ResponsesPOST /v1/responsesAuthorization: Bearer
Anthropic MessagesPOST /v1/messagesx-api-keyAuthorization: Bearer
Google GeminiPOST /v1beta/models/{model}:generateContentx-goog-api-keyAuthorization: Bearer
Google Gemini 串流POST /v1beta/models/{model}:streamGenerateContentx-goog-api-keyAuthorization: Bearer
原生串流POST /api/v1/jobs/streamAuthorization: Bearer

/v1 與 /v1beta 的回應和錯誤遵循各自協定,不帶原生的 {code,msg,data,request_id} 信封,不要用只會讀取 body.data 的用戶端解析它們。Gemini 格式把模型 ID 放在網址路徑裡,ID 中的斜線原樣保留,不需要編碼。

金鑰只放在請求標頭。x-api-keyx-goog-api-key 只在 /v1 與 /v1beta 生效,原生 /api/v1 只認 Authorization: Bearer。網址查詢參數裡的 ?key= 一律不接受:金鑰一旦進入網址,就會留在瀏覽器歷史、代理伺服器與存取日誌裡。官方 SDK 都用請求標頭傳金鑰,不受影響。

用官方 SDK 串接

只換 Base URL 和金鑰,其餘照各 SDK 的正常用法。

SDKBase URL
OpenAI(openaihttps://api.spicyapi.ai/v1
Anthropic(anthropichttps://api.spicyapi.ai
Google GenAI(google-genaihttps://api.spicyapi.ai

Anthropic 與 Google 的 SDK 會自己補上 /v1 或 /v1beta,因此 Base URL 不帶版本路徑;OpenAI SDK 的 Base URL 則要帶 /v1。下面三段 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 並持久儲存,用 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.responseMimeTyperesponseSchemaresponseJsonSchema
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 網址)傳入,模型是否收圖以 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 寫在網址裡,請求本文不帶 model;網址用雙引號包起來,並寫成 ${SPICY_MODEL},免得 shell 把後面的冒號當成變數修飾符。

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 時,回應是一個逐步寫出的 JSON 陣列;官方 SDK 會自己帶上 alt=sse

對話與工具呼叫

Chat 的非串流回應讀取 choices[0].message;串流則逐塊讀取 choices[].delta,不是逐 TCP 分片解析 JSON。工具參數可能分多塊回傳,按 index 組裝完整 tool_calls 後再驗證和執行,保留 assistant 訊息並用對應 tool_call_id 回填結果。Responses 在 input 中傳送完整歷史,不支援 previous_response_id;函式呼叫結果用 function_call_output 和對應 call_id。Messages 使用 content 中的 text、tool_use、tool_result 塊,不能直接套用 Chat 的訊息格式。

Gemini 的 contents 由 user 與 model 兩種角色交替組成。模型要呼叫工具時回傳 functionCall 部件;執行後把結果做成 functionResponse 部件,放在下一則 user 內容裡回填,name 與呼叫時一致。

結束、錯誤與斷線

協定正常結束用量
Chat Completionsdata: [DONE]最後一個 chunk 的 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 狀態碼在四種協定間一致。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 物件的事件,Responses 送出 error 事件,Messages 送出 event: error;用戶端必須處理這些錯誤事件、逾時和提前斷開。代理層停用回應緩衝。關閉瀏覽器或 AbortSignal 只停止接收,不等於取消或承諾退款。伺服器端有可信的用量時,用戶端主動斷線仍按實際已產生的用量結算;缺少用量時不能把連線關閉當成免費完成。

報價與帳單

要讓使用者先確認精確報價和上限,可改用原生 jobs/quote → jobs/stream,使用同一 model/input 和 quoteId、expectedCost。/v1 與 /v1beta 不接收這組原生報價憑證,按受理時價格執行。最終費用以任務記錄和帳單為準,文字串流的結束標記不是結算憑證;reasoning_tokens 已包含在 completion_tokens,不能重複相加。

其他請求形狀

以下範例只說明協定信封。把 MODEL_ID_FROM_CATALOG 替換為支援這些欄位的即時模型;不要把預留位置原樣提交。

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
}

進一步閱讀

報價與協定相容 · 計費 · 正式環境串接指南 · 疑難排解與請求恢復

本頁目錄