Imagina un servicio que envía notas de reparación a un modelo de DeepSeek y espera un objeto JSON a cambio: qué se inspeccionó, qué se recomendó, si el cliente aprobó y si el trabajo se hizo. Hoy llama a deepseek-chat, deepseek-flash o una ruta de OpenRouter, y quieres que funcione con DeepSeek V4.1 Flash a través de SpicyAPI (spicyapi.ai).
Cuatro cambios bastan: usa el ID de modelo deepseek/v4.1-flash/chat, borra todos los campos que este modelo no acepta, elige un reasoning_effort y da a max_tokens un margen real, porque el razonamiento se paga con ese mismo presupuesto. Después, ejecuta un caso de prueba pequeño antes del tráfico real. Usa la página del modelo V4.1 Flash para probar la petición.
Qué cambiar en la petición
La URL base, la clave y el ID del modelo son lo fácil (guía de migración). Lo que falla es el cuerpo de la petición:
| En tu cliente actual | En SpicyAPI |
|---|---|
URL base https://api.deepseek.com y una clave de DeepSeek | https://api.spicyapi.ai/v1 y una clave que empieza por sk-spicy- |
Modelo deepseek-flash, o los antiguos deepseek-chat, deepseek-reasoner, deepseek-v4-flash, deepseek-v4-pro | deepseek/v4.1-flash/chat |
thinking: {"type": "disabled"}, el comportamiento que daba deepseek-chat | reasoning_effort: "none" omite el paso de razonamiento |
| Thinking activado (el valor por defecto) | Omite reasoning_effort para high, o usa low para trabajo de volumen |
reasoning_effort con max, medium, minimal, xhigh | Solo se aceptan none, low y high; cualquier otro valor devuelve 400 invalid_request |
temperature, top_p, presence_penalty, frequency_penalty | Bórralos; 400 unsupported_parameter |
parallel_tool_calls | Bórralo; no está disponible en este modelo |
stop | Consérvalo: hasta 4 secuencias, y el texto coincidente no aparece en la respuesta |
Partes image_url o de archivo | Conserva las partes image_url para imágenes; quita las partes de PDF/archivo y extrae antes su texto |
models, route, provider, usage, transforms, plugins de OpenRouter y un objeto reasoning | Borra los seis primeros; sustituye reasoning por reasoning_effort de nivel superior |
Este modelo alojado no tiene control de muestreo, ni siquiera con reasoning_effort: "none". Valida los resultados de la tarea en lugar de depender de temperature: 0 para la repetibilidad.
Lo que se mantiene: messages (contenido de texto o imagen, hasta 256), tools (hasta 128 definiciones) con tool_choice (auto, none, required o una función concreta), response_format con {"type": "json_object"}, stop, max_tokens (por defecto 32.768, máximo 384.000) y una ventana de entrada de 1.048.576 tokens que cuenta todos los mensajes que envías, incluido el prompt de sistema y los resultados de herramientas anteriores.
SpicyAPI nunca descarta un campo en silencio: el 400 nombra el campo y no se cobra nada. La API propia de DeepSeek acepta e ignora temperature y los campos de penalización en modo thinking, así que un cliente antiguo puede arrastrarlos sin que nadie lo note.
El fallo silencioso es el nombre del modelo. En la API propia de DeepSeek, V4 Flash está retirado y deepseek-v4-flash redirige temporalmente a V4.1 Flash; el nombre actual es deepseek-flash. V4 Pro es distinto: DeepSeek anunció primero un cambio a Flash el 14 de septiembre, pero su registro de cambios confirmó después que el servicio de V4 Pro continuaría con la facturación sin cambios. No migres suponiendo que todas las llamadas antiguas a Pro ya se ejecutan en Flash.
En SpicyAPI, las tres rutas de chat de DeepSeek tienen contratos publicados distintos: mantener deepseek/v4-flash/chat en tu configuración es una petición válida con un tope de salida de 65.536 tokens y precios más altos. Un alias del servicio nativo no cambia ese contrato alojado. Usa deepseek/v4.1-flash/chat de forma explícita para esta migración.
Esta ruta acepta imágenes mediante partes image_url, pero no partes de PDF/archivo. Extrae el texto de un PDF antes de enviarlo; los ejemplos de notas de reparación solo de texto que siguen no necesitan entrada de imagen.
Deja espacio para la respuesta
El modelo razona antes de responder, y ese razonamiento sale de max_tokens. El esfuerzo por defecto es high, y un tope de 120 tokens no devolvió ningún contenido. El max_tokens por defecto es 32.768, y un resultado más largo se trunca. Dimensiona el tope según la respuesta visible más varios miles de tokens de margen para pensar, no según el máximo de 384.000.
Empieza la extracción con esfuerzo Low. Si pierde una distinción necesaria, prueba High en ese registro en lugar de encarecer todas las filas. Para entradas grandes, usa la interfaz de streaming.
Pruébalo primero en Playground
- Abre DeepSeek V4.1 Flash, elige Playground y pon el control de esfuerzo en Low.
- Pon la instrucción de forma JSON del script de abajo en System prompt y la nota de reparación R17 en Message to the model, y haz clic en Run model.
- Confirma que una aprobación pendiente no se convierte en una reparación completada antes de migrar tu aplicación.
Ejecuta un caso de prueba que detecte el error caro
Una nota de reparación es un buen caso de prueba porque un resumen fluido puede ser erróneo de una forma que cuesta dinero. «Recomendar sustitución» no es aprobación del cliente, y la aprobación no es trabajo completado.
Pon una clave de API Keys en SPICY_API_KEY, guarda este script como check_repair.py y ejecuta python3 check_repair.py. El prompt de sistema muestra la forma del JSON y contiene la palabra «json», que pide la guía del modo JSON de 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 decisión esperada es: inspeccionado, aprobación aún pendiente y finalización desconocida. La recomendación debe conservar las pastillas traseras y el plazo de antes del próximo viaje largo.
Añade la aprobación y la finalización como casos separados
No te quedes en el caso de aprobación pendiente. Usa estos como plantillas para más casos: actualiza SOURCE, REQUIRED_QUOTES y los valores esperados de aprobación/finalización antes de ejecutar cada uno. El verificador de R17 no se puede ejecutar tal cual contra R18 o R19:
| Último registro | Authorized | Completed |
|---|---|---|
| R17: “Customer approval is pending.” Sin registro posterior | false | null |
| R18: “Customer approved the quoted replacement. Parts ordered; work scheduled for Thursday.” | true | null |
| R19: “Replaced pads; road check completed Thursday.” Vinculado al trabajo aprobado | true | true |
Conserva junto a la fuente el ID del trabajo, el orden de los registros y las fechas: R18 sustituye a la aprobación pendiente, pero programar el trabajo no demuestra que esté completado. Si una nota de «hecho» sin fecha no se puede vincular al mismo trabajo, devuelve el vínculo sin resolver en lugar de fusionarla con R17.
La misma división en tres partes aparece en el flujo de producción de un creador: «Aprobar este diseño de personaje», «diseño aprobado» y «vídeo generado a partir del diseño aprobado» son una solicitud, una decisión y una ejecución. Pide al prompt de extracción esas etapas por separado, con el registro que respalda cada una, y conserva cualquier conflicto entre notas en lugar de pedirle al modelo que elija la frase que suene más segura.
Mantén separadas la aprobación y la finalización en flujos con herramientas
Un registro de aprobación no prueba que la reparación se hiciera: busca el registro de finalización antes de cambiar el estado, y mantén la aprobación y la finalización como campos separados en lo que devuelvan tus herramientas.
El chat sin herramientas no necesita nada extra. Con tools en la petición, un cliente que reconstruye cada mensaje del asistente solo con rol y contenido supera el primer turno y falla en el segundo con un 400, así que una prueba de un solo turno oculta el error. Añade el mensaje del asistente exactamente como se devolvió, incluido reasoning_content, luego añade un mensaje tool por llamada con el tool_call_id correspondiente, y limita el número de rondas. Prueba dos turnos, no uno. Las recetas de chat muestran el bucle completo.
Deja que un Agent prepare la llamada
Para la extracción repetida de registros, pide a tu Agent de programación que use el Skill de SpicyAPI: “Read deepseek/v4.1-flash/chat, then adapt the example to my input; show the request before running it.” La consulta con la CLI es spicyapi models get deepseek/v4.1-flash/chat --json; con MCP es spicyapi_model_get con model: "deepseek/v4.1-flash/chat". Las llamadas de chat usan un cliente compatible o la API de chat; los comandos de tareas multimedia no generan respuestas de chat.
Recalcula la factura
Las tarifas son 0,30 $ por millón de tokens de entrada sin caché, 0,006 $ por millón de entrada en caché y 1,20 $ por millón de salida, una tarifa única con cualquier longitud de petición. Un prompt de 40.000 tokens sin caché con 2.000 tokens de salida cuesta 0,0144 $. Si 30.000 de los tokens de entrada se facturan como en caché y 10.000 como sin caché, la misma llamada cuesta 0,00558 $. Los tokens de razonamiento se facturan como salida, así que una respuesta visible corta puede usar más salida de la que esperas; none es el único ajuste que los deja fuera de la factura.
Al dejar la API propia de DeepSeek importan dos comparaciones. Sus precios de lista son los mismos 0,30, 0,006 y 1,20 $ en horas punta, pero en horario valle cuestan la mitad allí y aquí hay una tarifa única: un lote que programes en horario valle en DeepSeek cuesta aproximadamente el doble en SpicyAPI por los mismos tokens. Y la tarifa en caché es una quincuagésima parte de la de entrada, pero solo para un prefijo idéntico byte a byte de una llamada a otra: un prompt de sistema de 1.500 tokens en 10.000 llamadas son 15 millones de tokens de entrada, 4,50 $ sin caché y como máximo 0,09 $ si todos fueran lecturas de caché. Compara el usage devuelto y el coste liquidado con estas estimaciones. Una marca de tiempo o unas instrucciones reordenadas pueden romper un prefijo compartido; una entrada sin cambios no garantiza por sí sola un acierto de caché.
Dónde aterriza cada ruta de DeepSeek
| V4.1 Flash | V4 Flash | V4 Pro | |
|---|---|---|---|
| ID del modelo | deepseek/v4.1-flash/chat | deepseek/v4-flash/chat | deepseek/v4-pro/chat |
| Salida máxima | 384.000 | 65.536 | 384.000 |
| Entrada / en caché / salida por 1 M | 0,30 $ / 0,006 $ / 1,20 $ | 0,44 $ / 0,014 $ / 1,32 $ | 1,32 $ / 0,044 $ / 3,96 $ |
parallel_tool_calls | No disponible | No disponible | Disponible |
Las tres aceptan 1.048.576 tokens de entrada, esfuerzo none, low o high (por defecto high) y un max_tokens por defecto de 32.768. Si vienes de deepseek/v4-flash/chat, el cuerpo de la petición es idéntico, así que solo cambia el ID: el tope de salida crece casi seis veces y todas las tarifas bajan. Si vienes de V4 Pro, pierdes parallel_tool_calls y pagas un 77 % menos por la entrada y alrededor de un 70 % menos por la salida. Si V4 Flash o V4 Pro encaja mejor en una tarea concreta es otra decisión, tratada en DeepSeek V4 Flash frente a Pro.
Si tu aplicación escribe ficción para adultos
V4.1 Flash figura como sin censura en SpicyAPI, pero eso no es una promesa de que complete cualquier petición de ficción para adultos. En nuestras pruebas de septiembre de 2026, las peticiones directas de escenas maduras intensas volvieron suavizadas todas las veces, mientras que plantearlas como ficción o fijar el marco en el prompt de sistema produjo más de ellas completas, aunque no todas (detalles en la reseña de V4.1 Flash). Si tus prompts se escribieron para un modelo que aceptaba peticiones directas, traslada el marco al mensaje de sistema durante la migración y mantén adultos a todos los personajes.
Corrige directamente los fallos de migración
| Síntoma | Causa probable | Corrección |
|---|---|---|
400 unsupported_parameter que nombra temperature, top_p, una penalización, parallel_tool_calls o thinking | Campo heredado del cliente antiguo | Bórralo |
content está vacío o finish_reason es length | max_tokens gastado en razonamiento, o la respuesta lo superó | Sube max_tokens, baja el esfuerzo y reintenta |
| El JSON se analiza pero informa de trabajo completado | La recomendación se leyó como finalización | Mantén campos separados, exige citas y ejecuta el verificador |
| Error en el segundo turno de una conversación con herramientas | Mensaje del asistente reconstruido sin reasoning_content | Añade el mensaje devuelto sin cambios |
| 402 con saldo suficiente | La retención 40201 supera el saldo, o se alcanzó el límite de gasto 40202 | Inspecciona el error: reduce un presupuesto de salida excesivo para la retención, o revisa los límites de la clave/espacio de trabajo |
Idempotency-Key reutilizada rechazada (400 o 409) | La clave pertenece a una petición completada o a un cuerpo distinto | Un 400 por una petición completada no puede reproducir la respuesta; un 409 puede indicar un cuerpo cambiado. Reutiliza la clave solo para un reintento de red idéntico dentro de su ventana; revisa la liquidación de la tarea antes de elegir una nueva llamada de pago |
| Una llamada larga sin streaming se cae cerca de los 100 segundos | Corte en el borde de la red | Usa streaming en la respuesta |
Cambia el tráfico sin perder la referencia anterior
Mantén el ID del modelo antiguo en la configuración y ejecuta el caso de prueba en ambos. Pasa el tráfico cuando el caso se supere con el nuevo ID, tus propias fuentes se validen y el usage registrado coincida con la aritmética de arriba.








