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

Cách gỡ lỗi các lần sinh thất bại của API AI

CometAPI
AnnaJun 4, 2026
Cách gỡ lỗi các lần sinh thất bại của API AI

Các lỗi của AI API khác với lỗi API thông thường. Phản hồi 200 không có nghĩa là lần sinh nội dung của bạn đã thành công. Trường contentnull không phải lúc nào cũng là lỗi. Và cùng một prompt hoạt động hôm qua có thể thất bại hôm nay vì nhà cung cấp cập nhật chính sách nội dung.

Hướng dẫn này trình bày cách đọc lỗi AI API, ý nghĩa thực sự của từng chế độ lỗi, và cách xây dựng xử lý lỗi để cho bạn biết cái gì hỏng thay vì chỉ biết rằng có thứ gì đó hỏng.

Note: Tên model như gpt-5.4gpt-5.4-mini được dùng trong bài viết này là định danh nền tảng của CometAPI. Chúng chỉ hoạt động qua https://api.cometapi.com/v1 — không hoạt động trực tiếp với API của OpenAI hoặc Anthropic. Xem danh sách model đầy đủ.

Tại sao debug AI API khó hơn debug API thông thường

Với một REST API điển hình, mã 200 nghĩa là thành công và 4xx nghĩa là bạn làm sai. AI API thêm một loại thứ ba: các lỗi mềm — phản hồi trả về 200 nhưng không có nội dung sử dụng được.

Có ba điều có thể xảy ra lỗi:

  1. Lỗi cứng — Lỗi HTTP (4xx, 5xx). Yêu cầu không hoàn tất.
  2. Lỗi mềm — HTTP 200, nhưng finish_reasoncontent_filter hoặc length, hoặc contentnull.
  3. Lỗi im lặng — HTTP 200, nội dung trông có vẻ ổn, nhưng đầu ra sai theo cách mà bạn chỉ phát hiện được ở tầng ứng dụng.

Hầu hết xử lý lỗi chỉ bao phủ trường hợp 1. Trường hợp 2 và 3 mới là nơi đa số lỗi sản xuất xuất hiện.

Hiểu định dạng phản hồi lỗi

Endpoint text completions trả về một cấu trúc lỗi nhất quán:

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

Các endpoint hình ảnh và video trả về các định dạng lỗi khác — luôn phân tích phần thân phản hồi thô thay vì giả định một cấu trúc cố định giữa các endpoint.

Trường message thường cho bạn biết chính xác điều gì sai. Trường param cho biết tham số nào gây ra lỗi. Luôn ghi log cả hai.

Biết mỗi mã trạng thái HTTP có ý nghĩa gì

StatusÝ nghĩaNguyên nhân phổ biếnCách khắc phục
400Yêu cầu saiThiếu model, tham số không phù hợp với modelKiểm tra error.param trong phản hồi
401Chưa xác thựcSai hoặc thiếu API keyXác minh định dạng Authorization: Bearer
429Bị giới hạn tần suấtQuá nhiều yêu cầuBackoff theo cấp số nhân (xem Bước 4)
500Lỗi máy chủVấn đề phía nhà cung cấp, hoặc body yêu cầu sai định dạngThử lại với backoff; kiểm tra định dạng yêu cầu
504Gateway timeoutNhà cung cấp mất quá nhiều thời gianThử lại; cân nhắc model nhanh hơn

Nguồn**: Tài liệu chat completions của CometAPI

Sự khác biệt giữa 400500 quan trọng đối với logic retry. 400 nghĩa là yêu cầu của bạn sai — thử lại cùng yêu cầu sẽ không giúp ích. 500 hoặc 504 nghĩa là máy chủ gặp vấn đề — retry là hợp lý.

Kiểm tra finish_reason — trường bị bỏ qua nhiều nhất

Phản hồi 200 với finish_reason: "content_filter" nghĩa là lần sinh nội dung của bạn bị chặn. Trường content sẽ là null hoặc rỗng. Nếu bạn không kiểm tra điều này, ứng dụng của bạn sẽ âm thầm trả về không có gì.

finish_reasonÝ nghĩaCần làm gìCách khắc phục
stopHoàn tất bình thườngKhông cần làm gì — đây là thành côngKiểm tra error.param trong phản hồi
lengthChạm giới hạn tokenTăng max_tokens hoặc rút ngắn promptXác minh định dạng Authorization: Bearer
content_filterBị chặn bởi chính sách an toànDiễn đạt lại prompt; tránh tên/chủ đề cụ thểBackoff theo cấp số nhân (xem Bước 4)
tool_callsModel gọi một công cụ thay vì trả văn bảnXử lý lời gọi công cụ; content sẽ là nullThử lại với backoff; kiểm tra định dạng yêu cầu
504Gateway timeoutNhà cung cấp mất quá nhiều thời gianThử lại; cân nhắc model nhanh hơn

Nguồn**: Tài liệu chat completions của CometAPI

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"])

Phát hiện lỗi im lặng ở tầng ứng dụng

Lỗi im lặng là khó bắt nhất. API trả về 200, finish_reasonstop, nhưng đầu ra sai về mặt ngữ nghĩa. Bạn chỉ có thể bắt chúng ở tầng ứng dụng.

Các mẫu thường gặp:

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")

Các lỗi im lặng thường đến từ một trong ba nguồn: prompt mơ hồ, model bỏ qua hướng dẫn định dạng của bạn, hoặc đầu vào quá ngắn/dài cho tác vụ. Ghi log đầy đủ đầu ra khi xác thực thất bại là cách nhanh nhất để chẩn đoán đâu là nguyên nhân.

Thêm backoff theo cấp số nhân cho lỗi giới hạn tần suất

Lỗi giới hạn tần suất (429) là tạm thời. Cách đúng là chờ và retry với độ trễ tăng dần — một thực hành tiêu chuẩn cho bất kỳ API nào có giới hạn tần suất:

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

Đừng retry với 400 hoặc 401 — đó là lỗi phía client sẽ không tự hết. Ngoại lệ là 401 nếu bạn đang xoay vòng API key.

Debug lỗi sinh ảnh

Sinh ảnh có các chế độ lỗi riêng bên cạnh lỗi HTTP tiêu chuẩn:

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    }

Các vấn đề riêng của ảnh cần lưu ý:

Triệu chứngNguyên nhânCách khắc phục
Mảng data rỗngPrompt bị lọcKiểm tra revised_prompt; diễn đạt lại
Lỗi response_format trên GPT Image 2Tham số không được hỗ trợDùng output_format thay thế
n > 1 lỗi trên Qwen ImageGiới hạn của modelLặp lại yêu cầu thay vì dùng n > 1
URL trả về 403 sau đóURL hết hạnTải xuống ngay sau khi sinh

Nguồn**: Tài liệu sinh ảnh của CometAPI

Debug lỗi sinh video

Sinh video lỗi khác vì là bất đồng bộ. Khởi tạo các biến trạng thái trước vòng lặp để thông báo lỗi timeout luôn đầy đủ:

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}%"    )

Các vấn đề riêng của video:

Triệu chứngNguyên nhânCách khắc phục
Tác vụ kẹt ở trạng thái queued > 10 phútTải máy chủ caoThử lại với model khác
failed không có chi tiết lỗiPrompt bị lọc hoặc lỗi modelDiễn đạt lại prompt
URL video trả về 403URL hết hạnTải xuống ngay
task_not_exist ở lần poll đầu của RunwayTác vụ vẫn đang khởi tạo (hành vi được CometAPI ghi nhận)Chờ 5s và thử lại
Kling trả về "succeed" không phải "succeeded"API của Kling dùng chuỗi trạng thái không chuẩnXử lý cả hai trong logic poll

Nguồn**: Tài liệu sinh video của CometAPI**, Tài liệu Kling Video

Phiên bản 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);}

Danh sách kiểm tra debug

Khi một lần sinh thất bại và bạn không chắc bắt đầu từ đâu:

Đối với sinh văn bản:

  • API key đã được đặt và theo định dạng Authorization: Bearer <key> chưa?
  • finish_reason có khác stop không?
  • content có null không? Kiểm tra xem finish_reason có phải tool_calls không
  • Đầu ra có bị cắt ngắn không? Kiểm tra finish_reason: "length"usage.completion_tokens
  • Lỗi là 4xx (sửa yêu cầu) hay 5xx (retry)?
  • Đầu ra có vượt qua xác thực ở tầng ứng dụng của bạn không? (lỗi im lặng)

Đối với sinh ảnh:

  • Mảng data có rỗng không? (bộ lọc nội dung)
  • Bạn có dùng response_format trên GPT Image 2 không? (không được hỗ trợ — dùng output_format)
  • Bạn có đặt n > 1 trên Qwen Image không? (không được hỗ trợ)
  • Bạn có tải ảnh xuống trước khi URL hết hạn không?

Đối với sinh video:

  • Tác vụ có bị kẹt ở queued không? (thử model khác)
  • Bạn có kiểm tra trường error trong phản hồi tác vụ thất bại không?
  • Bạn có tải video xuống trước khi URL hết hạn không?
  • Bạn có xử lý cả "succeed" (Kling) và "succeeded" (Veo, Runway) không?

FAQ

Q: Yêu cầu của tôi trả về 200 nhưng không có nội dung. Chuyện gì xảy ra?

Kiểm tra finish_reason. Nếu là content_filter, lần sinh nội dung bị chặn — yêu cầu thành công nhưng đầu ra bị ẩn. Nếu là tool_calls, model đã gọi một công cụ thay vì trả văn bản, và content là null theo thiết kế. Nếu finish_reasonstop nhưng nội dung vẫn rỗng, đó là lỗi im lặng — ghi log toàn bộ phản hồi và kiểm tra prompt của bạn.

Q: Làm sao biết prompt của tôi đang bị lọc?

Đối với văn bản: kiểm tra finish_reason === "content_filter". Đối với ảnh: kiểm tra xem mảng data có rỗng không. Đối với video: kiểm tra xem tác vụ chuyển sang trạng thái failed ngay sau khi gửi mà không có chi tiết lỗi. Trong mọi trường hợp, thử diễn đạt prompt trung tính hơn.

Q: Khi nào tôi nên retry một yêu cầu thất bại?

Retry với 4295xx bằng backoff theo cấp số nhân. Đừng retry với 4xx — một yêu cầu sai sẽ không tự sửa. Ngoại lệ là 401 nếu bạn đang xoay vòng API key.

Q: Backoff theo cấp số nhân là gì và tại sao quan trọng?

Thay vì retry ngay lập tức, bạn chờ lâu dần: 1s, 2s, 4s. Thêm jitter ngẫu nhiên (+ random.random()) để ngăn nhiều client retry cùng lúc. Đây là thực hành tiêu chuẩn cho bất kỳ API nào có giới hạn tần suất — không riêng CometAPI.

Q: Tác vụ video bị kẹt ở queued trong 10 phút. Có phải đã thất bại?

Không nhất thiết — hàng đợi có thể bị dồn khi tải cao. Chờ đến ngưỡng max_wait của bạn, sau đó ném TimeoutError và thử lại với model khác. Ghi log ID tác vụ để bạn có thể kiểm tra trạng thái thủ công nếu cần.

Q: Làm sao bắt lỗi im lặng?

Lỗi im lặng cần xác thực ở tầng ứng dụng — API sẽ không nói cho bạn biết đầu ra sai về mặt ngữ nghĩa. Kiểm tra đầu ra có khớp định dạng mong đợi không (JSON hợp lệ, nhãn kỳ vọng, độ dài tối thiểu). Ghi log đầy đủ đầu ra khi xác thực thất bại. Nguyên nhân phổ biến nhất là prompt mơ hồ, hướng dẫn định dạng bị bỏ qua, hoặc đầu vào quá ngắn hoặc quá dài cho tác vụ.

Sẵn sàng giảm 20% chi phí phát triển AI?

Bắt đầu miễn phí trong vài phút. Bao gồm tín dụng dùng thử miễn phí. Không cần thẻ tín dụng.

Đọc thêm