Kimi K2.7 Code is now on CometAPI — Kimi's most intelligent coding model to date, reliably follows instructions in long contexts and completes programming tasks with a higher success rate. Try it now

Come eseguire il debug delle generazioni dell'API di IA non riuscite

CometAPI
AnnaJun 4, 2026
Come eseguire il debug delle generazioni dell'API di IA non riuscite

I guasti delle API di AI sono diversi dai guasti delle API tradizionali. Una risposta 200 non significa che la tua generazione sia riuscita. Un campo null in content non è sempre un errore. E lo stesso prompt che funzionava ieri potrebbe fallire oggi perché un provider ha aggiornato la sua policy sui contenuti.

Questa guida spiega come leggere gli errori delle API di AI, cosa significa realmente ciascuna modalità di errore e come costruire una gestione degli errori che ti dica cosa si è rotto invece di limitarsi a dire che qualcosa si è rotto.

Nota: I nomi dei modelli come gpt-5.4 e gpt-5.4-mini usati in questo articolo sono identificatori di piattaforma di CometAPI. Funzionano solo tramite https://api.cometapi.com/v1 — non direttamente tramite le API di OpenAI o Anthropic. Vedi l’elenco completo dei modelli.

Perché il debugging delle API di AI è più difficile rispetto al debugging delle API tradizionali

Con una tipica API REST, un 200 significa successo e un 4xx significa che hai sbagliato qualcosa. Le API di AI aggiungono una terza categoria: soft failures — risposte che restituiscono 200 ma non contengono contenuto utilizzabile.

Tre cose possono andare storte:

  1. Guasto hard — errore HTTP (4xx, 5xx). La richiesta non è stata completata.
  2. Guasto soft — HTTP 200, ma finish_reason è content_filter o length, oppure content è null.
  3. Guasto silenzioso — HTTP 200, il contenuto sembra a posto, ma l’output è errato in un modo che puoi cogliere solo a livello applicativo.

La maggior parte della gestione degli errori copre solo il caso 1. I casi 2 e 3 sono dove si annidano la maggior parte dei bug in produzione.

Comprendere il formato della risposta di errore

L’endpoint delle completions testuali restituisce una struttura di errore coerente:

{  "error": {    "message": "human-readable description (often includes request id)",    "type": "comet_api_error",    "param": "the_problematic_parameter_or_null",    "code": "error_code_or_null"  }}

Gli endpoint di immagini e video restituiscono formati di errore differenti — analizza sempre il body grezzo della risposta invece di assumere una struttura fissa tra endpoint.

Il campo message di solito ti dice esattamente cosa non va. Il campo param ti dice quale parametro l’ha causato. Registra sempre entrambi.

Conosci il significato di ciascun codice di stato HTTP

StatoSignificatoCausa comuneCorrezione
400Richiesta non validaModello mancante, parametro sbagliato per questo modelloControlla error.param nella risposta
401Non autorizzatoChiave API errata o mancanteVerifica il formato Authorization: Bearer
429Rate limitedTroppe richiesteBackoff esponenziale (vedi Passo 4)
500Errore del serverProblema lato provider o body della richiesta malformatoRiprova con backoff; controlla il formato della richiesta
504Timeout del gatewayIl provider ha impiegato troppo tempoRiprova; considera un modello più veloce

Fonte**: Documentazione CometAPI chat completions

La distinzione tra 400 e 500 è importante per la logica di retry. Un 400 significa che la tua richiesta è errata — ripetere la stessa richiesta non aiuterà. Un 500 o 504 significa che il server ha avuto un problema — ha senso riprovare.

Controlla finish_reason — il campo più trascurato

Una risposta 200 con finish_reason: "content_filter" significa che la generazione è stata bloccata. Il campo content sarà null o vuoto. Se non lo controlli, la tua app restituirà silenziosamente il nulla.

finish_reasonSignificatoCosa fareCorrezione
stopCompletamento normaleNiente — questo è un successoControlla error.param nella risposta
lengthRaggiunto il limite di tokenAumenta max_tokens o accorcia il promptVerifica il formato Authorization: Bearer
content_filterBloccato dalla policy di sicurezzaRiformula il prompt; evita nomi/argomenti specificiBackoff esponenziale (vedi Passo 4)
tool_callsIl modello ha chiamato uno strumento invece di restituire testoGestisci la chiamata allo strumento; il contenuto sarà nullRiprova con backoff; controlla il formato della richiesta
504Timeout del gatewayIl provider ha impiegato troppo tempoRiprova; considera un modello più veloce

Fonte**: Documentazione CometAPI chat completions

import osimport loggingfrom openai import OpenAI, APIStatusError, APIConnectionError, APITimeoutErrorfrom dotenv import load_dotenv​load_dotenv()​api_key = os.environ.get("COMETAPI_KEY")if not api_key:    raise ValueError("COMETAPI_KEY is not set")​client = OpenAI(    base_url="https://api.cometapi.com/v1",    api_key=api_key,)​def safe_complete(messages: list, model: str = "gpt-5.4-mini", **kwargs) -> dict:    """    Complete a chat request with full error and finish_reason handling.    Returns {"content": str, "finish_reason": str, "tool_calls": list | None}    Raises on API errors.    """    try:        response = client.chat.completions.create(            model=model,            messages=messages,            **kwargs        )    except APIStatusError as e:        error_body = {}        try:            error_body = e.response.json().get("error", {})        except Exception:            pass        logging.error(            f"API error status={e.status_code} "            f"message={error_body.get('message')} "            f"param={error_body.get('param')}"        )        raise    except (APIConnectionError, APITimeoutError) as e:        logging.error(f"Network/timeout error: {e}")        raise​    choice = response.choices[0]    finish_reason = choice.finish_reason​    if finish_reason == "content_filter":        raise ValueError(            f"Generation blocked by content filter. "            f"Model: {model}. Rephrase the prompt."        )​    if finish_reason == "length":        used = response.usage.completion_tokens if response.usage else "unknown"        logging.warning(f"Output truncated at token limit. Used {used} tokens.")​    # Return structured result so callers can handle tool_calls explicitly    return {        "content": choice.message.content or "",        "finish_reason": finish_reason,        "tool_calls": choice.message.tool_calls,    }​# Usageresult = safe_complete(    messages=[{"role": "user", "content": "Summarize this article: [text]"}],    model="gpt-5.4-mini")​if result["finish_reason"] == "tool_calls":    # Handle tool call — content will be empty    print("Model wants to call a tool:", result["tool_calls"])else:    print(result["content"])

Rileva i guasti silenziosi a livello applicativo

I guasti silenziosi sono i più difficili da individuare. L’API restituisce 200, finish_reason è stop, ma l’output è semanticamente errato. Puoi rilevarli solo a livello applicativo.

Pattern comuni:

def validate_completion(result: dict, task: str) -> str:    """    Application-layer validation for silent failures.    Raises ValueError if the output doesn't meet basic expectations.    """    content = result["content"].strip()​    # Empty output that isn't a tool call    if not content and result["finish_reason"] != "tool_calls":        raise ValueError(f"Empty output for task '{task}' with finish_reason='{result['finish_reason']}'")​    # Task-specific checks    if task == "classify":        valid_labels = {"positive", "negative", "neutral"}        if content.lower() not in valid_labels:            logging.warning(                f"Unexpected classification output: '{content}'. "                f"Expected one of {valid_labels}. "                f"Model may have returned explanation instead of label."            )​    if task == "json_extract":        import json        try:            json.loads(content)        except json.JSONDecodeError:            raise ValueError(                f"Expected JSON output but got: '{content[:100]}...'. "                f"Try adding 'respond with valid JSON only' to the prompt, "                f"or use response_format={{\"type\": \"json_object\"}}."            )​    if task == "summarize" and len(content.split()) < 10:        logging.warning(            f"Suspiciously short summary ({len(content.split())} words). "                f"Check if the input was too short or the model misunderstood the task."        )​    return content​​# Full flow with silent failure detectionresult = safe_complete(    messages=[{"role": "user", "content": "Classify as positive/negative/neutral: 'Great product!'"}],    model="claude-haiku-4-5")label = validate_completion(result, task="classify")

I guasti silenziosi solitamente derivano da una delle tre cause: il prompt è ambiguo, il modello ha ignorato le istruzioni di formattazione oppure l’input era troppo corto/lungo per il compito. Registrare l’output completo quando la validazione fallisce è il modo più rapido per diagnosticare quale delle tre sia.

Aggiungi backoff esponenziale per i rate limit

Gli errori di rate limit (429) sono temporanei. La risposta corretta è attendere e riprovare con ritardi crescenti — una pratica standard per qualsiasi API con rate limit:

import timeimport randomfrom openai import RateLimitError​def complete_with_retry(    messages: list,    model: str = "gpt-5.4-mini",    max_retries: int = 3,    **kwargs) -> dict:    """Retry on rate limits and server errors with exponential backoff."""    last_error = None​    for attempt in range(max_retries):        try:            return safe_complete(messages, model=model, **kwargs)​        except APIStatusError as e:            if e.status_code < 500:                raise  # 4xx: don't retry, request is wrong            last_error = e​        except RateLimitError as e:            last_error = e​        except (APIConnectionError, APITimeoutError) as e:            last_error = e​        if attempt < max_retries - 1:            wait = (2 ** attempt) + random.random()  # jitter prevents thundering herd            logging.warning(f"Attempt {attempt + 1} failed. Waiting {wait:.1f}s before retry.")            time.sleep(wait)​    raise RuntimeError(f"All {max_retries} attempts failed") from last_error

Non riprovare su 400 o 401 — sono errori del client che non si risolvono da soli.

Debug degli errori di generazione di immagini

La generazione di immagini ha modalità di errore proprie oltre ai consueti errori HTTP:

import base64import requests​def generate_image_safe(prompt: str, model: str = "dall-e-3") -> dict:    """    Generate an image with full error handling.    Returns {"url": str | None, "bytes": bytes | None, "blocked": bool}    """    api_key = os.environ.get("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY is not set")​    BASE64_MODELS = {"gpt-image-2", "qwen-image"}​    headers = {        "Authorization": f"Bearer {api_key}",        "Content-Type": "application/json"    }​    payload = {"model": model, "prompt": prompt, "size": "1024x1024"}    if model in BASE64_MODELS:        payload["output_format"] = "png"    else:        payload["response_format"] = "url"​    try:        response = requests.post(            "https://api.cometapi.com/v1/images/generations",            json=payload,            headers=headers,            timeout=60        )        response.raise_for_status()    except requests.exceptions.HTTPError as e:        logging.error(f"Image generation HTTP error: {e.response.status_code} {e.response.text}")        raise    except requests.exceptions.Timeout:        logging.error("Image generation timed out after 60s")        raise​    data = response.json().get("data", [])​    if not data:        logging.warning("Image generation returned empty data — prompt may have been filtered.")        return {"url": None, "bytes": None, "blocked": True}​    item = data[0]​    if "revised_prompt" in item:        logging.info(f"Provider revised prompt to: {item['revised_prompt']}")​    if "url" in item:        return {"url": item["url"], "bytes": None, "blocked": False}​    return {        "url": None,        "bytes": base64.b64decode(item["b64_json"]),        "blocked": False    }

Problemi specifici delle immagini da tenere d’occhio:

SintomoCausaCorrezione
Array data vuotoPrompt filtratoControlla revised_prompt; riformula
Errore response_format su GPT Image 2Parametro non supportatoUsa output_format invece
Errore n > 1 su Qwen ImageLimitazione del modelloEffettua richieste in loop
L’URL restituisce 403 in seguitoURL scadutoScarica immediatamente dopo la generazione

Fonte**: Documentazione CometAPI image generation

Debug degli errori di generazione video

La generazione video fallisce in modo diverso perché è asincrona. Inizializza le variabili di stato prima del loop in modo che il messaggio di timeout sia sempre ben formato:

def submit_and_poll_video(    prompt: str,    model: str = "veo3-fast",    max_wait: int = 600) -> str:    """Submit video task and poll to completion. Returns video URL."""    api_key = os.environ.get("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY is not set")​    headers = {"Authorization": f"Bearer {api_key}"}​    try:        response = requests.post(            "https://api.cometapi.com/v1/videos",            headers=headers,            files={                "prompt": (None, prompt),                "model": (None, model),                "size": (None, "16x9")            },            timeout=30        )        response.raise_for_status()    except requests.exceptions.HTTPError as e:        logging.error(f"Video submit failed: {e.response.status_code} {e.response.text}")        raise​    task_id = response.json()["id"]    logging.info(f"Video task submitted: {task_id}")​    poll_url = f"https://api.cometapi.com/v1/videos/{task_id}"    elapsed = 0    interval = 10    status = "unknown"   # initialize before loop    progress = 0         # initialize before loop​    while elapsed < max_wait:        try:            poll_response = requests.get(poll_url, headers=headers, timeout=30)            poll_response.raise_for_status()        except requests.exceptions.HTTPError as e:            logging.error(f"Poll request failed: {e.response.status_code}")            raise​        result = poll_response.json()        status = result.get("status", "unknown")        progress = result.get("progress", 0)​        logging.info(f"Task {task_id}: status={status} progress={progress}%")​        if status == "succeeded":            return result["output"][0]        elif status in ("failed", "cancelled"):            error_detail = result.get("error", "no error detail returned")            raise RuntimeError(f"Video task {task_id} failed: {error_detail}")​        time.sleep(interval)        elapsed += interval​    raise TimeoutError(        f"Video task {task_id} did not complete within {max_wait}s. "        f"Last status: {status}, progress: {progress}%"    )

Problemi specifici dei video:

SintomoCausaCorrezione
Task bloccato in queued per 10+ minCarico del serverRiprova con un modello diverso
failed senza dettagli errorePrompt filtrato o errore del modelloRiformula il prompt
L’URL del video restituisce 403URL scadutoScarica immediatamente
task_not_exist al primo poll di RunwayIl task è ancora in inizializzazione (comportamento documentato da CometAPI)Attendi 5s e riprova
Kling restituisce "succeed" non "succeeded"L’API di Kling usa una stringa di stato non standardGestisci entrambe nella logica di polling

Fonte**: Documentazione CometAPI video generation**, Documentazione Kling Video

Versione Node.js

import OpenAI from 'openai';​const apiKey = process.env.COMETAPI_KEY;if (!apiKey) throw new Error('COMETAPI_KEY is not set');​const client = new OpenAI({  baseURL: 'https://api.cometapi.com/v1',  apiKey,});​async function safeComplete(messages, model = 'gpt-5.4-mini', options = {}) {  let response;​  try {    response = await client.chat.completions.create({ model, messages, ...options });  } catch (err) {    if (err.status && err.status < 500) {      console.error(`Client error ${err.status}: ${err.message}`);    } else {      console.error(`Server/network error: ${err.message}`);    }    throw err;  }​  const choice = response.choices[0];  const finishReason = choice.finish_reason;​  if (finishReason === 'content_filter') {    throw new Error(`Generation blocked by content filter. Model: ${model}`);  }​  if (finishReason === 'length') {    console.warn(`Output truncated. Used ${response.usage?.completion_tokens ?? 'unknown'} tokens.`);  }​  return {    content: choice.message.content ?? '',    finishReason,    toolCalls: choice.message.tool_calls ?? null,  };}​async function completeWithRetry(messages, model = 'gpt-5.4-mini', maxRetries = 3) {  let lastError;​  for (let attempt = 0; attempt < maxRetries; attempt++) {    try {      return await safeComplete(messages, model);    } catch (err) {      // Don't retry 4xx client errors      if (err.status && err.status < 500) throw err;​      lastError = err;      if (attempt < maxRetries - 1) {        const wait = (2 ** attempt + Math.random()) * 1000;        console.warn(`Attempt ${attempt + 1} failed. Retrying in ${(wait / 1000).toFixed(1)}s`);        await new Promise(r => setTimeout(r, wait));      }    }  }​  throw new Error(`All ${maxRetries} attempts failed: ${lastError?.message}`);}​// Usageconst result = await safeComplete(  [{ role: 'user', content: 'Classify as positive/negative/neutral: "Great product!"' }],  'claude-haiku-4-5');​if (result.finishReason === 'tool_calls') {  console.log('Tool call requested:', result.toolCalls);} else {  console.log(result.content);}

Una checklist di debugging

Quando una generazione fallisce e non sai da dove iniziare:

Per la generazione di testo:

  • La chiave API è impostata e nel formato Authorization: Bearer <key>?
  • Il finish_reason è diverso da stop?
  • Il content è null? Verifica se finish_reason è tool_calls
  • L’output è stato troncato? Controlla finish_reason: "length" e usage.completion_tokens
  • L’errore è un 4xx (correggi la richiesta) o 5xx (riprova)?
  • L’output supera la tua validazione a livello applicativo? (guasto silenzioso)

Per la generazione di immagini:

  • L’array data è vuoto? (content filter)
  • Hai usato response_format su GPT Image 2? (non supportato — usa output_format)
  • Hai impostato n > 1 su Qwen Image? (non supportato)
  • Hai scaricato l’immagine prima che l’URL scadesse?

Per la generazione video:

  • Il task è bloccato in queued? (prova un modello diverso)
  • Hai controllato il campo error nella risposta del task fallito?
  • Hai scaricato il video prima che l’URL scadesse?
  • Stai gestendo sia "succeed" (Kling) sia "succeeded" (Veo, Runway)?

Domande frequenti

D: La mia richiesta restituisce 200 ma non c’è contenuto. Cosa è successo?

Controlla finish_reason. Se è content_filter, la generazione è stata bloccata — la richiesta è riuscita ma l’output è stato soppresso. Se è tool_calls, il modello ha chiamato uno strumento invece di restituire testo, e content è null per progettazione. Se finish_reason è stop ma il contenuto è comunque vuoto, è un guasto silenzioso — registra la risposta completa e controlla il prompt.

D: Come faccio a sapere se il mio prompt viene filtrato?

Per il testo: controlla finish_reason === "content_filter". Per le immagini: verifica se l’array data è vuoto. Per i video: controlla se il task arriva a stato failed poco dopo la sottomissione senza dettagli di errore. In tutti i casi, prova a riformulare il prompt in modo più neutro.

D: Quando dovrei riprovare una richiesta fallita?

Riprova con 429 e 5xx usando il backoff esponenziale. Non riprovare con 4xx — una richiesta errata non si corregge da sola. L’eccezione è 401 se stai ruotando le chiavi API.

D: Che cos’è il backoff esponenziale e perché è importante?

Invece di riprovare immediatamente, aspetti sempre più a lungo: 1s, 2s, 4s. Aggiungere jitter casuale (+ random.random()) impedisce che più client riprovino in sincronia. È una pratica standard per qualsiasi API con rate limit — non è specifica di CometAPI.

D: Il task video è bloccato in queued da 10 minuti. È fallito?

Non necessariamente — le code possono accumularsi sotto carico. Attendi fino alla tua soglia max_wait, poi solleva un TimeoutError e riprova con un modello diverso. Registra l’ID del task così puoi controllarne lo stato manualmente se necessario.

D: Come intercetto i guasti silenziosi?

I guasti silenziosi richiedono validazione a livello applicativo — l’API non ti dirà che l’output è semanticamente errato. Controlla che l’output rispetti il formato atteso (JSON valido, etichetta prevista, lunghezza minima). Registra l’output completo quando la validazione fallisce. Le cause più comuni sono prompt ambigui, istruzioni di formato ignorate o input troppo corti o troppo lunghi per il compito.

Pronto a ridurre i costi di sviluppo AI del 20%?

Inizia gratuitamente in pochi minuti. Crediti di prova gratuiti inclusi. Nessuna carta di credito richiesta.

Leggi di più