spicyapiDocumentazione
Testo e chat
Contenuto principale

Migrare da OpenAI o OpenRouter

Collega il codice OpenAI esistente a SpicyAPI: URL di base, chiave, ID del modello, differenze nei campi e impostazioni OpenRouter da rimuovere.

Se il codice chiama già un modello di chat tramite le librerie ufficiali OpenAI o uno strumento con un'impostazione «compatibile con OpenAI», per passare a SpicyAPI servono tre modifiche: URL di base, chiave API e ID del modello. Questa pagina elenca le modifiche, poi tutti i comportamenti diversi, così la migrazione non si conclude con un 400 inatteso. Per programmi di esempio completi, consulta Esempi pratici di chat.

Le tre modifiche

ImpostazioneValore su SpicyAPI
URL di basehttps://api.spicyapi.ai/v1
Chiave APIUna chiave SpicyAPI (inizia con sk-spicy-), inviata come Authorization: Bearer
ModelloUn ID esatto di modello di testo dal catalogo aggiornato, per esempio in SPICY_MODEL (consulta ID dei modelli)
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)

Le librerie OpenAI leggono anche OPENAI_BASE_URL e OPENAI_API_KEY dall'ambiente. Impostarle entrambe permette al codice esistente di funzionare senza modifiche, ma reindirizza anche ogni altra chiamata OpenAI nello stesso processo: preferisci quindi passare esplicitamente i valori, come sopra.

ID dei modelli

Gli ID dei modelli di testo hanno la forma publisher/model/chat. Copiali dal catalogo aggiornato invece di convertire un nome che conosci da un altro servizio:

  • GET /api/v1/models?modality=text elenca soltanto modelli di testo con i rispettivi prezzi e aggiunge l'inputSchema di ciascuno con includeSchema=1. Consulta Catalogo dei modelli e schemi.
  • GET /v1/models è l'elenco nel formato OpenAI letto dagli strumenti. Senza un filtro include modelli di immagini, video e audio, che non possono essere chiamati su /v1/chat/completions; aggiungi ?modality=text per avere soltanto modelli di testo. Oltre ai quattro campi OpenAI, ogni voce ha modality e task; i modelli di chat hanno anche context_length, max_output_tokens e supported_parameters (i campi di richiesta accettati dal modello). Le librerie OpenAI ignorano i campi aggiuntivi.

Un ID di modello inesistente restituisce 404 con code: "model_not_found" e param: "model"; il messaggio mostra la struttura dell'ID e, se ne esiste uno, il modello di chat utilizzabile più simile. Chiamare un modello di immagini o video su un endpoint di chat restituisce 400, indicando che non è un modello di chat. Gli ID di altri servizi spesso sembrano simili ma non sono intercambiabili: i nomi dei creatori sono scritti diversamente, qui non esistono suffissi come :free e i modelli di testo terminano con /chat.

Campi della richiesta

Ogni modello pubblica i campi accettati nel proprio inputSchema (e i loro nomi in supported_parameters su /v1/models).

  • I campi di campionamento non elencati dal modello vengono ignorati. temperature, top_p, top_k, min_p, top_a, frequency_penalty, presence_penalty e repetition_penalty cambiano soltanto la casualità o la ripetitività della risposta e molti client li inviano per impostazione predefinita: un modello che non li accetta genera semplicemente senza usarli. Lo stesso vale per n: 1, uno stop vuoto, stop su un modello senza sequenze di arresto e reasoning_effort su un modello senza livelli di ragionamento. La risposta indica ciò che è stato ignorato nell'header X-Spicy-Ignored-Params, per esempio frequency_penalty, presence_penalty.
  • I valori non accettati dal modello vengono portati al valore accettato più vicino. Un valore di campionamento fuori intervallo viene limitato al confine dell'intervallo; una lista stop più lunga di quella consentita conserva i primi elementi; un livello reasoning_effort non offerto viene sostituito con quello disponibile più vicino (quello inferiore in caso di parità). La risposta elenca ogni modifica come name=value nell'header X-Spicy-Adjusted-Params, per esempio reasoning_effort=low, temperature=1.
  • Ogni altro campo non elencato dal modello viene rifiutato con 400, code: "unsupported_parameter", param impostato sul campo e un messaggio che lo nomina, per esempio logprobs: is not a supported field. Questi campi cambiano ciò che ricevi, quindi non paghi mai una risposta generata senza un campo che credevi di aver inviato.
CampoSu SpicyAPI
messagesStessa struttura. Ruoli system, developer, user, assistant e tool. Il testo funziona ovunque; le parti image_url soltanto sui modelli il cui schema le elenca
max_tokens, max_completion_tokensEntrambi accettati, stesso significato. I token di ragionamento vengono conteggiati. Ogni modello ha il proprio valore predefinito e limite massimo
stream, stream_optionsSupportati, con le regole OpenAI sull'utilizzo: uno stream contiene l'utilizzo soltanto quando include_usage è true (consulta Risposte e stream)
tools, tool_choiceSupportati su ogni modello di testo. Alcuni accettano soltanto auto e none per tool_choice, quindi forzare uno strumento per nome restituisce 400 su questi modelli
parallel_tool_callsSoltanto sui modelli che lo elencano
reasoning_effortSulla maggior parte dei modelli, ma i valori consentiti variano per modello: alcuni aggiungono none, minimal, xhigh o max, altri non hanno medium. Un valore non offerto viene portato a quello disponibile più vicino, quindi leggi l'enum del modello se il livello esatto è importante
temperature, top_p, top_k, frequency_penalty, presence_penaltyUsati sui modelli che li elencano, limitati all'intervallo del modello, ignorati sugli altri (indicati in X-Spicy-Ignored-Params). Vari modelli non accettano alcun campo di campionamento
seedSoltanto sui modelli che lo elencano
stopUsato sui modelli che lo elencano, accorciato al numero massimo previsto; ignorato sui modelli senza sequenze di arresto e quando è vuoto
response_formatjson_object sui modelli che elencano il campo; non disponibile sugli altri
user, metadata, storeAccettati e ignorati
n1 viene ignorato; qualsiasi altro valore viene rifiutato con 400 sui modelli che non lo elencano
logprobs, top_logprobs, logit_bias e ogni altro campo non elencato dal modelloRifiutati con 400

Per vedere campi e valori consentiti di un modello prima di inviare qualcosa, esegui il comando in Esempi pratici di chat: prima di iniziare.

Risposte e stream

Una risposta senza streaming è un normale oggetto chat.completion, quindi reply.choices[0].message.content e reply.usage funzionano come prima. Le differenze sono piccole:

  • reasoning_content. I modelli che ragionano prima di rispondere restituiscono quel testo in message.reasoning_content (oppure delta.reasoning_content in streaming), accanto a content. È già conteggiato in usage.completion_tokens.
  • L'ID è quello della richiesta. id ha la forma chatcmpl-req_... e l'header di risposta x-request-id contiene lo stesso valore req_.... Citalo quando contatti l'assistenza. Un secondo header, X-Spicy-Task-Id, identifica l'attività che conserva l'addebito (consulta Costo e utilizzo).
  • Nessun system_fingerprint né logprobs.

Gli stream usano gli stessi eventi chat.completion.chunk, terminano con data: [DONE] e seguono le regole OpenAI sull'utilizzo:

  • Utilizzo soltanto su richiesta. Senza stream_options.include_usage, nessun frammento contiene usage e ogni frammento ha una lista choices non vuota. Con "include_usage": true, arriva un frammento aggiuntivo con una lista choices vuota e l'usage finale poco prima di [DONE]; ogni altro frammento ha usage: null. Il codice scritto sugli esempi di streaming OpenAI funziona senza modifiche; controllare choices prima di leggere choices[0] lo rende sicuro su quell'ultimo frammento.
  • Un finish_reason per scelta. Ogni scelta termina con esattamente un frammento che contiene il suo finish_reason, qualunque sia il modello chiamato.
  • Righe keep-alive. Mentre un modello sta ancora ragionando, lo stream contiene righe di commento : keep-alive per mantenere aperta la connessione. Le librerie OpenAI le saltano; anche un parser scritto a mano deve farlo.

Per risposte lunghe o reasoning_effort elevato, usa lo streaming. Una richiesta senza streaming che dura oltre circa 30 secondi viene mantenuta aperta con spazi prima del corpo JSON e l'header X-Spicy-Keepalive: 1, così le librerie OpenAI riescono ancora a interpretarla; ma se fallisce dopo quel momento, l'oggetto di errore arriva nel corpo di una risposta 200, che le librerie non segnalano come errore. Consulta Timeout e streaming.

Errori

Gli errori usano il formato OpenAI, {"error": {"message", "type", "param", "code"}}, con i consueti codici di stato HTTP, quindi le librerie OpenAI sollevano le eccezioni abituali. param identifica il campo interessato come percorso con punti, per esempio temperature o messages.1.content.0, e code distingue errori con lo stesso stato: unsupported_parameter (un campo o una parte di messaggio non accettati), invalid_request (campo mancante o valore non accettato), content_rejected (rifiuto per politiche sui contenuti, anch'esso 400) e model_not_found (404). Una differenza rispetto a OpenAI: 402 viene usato quando il saldo o un limite di spesa non copre la richiesta. La tabella completa, con le azioni per ogni codice, è in Esempi pratici di chat: quando qualcosa non funziona.

Le librerie OpenAI riprovano autonomamente le risposte 429 e 5xx. Qui è sicuro: una richiesta fallita non viene addebitata. Se invii un Idempotency-Key, passalo per ogni chiamata e leggi prima Chiavi di idempotenza sugli endpoint di testo.

Costo e utilizzo

Le risposte riportano conteggi di token in usage, non un prezzo. I prezzi per milione di token di input, input in cache e output sono elencati sulla pagina di ogni modello e nel catalogo. Per sapere il costo effettivo di una chiamata, leggi l'header di risposta X-Spicy-Task-Id (job_…), presente su ogni richiesta accettata, con o senza streaming, e cerca l'attività con GET /api/v1/jobs/recordInfo?taskId=<job_…>: cost è finale quando settled è true. Le voci di GET /api/v1/jobs contengono anche il requestId della chiamata che le ha create (l'header X-Request-Id), e Console → Log mostra le stesse attività. Come leggere l'header con le librerie OpenAI è mostrato in Esempi pratici di chat: trovare il costo di una chiamata; consulta anche Preventivi e fatturazione.

Prima di eseguire una richiesta, viene bloccato dal saldo un importo che copre i messaggi e l'intero max_tokens di output (il valore predefinito del modello se lo ometti). Paghi soltanto ciò che viene usato, ma il blocco deve prima essere coperto: un saldo piccolo con un grande max_tokens restituisce quindi 402. Imposta un max_tokens realistico oppure limiti di spesa sulla chiave; consulta Fatturazione.

Endpoint che non troverai qui

La generazione di testo è disponibile come OpenAI Chat Completions, OpenAI Responses, Anthropic Messages e Google Gemini; i dettagli sono in Testo e streaming. Questi endpoint OpenAI non esistono e restituiscono 404: /v1/embeddings, il vecchio /v1/completions, /v1/images/*, /v1/audio/*, /v1/moderations, /v1/files, /v1/batches, /v1/assistants e il fine-tuning. I modelli di immagini, video e audio vengono chiamati attraverso l'API delle attività, mentre i video anche attraverso /v1/videos.

Arrivare da OpenRouter

Anche OpenRouter usa il formato OpenAI, quindi valgono le tre modifiche sopra: sostituisci l'URL di base https://openrouter.ai/api/v1 con https://api.spicyapi.ai/v1 e cambia chiave e ID dei modelli. Poi rimuovi i campi presenti soltanto su OpenRouter: qui restituiscono 400.

Campo OpenRouterCosa fare
models (un elenco di fallback) e routeRimuovi. Invia un solo ID di modello; SpicyAPI sceglie come servirlo
providerRimuovi. Non esiste una selezione del fornitore per ogni richiesta
reasoning (un oggetto come {"effort": "high"})Convertito nel reasoning_effort al livello principale (enabled: false diventa none dove il modello lo prevede); le altre chiavi al suo interno vengono ignorate. Inviare direttamente reasoning_effort è più chiaro
usage ({"include": true})Rimuovi. Le risposte senza streaming includono sempre usage; per gli stream invia stream_options: {"include_usage": true}
transforms, pluginsRimuovi

Gli header HTTP-Referer e X-Title sono innocui e ignorati.

Checklist

  1. URL di base https://api.spicyapi.ai/v1 e una chiave SpicyAPI.
  2. ID di modello copiato dal catalogo aggiornato, che termina con /chat.
  3. Campi della richiesta controllati rispetto all'inputSchema del modello; campi non di campionamento non supportati rimossi; header di risposta X-Spicy-Ignored-Params e X-Spicy-Adjusted-Params controllati una volta per vedere cosa sia stato cambiato.
  4. Se invii stream_options.include_usage, i cicli di streaming saltano il frammento finale con la lista choices vuota.
  5. max_tokens impostato su un valore realistico, oppure lasciato al valore predefinito del modello, con limiti di spesa sulla chiave.
  6. Le richieste lunghe o con ragionamento elevato usano stream: true.

Continua a leggere

Esempi pratici di chat · Testo e streaming · Catalogo dei modelli e schemi · Autenticazione · Fatturazione

In questa pagina