Immagina un servizio che invia note di riparazione a un modello DeepSeek e si aspetta in risposta un oggetto JSON: cosa è stato ispezionato, cosa è stato consigliato, se il cliente ha approvato, se il lavoro è stato fatto. Oggi chiama deepseek-chat, deepseek-flash o una route OpenRouter, e tu vuoi farlo girare su DeepSeek V4.1 Flash tramite SpicyAPI (spicyapi.ai).
Bastano quattro modifiche: usare l'ID del modello deepseek/v4.1-flash/chat, eliminare ogni campo che questo modello non accetta, scegliere un reasoning_effort e dare a max_tokens un margine reale, perché il ragionamento si paga con lo stesso budget. Poi esegui un piccolo caso di test prima del traffico reale. Usa la pagina del modello V4.1 Flash per provare la richiesta.
Cosa cambiare nella richiesta
URL di base, chiave e ID del modello sono la parte facile (guida alla migrazione). È il corpo della richiesta che si rompe:
| Nel tuo client attuale | Su SpicyAPI |
|---|---|
URL di base https://api.deepseek.com e una chiave DeepSeek | https://api.spicyapi.ai/v1 e una chiave che inizia con sk-spicy- |
Modello deepseek-flash, o i vecchi deepseek-chat, deepseek-reasoner, deepseek-v4-flash, deepseek-v4-pro | deepseek/v4.1-flash/chat |
thinking: {"type": "disabled"}, il comportamento che dava deepseek-chat | reasoning_effort: "none" salta il passaggio di ragionamento |
| Thinking attivo (il valore predefinito) | Ometti reasoning_effort per high, oppure imposta low per il lavoro in volume |
reasoning_effort con max, medium, minimal, xhigh | Sono accettati solo none, low e high; qualsiasi altro valore restituisce 400 invalid_request |
temperature, top_p, presence_penalty, frequency_penalty | Eliminali; 400 unsupported_parameter |
parallel_tool_calls | Eliminalo; non esposto su questo modello |
stop | Tienilo: fino a 4 sequenze, e il testo corrispondente non compare nella risposta |
Parti image_url o file | Tieni le parti image_url per le immagini; rimuovi le parti PDF/file ed estrai prima il loro testo |
OpenRouter models, route, provider, usage, transforms, plugins e un oggetto reasoning | Elimina i primi sei; sostituisci reasoning con reasoning_effort di primo livello |
Questo modello ospitato non ha alcun controllo di campionamento, nemmeno con reasoning_effort: "none". Convalida i risultati dei compiti invece di affidarti a temperature: 0 per la ripetibilità.
Cosa resta uguale: messages (contenuto testuale o immagine, fino a 256), tools (fino a 128 definizioni) con tool_choice (auto, none, required o una funzione indicata per nome), response_format con {"type": "json_object"}, stop, max_tokens (predefinito 32.768, massimo 384.000) e una finestra di input di 1.048.576 token che conta ogni messaggio inviato, compresi il prompt di sistema e i risultati degli strumenti precedenti.
SpicyAPI non scarta mai un campo in silenzio: il 400 indica il campo e non viene addebitato nulla. L'API di DeepSeek accetta e ignora temperature e i campi di penalità in modalità thinking, quindi un vecchio client può portarseli dietro senza che nessuno se ne accorga.
L'errore silenzioso è il nome del modello. Sull'API di DeepSeek, V4 Flash è stato ritirato e deepseek-v4-flash reindirizza temporaneamente a V4.1 Flash; il nome attuale è deepseek-flash. V4 Pro è diverso: DeepSeek aveva annunciato inizialmente il passaggio a Flash per il 14 settembre, poi il suo registro delle modifiche ha confermato che il servizio V4 Pro sarebbe proseguito con la fatturazione invariata. Non migrare dando per scontato che ogni vecchia chiamata Pro giri già su Flash.
Su SpicyAPI le tre route chat di DeepSeek hanno contratti pubblicati distinti: mantenere deepseek/v4-flash/chat nella configurazione è una richiesta valida, con un tetto di output di 65.536 token e prezzi più alti. Un alias del servizio nativo non cambia quel contratto ospitato. Per questa migrazione usa esplicitamente deepseek/v4.1-flash/chat.
Questa route accetta immagini tramite parti image_url, ma non parti PDF/file. Estrai il testo di un PDF prima di inviarlo; gli esempi sotto, con sole note di riparazione in testo, non richiedono input di immagini.
Lascia spazio alla risposta
Il modello ragiona prima di rispondere, e il ragionamento viene prelevato da max_tokens. L'effort predefinito è high, e un tetto di 120 token non ha restituito alcun contenuto. Il max_tokens predefinito è 32.768, e un risultato più lungo viene troncato. Dimensiona il tetto sulla risposta visibile più alcune migliaia di token di spazio per pensare, non sul massimo di 384.000.
Inizia l'estrazione con effort Low. Se perde una distinzione necessaria, prova High su quel record invece di rendere più costosa ogni riga. Per input grandi usa l'interfaccia di streaming.
Prova prima nel Playground
- Apri DeepSeek V4.1 Flash, scegli Playground e imposta il controllo dell'effort su Low.
- Inserisci l'istruzione sulla forma del JSON dallo script qui sotto in System prompt e la nota di riparazione R17 in Message to the model, poi fai clic su Run model.
- Verifica che un'approvazione in sospeso non diventi una riparazione completata prima di migrare la tua app.
Esegui un caso di test che intercetta l'errore costoso
Una nota di riparazione è un buon caso di test perché un riassunto scorrevole può essere sbagliato in un modo che costa denaro. «Consigliata la sostituzione» non è approvazione del cliente, e l'approvazione non è lavoro completato.
Metti una chiave da API Keys in SPICY_API_KEY, salva questo script come check_repair.py ed esegui python3 check_repair.py. Il prompt di sistema mostra la forma del JSON e contiene la parola «json», richiesta dalla guida alla modalità JSON di DeepSeek.
import json
import os
import uuid
import urllib.request
import re
SOURCE = (
'Repair note R17: "Inspected the rear brake pads. Recommend replacement before '
'the next long ride. Customer approval is pending." No later work record is supplied.'
)
KEYS = {"inspected", "recommended_action", "authorized", "completed", "evidence"}
REQUIRED_QUOTES = (
"Inspected the rear brake pads.",
"Recommend replacement before the next long ride.",
"Customer approval is pending.",
)
def check(response: dict) -> list[str]:
choice = response["choices"][0]
problems = []
if choice.get("finish_reason") != "stop":
problems.append(f"finish_reason is {choice.get('finish_reason')!r}: the reply may be cut off")
content = (choice.get("message") or {}).get("content")
if not content or not content.strip():
return problems + ["empty content: reasoning may have used the whole max_tokens budget"]
try:
data = json.loads(content)
except json.JSONDecodeError as error:
return problems + [f"not valid JSON: {error}"]
if not isinstance(data, dict):
return problems + ["JSON must be an object"]
if missing := KEYS - data.keys():
problems.append(f"missing keys: {sorted(missing)}")
for key in ("inspected", "authorized", "completed"):
if data.get(key) is not None and type(data.get(key)) is not bool:
problems.append(f"{key} must be true, false or null")
if data.get("inspected") is not True:
problems.append("inspected should be true")
if data.get("authorized") is not False:
problems.append("authorized must be false: approval is pending")
if data.get("completed") is not None:
problems.append("completed must be null: no completion record is supplied")
action = data.get("recommended_action")
if not isinstance(action, str) or not action.strip():
problems.append("recommended_action must be a non-empty string")
else:
words = set(re.findall(r"[a-z]+", action.lower()))
# A lexical screen, not a proof of semantic equivalence.
required = {"rear", "long", "ride"}
compatible = (required <= words
and bool(words & {"replace", "replacement", "replacing"})
and bool(words & {"pad", "pads"})
and not words & {"not", "no", "never", "front", "after"})
if not compatible:
problems.append("review recommendation wording against R17")
quotes = data.get("evidence")
if not isinstance(quotes, list) or not quotes:
problems.append("evidence must be a non-empty list of quotes")
else:
problems += [f"invalid or unmatched quote: {q!r}" for q in quotes
if not isinstance(q, str) or not q.strip() or q not in SOURCE]
for expected in REQUIRED_QUOTES:
if not any(isinstance(q, str) and expected in q for q in quotes):
problems.append(f"missing supporting quote: {expected}")
return problems
body = {'model': 'deepseek/v4.1-flash/chat',
'messages': [{'role': 'system',
'content': 'Extract only facts stated in the source. Reply with one '
'JSON object shaped like {"inspected": null, '
'"recommended_action": null, "authorized": null, '
'"completed": null, "evidence": []}. inspected, '
'authorized and completed are true, false or null; use '
'null for anything the source does not state. evidence '
'lists exact quotes from the source that support every '
'non-null value.'},
{'role': 'user',
'content': 'Repair note R17: "Inspected the rear brake pads. '
'Recommend replacement before the next long ride. '
'Customer approval is pending." No later work record is '
'supplied.'}],
'max_tokens': 2048,
'reasoning_effort': 'low',
'response_format': {'type': 'json_object'}}
request = urllib.request.Request(
"https://api.spicyapi.ai/v1/chat/completions",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + os.environ["SPICY_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4())},
)
with urllib.request.urlopen(request, timeout=90) as reply:
response = json.load(reply)
problems = check(response)
print(json.dumps(response.get("usage", {})))
if problems:
raise RuntimeError("; ".join(problems))
print(response["choices"][0]["message"]["content"])La decisione attesa è: ispezionato, approvazione ancora in sospeso, completamento sconosciuto. La raccomandazione deve mantenere le pastiglie posteriori e la scadenza «prima del prossimo viaggio lungo».
Aggiungi approvazione e completamento come casi di test separati
Non fermarti al caso dell'approvazione in sospeso. Usa questi come modelli per altri casi di test: aggiorna SOURCE, REQUIRED_QUOTES e i valori attesi di approvazione/completamento prima di eseguire ciascuno. Il controllo di R17 non è eseguibile direttamente su R18 o R19:
| Ultimo record | Authorized | Completed |
|---|---|---|
| R17: “Customer approval is pending.” Nessun record successivo | false | null |
| R18: “Customer approved the quoted replacement. Parts ordered; work scheduled for Thursday.” | true | null |
| R19: “Replaced pads; road check completed Thursday.” Collegato all'intervento approvato | true | true |
Conserva insieme alla fonte l'ID dell'intervento, l'ordine dei record e le date: R18 sostituisce l'approvazione in sospeso, ma la programmazione del lavoro non stabilisce il completamento. Se una nota «fatto» senza data non può essere collegata allo stesso intervento, restituisci il collegamento irrisolto invece di fonderla con R17.
La stessa divisione in tre appare nel flusso di produzione di un creator: «Approva questo design del personaggio», «design approvato» e «video generato dal design approvato» sono una richiesta, una decisione e un'esecuzione. Chiedi al prompt di estrazione queste fasi separatamente, con il record che sostiene ciascuna, e conserva ogni conflitto tra note invece di chiedere al modello di scegliere la frase che suona più sicura.
Tieni separati approvazione e completamento nei flussi con strumenti
Un record di approvazione non prova che la riparazione sia avvenuta: cerca il record di completamento prima di cambiare lo stato, e tieni approvazione e completamento come campi separati in ciò che restituiscono i tuoi strumenti.
La chat senza strumenti non richiede nulla di extra. Con tools nella richiesta, un client che ricostruisce ogni messaggio dell'assistente con solo ruolo e contenuto supera il primo turno e fallisce al secondo con un 400, quindi un test a turno singolo nasconde il bug. Aggiungi il messaggio dell'assistente esattamente come restituito, compreso reasoning_content, poi un messaggio tool per ogni chiamata con il tool_call_id corrispondente, e limita il numero di round. Testa due turni, non uno. Le ricette per la chat mostrano il ciclo completo.
Lascia che un Agent prepari la chiamata
Per l'estrazione ripetuta di record, chiedi al tuo Agent di programmazione di usare lo Skill SpicyAPI: “Read deepseek/v4.1-flash/chat, then adapt the example to my input; show the request before running it.” La consultazione con la CLI è spicyapi models get deepseek/v4.1-flash/chat --json; con MCP è spicyapi_model_get con model: "deepseek/v4.1-flash/chat". Le chiamate chat usano un client compatibile o l'API chat; i comandi per le attività multimediali non generano risposte di chat.
Ricalcola il conto
Le tariffe sono 0,30 $ per milione di token di input non in cache, 0,006 $ per milione di input in cache e 1,20 $ per milione di output, una tariffa unica a qualsiasi lunghezza di richiesta. Un prompt da 40.000 token non in cache con 2.000 token di output costa 0,0144 $. Se 30.000 dei token di input sono fatturati come in cache e 10.000 come non in cache, la stessa chiamata costa 0,00558 $. I token di ragionamento si fatturano come output, quindi una breve risposta visibile può usare più output di quanto ti aspetti; none è l'unica impostazione che li tiene fuori dal conto.
Due confronti contano quando lasci l'API di DeepSeek. I suoi prezzi di listino sono gli stessi 0,30 $, 0,006 $ e 1,20 $ nelle ore di punta, ma fuori punta lì costano la metà, mentre qui c'è una tariffa unica: un batch che pianifichi fuori punta su DeepSeek costa circa il doppio su SpicyAPI per gli stessi token. E la tariffa in cache è un cinquantesimo di quella di input, ma solo per un prefisso identico byte per byte da una chiamata all'altra: un prompt di sistema da 1.500 token su 10.000 chiamate fa 15 milioni di token di input, 4,50 $ non in cache e al massimo 0,09 $ se ognuno fosse una lettura da cache. Confronta lo usage restituito e il costo liquidato con queste stime. Un timestamp o istruzioni riordinate possono rompere un prefisso condiviso; un input invariato da solo non garantisce un cache hit.
Dove approda ogni route DeepSeek
| V4.1 Flash | V4 Flash | V4 Pro | |
|---|---|---|---|
| ID del modello | deepseek/v4.1-flash/chat | deepseek/v4-flash/chat | deepseek/v4-pro/chat |
| Output max | 384.000 | 65.536 | 384.000 |
| Input / cache / output per 1 M | 0,30 $ / 0,006 $ / 1,20 $ | 0,44 $ / 0,014 $ / 1,32 $ | 1,32 $ / 0,044 $ / 3,96 $ |
parallel_tool_calls | Non esposto | Non esposto | Esposto |
Tutte e tre accettano 1.048.576 token di input, effort none, low o high (predefinito high) e un max_tokens predefinito di 32.768. Se parti da deepseek/v4-flash/chat, il corpo della richiesta è identico, quindi cambia solo l'ID: il tetto di output cresce di quasi sei volte e ogni tariffa scende. Se parti da V4 Pro, perdi parallel_tool_calls e paghi il 77% in meno per l'input e circa il 70% in meno per l'output. Se per un dato compito sia meglio V4 Flash o V4 Pro è un'altra decisione, trattata in DeepSeek V4 Flash vs Pro.
Se la tua app scrive narrativa per adulti
V4.1 Flash è indicato come senza censura su SpicyAPI, ma questo non è una promessa che completi ogni richiesta di narrativa per adulti. Nei nostri test di settembre 2026, le richieste dirette di scene mature intense sono tornate attenuate ogni volta, mentre inquadrarle come narrativa o impostare la cornice nel prompt di sistema ne ha reso di più per intero, anche se non tutte (dettagli nella recensione di V4.1 Flash). Se i tuoi prompt erano scritti per un modello che accettava richieste dirette, sposta l'inquadramento nel messaggio di sistema durante la migrazione e mantieni adulto ogni personaggio.
Correggi direttamente i fallimenti della migrazione
| Sintomo | Causa probabile | Correzione |
|---|---|---|
400 unsupported_parameter che indica temperature, top_p, una penalità, parallel_tool_calls o thinking | Campo portato dal vecchio client | Eliminalo |
content è vuoto o finish_reason è length | max_tokens consumato dal ragionamento, o la risposta lo ha superato | Alza max_tokens, abbassa l'effort, riprova |
| Il JSON viene analizzato ma riporta lavoro completato | Raccomandazione letta come completamento | Mantieni campi separati, richiedi citazioni, esegui il controllo |
| Errore al secondo turno di una conversazione con strumenti | Messaggio dell'assistente ricostruito senza reasoning_content | Aggiungi il messaggio restituito senza modifiche |
| 402 con saldo sufficiente | Il blocco (hold) 40201 supera il saldo, oppure è stato raggiunto il limite di spesa 40202 | Ispeziona l'errore: riduci un budget di output eccessivo per il blocco, oppure controlla i limiti di chiave/workspace |
Idempotency-Key riutilizzata rifiutata (400 o 409) | La chiave appartiene a una richiesta completata o a un corpo diverso | Un 400 per una richiesta completata non può riprodurre la risposta; un 409 può indicare un corpo cambiato. Riusa la chiave solo per un nuovo tentativo di rete identico entro la sua finestra; controlla la liquidazione dell'attività prima di scegliere una nuova chiamata a pagamento |
| Una chiamata lunga senza streaming cade intorno ai 100 secondi | Interruzione al bordo della rete | Trasmetti la risposta in streaming |
Passa al nuovo modello senza perdere la vecchia base di confronto
Tieni l'ID del vecchio modello nella configurazione ed esegui il caso di test su entrambi. Sposta il traffico quando il test passa con il nuovo ID, le tue fonti si convalidano e lo usage registrato corrisponde ai calcoli sopra.








