A API Doubao é oferecida pelo Volcano Ark e tem formato próximo ao da OpenAI, mas a semântica dos erros tem características próprias. O caso mais típico é o 404: o serviço não caiu, e o problema costuma estar no ID do endpoint de inferência, no nome do modelo, na região ou no caminho. O 429 também é mal interpretado com frequência: pode ser limite de taxa, concorrência, cota, inadimplência ou pacote de recursos esgotado, e o tratamento é completamente diferente em cada caso.

A ordem de diagnóstico deve ser fixa em cinco passos: ver o status HTTP, ler code e message no corpo, capturar o request ID, reproduzir com uma requisição mínima e só então alterar parâmetros ou trocar de modelo. Este artigo percorre 401, 404, 400, 429, timeout e 5xx, com código de tratamento de erros e checklist de logs. Modelos e cobrança seguem a página de preços.

Nota: Doubao, Volcano Ark e Volcano Engine são marcas da ByteDance. Este artigo é apenas uma explicação técnica e não implica vínculo de subordinação, representação ou parceria com a ByteDance. Códigos de erro, critérios de limitação e comportamento da interface podem mudar entre versões; prevalecem a documentação do fornecedor e a resposta real.

1. Distinga primeiro as duas camadas de erro

O status HTTP descreve o resultado no nível do protocolo; code, type e message no corpo descrevem a causa específica. O mesmo 401 pode ser Key ausente, Key errada, Key expirada ou projeto sem permissão; o mesmo 429 pode ser requisição rápida demais, concorrência alta, cota esgotada ou inadimplência. Se você registra apenas o status, o diagnóstico vira adivinhação.

HTTP Significado comum Cenário Doubao Repetir?
401 Falha de autenticação Key ausente, formato errado, expirada ou incompatível com a região Não; corrigir credencial
403 Sem permissão Key sem permissão para o modelo ou endpoint Não; verificar autorização
404 Recurso inexistente ID do endpoint, nome do modelo, caminho ou região errados Não; conferir primeiro
400 Erro de parâmetro Contexto longo, max_tokens inválido, formato de imagem não suportado Não; ajustar requisição
429 Limite ou cota RPM, TPM, concorrência, inadimplência ou pacote esgotado Depende da causa
500/502/503 Erro do servidor Anomalia no upstream, oscilação do gateway, indisponibilidade breve Sim; backoff
Timeout Conexão ou leitura Rede, fila, entrada longa, saída longa Retentativa limitada

O nome do campo depende da resposta real: às vezes fica em error.code, às vezes no nível superior. O código não deve presumir uma única estrutura.

2. 401: quatro causas de falha de autenticação

O 401 não tem relação com capacidade do modelo nem saldo; verifique primeiro a cadeia de credenciais:

  • O cabeçalho é Authorization: Bearer <key>, com um espaço após os dois-pontos?
  • A Key foi carregada corretamente da variável de ambiente, sem aspas, espaços ou quebras de linha?
  • A Key foi excluída, rotacionada ou expirou?
  • Projeto, região e Base URL da Key são compatíveis?

Keys copiadas de janelas de chat costumam trazer caracteres invisíveis. Envie primeiro uma requisição mínima que retorna uma única palavra; isso elimina a maioria dos erros básicos. Se a mesma Key funciona no console mas o código retorna 401, verifique se a variável de ambiente e o proxy reverso estão descartando o cabeçalho de autenticação.

3. 404: ID do endpoint e nome do modelo são os mais confundidos

O Volcano Ark tem duas formas comuns de especificar modelo: o endpoint de inferência criado no console, geralmente iniciado por ep-, e a chamada direta pelo ID do modelo, suportada por alguns modelos. Ambos vão no campo model, mas permissões, limitação e escopo podem diferir. Misturar ID de endpoint com nome de modelo, ou copiar o endpoint de outro projeto, gera 404 ou um "recurso inexistente" semelhante.

Ordem de diagnóstico:

  1. Confirme se a Base URL é um endereço regional como https://ark.cn-beijing.volces.com/api/v3, sem /api/v3 a mais ou a menos.
  2. No console, verifique se o endpoint existe, pertence ao projeto atual e não foi desativado.
  3. Confirme se a chamada atual exige ID de endpoint ou ID de modelo; não preencha de memória.
  4. Envie uma requisição mínima com curl para eliminar efeitos de montagem automática de caminho pelo SDK e de reescrita pelo proxy.
  5. Observe o tipo de conteúdo: se voltar uma página HTML de erro, a requisição pode não ter chegado ao Ark e ter sido bloqueada pelo gateway ou proxy reverso.

404 com HTML normalmente é problema de caminho ou proxy; 404 com corpo JSON de erro se parece mais com problema de modelo ou endpoint.

4. 400: contexto longo e parâmetros inválidos

O 400 cobre muita coisa; o caso mais comum é contexto longo. A janela de contexto geralmente soma tokens de entrada e de saída, e um max_tokens grande demais faz uma requisição antes válida ultrapassar o limite. Imagens, definições de ferramentas e mensagens históricas também consomem tokens.

A solução não é aumentar o limite, mas deixar margem na entrada:

  • Comprimir ou dividir o prompt de sistema e remover regras repetidas.
  • Resumir o histórico, mantendo apenas as últimas rodadas e os fatos principais.
  • Em documentos longos, recuperar primeiro e depois concatenar; não inserir o texto inteiro.
  • Comprimir imagens e confirmar se formato e tamanho estão dentro do suportado.
  • Verificar se temperature e top_p estão fora da faixa e se os papéis das mensagens são válidos.

A contagem de tokens é estimada; perto do limite, deixar 10% a 20% de margem é mais seguro.

5. 429: limite, concorrência e cota devem ser tratados separadamente

O 429 tem quatro causas:

  • Limite de taxa: requisições ou tokens por minuto acima do plano; com backoff, normalmente se recupera.
  • Limite de concorrência: requisições simultâneas demais; é preciso reduzir concorrência ou enfileirar, pois repetir só agrava o congestionamento.
  • Cota ou pacote esgotado: a cota acabou; repetir não adianta, é preciso recarregar ou ajustar o plano.
  • Estado anormal da conta: inadimplência, falta de verificação ou mudança de permissão; siga a orientação do console.

Se a resposta trouxer Retry-After, espere o valor indicado; caso contrário, use backoff exponencial com jitter aleatório. Não repita 429 de cota ou inadimplência; isso apenas prolonga a falha.

Um 429 em streaming pode ocorrer na abertura da conexão ou aparecer como evento de erro dentro do fluxo. O cliente precisa tratar tanto o status quanto o erro no fluxo; não basta olhar o primeiro trecho de dados.

6. Timeout e interrupção de streaming: localize em três segmentos

O timeout deve ser dividido em três partes:

  • Timeout de conexão: DNS, TLS, proxy de saída ou rede inacessível; costuma falhar em poucos segundos.
  • Timeout do primeiro token: a requisição chegou ao upstream, mas está na fila ou processando entrada longa, e o primeiro fragmento demora.
  • Timeout de leitura: o intervalo entre dois fragmentos é longo demais, por saída extensa, raciocínio demorado ou conexão encerrada por equipamento intermediário.

Os três exigem configurações diferentes. Um timeout total grande demais mantém requisições com falha ocupando o pool de conexões; pequeno demais interrompe saídas longas válidas. Recomenda-se timeout de conexão em torno de 5 segundos, timeout de leitura conforme o P95 do negócio e um orçamento total na camada de aplicação.

Em streaming, verifique também o buffer do proxy reverso. O buffer atrasa a entrega dos fragmentos e parece "travamento". Após uma interrupção, não grave conteúdo parcial como resultado completo.

7. Request ID e logs: a evidência do diagnóstico

As respostas do Volcano Ark normalmente trazem um identificador de requisição, com campos como X-Request-Id; algumas interfaces também retornam um log ID como X-Tt-Logid. O nome depende da resposta real, e o cabeçalho completo deve ser preservado, não apenas um campo escolhido a dedo.

O cliente também deve gerar seu próprio trace_id, mantido igual em todas as retentativas da mesma requisição de negócio. O log deve registrar pelo menos horário, trace_id, Base URL, modelo ou endpoint, status, code e message do erro, duração, número de tentativas, se é streaming e consumo de tokens.

Registre apenas os primeiros caracteres ou o hash da Key; não grave o texto do usuário nem a resposta completa em disco. Para o corpo de erro, os primeiros 500 caracteres bastam.

8. Retentativa e backoff: um código utilizável

O código abaixo desativa a retentativa automática do SDK, decide pelo status e pelo conteúdo do erro, respeita Retry-After e gera um trace_id para cada requisição lógica:

import os
import random
import time
import uuid

import httpx
from openai import OpenAI, APIConnectionError, APIStatusError, APITimeoutError

client = OpenAI(
    api_key=os.environ["ARK_API_KEY"],
    base_url=os.environ.get(
        "ARK_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"
    ),
    timeout=httpx.Timeout(connect=5.0, read=180.0, write=30.0, pool=10.0),
    max_retries=0,  # desativa a retentativa automática do SDK; a lógica abaixo controla tudo
)

def parse_error(exc: APIStatusError) -> tuple[str, str]:
    try:
        body = exc.response.json()
    except Exception:
        return "", exc.response.text[:200]
    err = body.get("error", body)
    return str(err.get("code") or err.get("type") or ""), str(err.get("message") or "")

def is_retryable(exc: Exception) -> bool:
    if isinstance(exc, (APITimeoutError, APIConnectionError)):
        return True
    if not isinstance(exc, APIStatusError):
        return False
    if exc.status_code >= 500:
        return True
    if exc.status_code != 429:
        return False
    code, message = parse_error(exc)
    text = f"{code} {message}".lower()
    # 429 de cota, inadimplência e permissão não adianta repetir; complemente as palavras-chave conforme o texto real
    return not any(k in text for k in ("quota", "arrears", "balance", "permission"))

def ask(prompt: str, max_attempts: int = 4) -> str:
    trace_id = uuid.uuid4().hex[:12]
    for attempt in range(1, max_attempts + 1):
        try:
            resp = client.chat.completions.create(
                model=os.environ["ARK_MODEL"],  # ID do endpoint ou ID do modelo
                messages=[{"role": "user", "content": prompt}],
                max_tokens=1024,
            )
            return resp.choices[0].message.content
        except Exception as exc:
            status = getattr(exc, "status_code", "")
            code, message = parse_error(exc) if isinstance(exc, APIStatusError) else ("", "")
            print(
                f"trace={trace_id} attempt={attempt} status={status} "
                f"code={code} message={message[:200]}"
            )
            if attempt == max_attempts or not is_retryable(exc):
                raise
            retry_after = getattr(
                getattr(exc, "response", None), "headers", {}
            ).get("retry-after")
            try:
                wait = float(retry_after) if retry_after else min(2 ** (attempt - 1), 20)
            except ValueError:
                wait = min(2 ** (attempt - 1), 20)
            time.sleep(wait + random.uniform(0, 1))
    raise RuntimeError("tentativas esgotadas")

Três regras adicionais: em requisições com efeitos colaterais, use uma chave de idempotência para confirmar o estado; controle o orçamento de retentativas globalmente; monitore a proporção de 429 e a latência P95, reduzindo a concorrência primeiro quando houver salto.

9. Checklist para antes de contatar o suporte

  1. Horário e fuso da falha, e se é contínua ou esporádica.
  2. Base URL, caminho completo, nome do modelo ou ID do endpoint.
  3. Status HTTP, código de erro, mensagem e request ID.
  4. Uma requisição curl mínima reproduzível, com a Key mascarada.
  5. Se é streaming, contexto longo, imagem ou chamada de ferramenta.
  6. Se houve mudança recente de Key, endpoint, cota ou proxy.
  7. Escopo: uma Key, um projeto, uma região ou todo o serviço.

Com essas informações, o suporte consegue comparar diretamente com os logs do upstream. Após cada falha, acrescente um monitoramento ou alerta.

10. Perguntas frequentes (FAQ)

1. Todo 404 significa que o modelo foi descontinuado?

Não. O mais comum é ID de endpoint ou nome de modelo errado, endpoint excluído, região incompatível ou caminho sem /api/v3. Veja primeiro se a resposta é JSON ou uma página HTML de erro e depois confira item a item.

2. Quantas vezes repetir um 429?

Para limite de taxa e congestionamento breve, repita de 2 a 3 vezes com backoff exponencial. Não repita 429 por cota esgotada, inadimplência ou permissão insuficiente; resolva primeiro a conta ou o plano. Respeite Retry-After.

3. Como o comprimento do contexto é calculado?

Geralmente pela soma de tokens de entrada e saída; max_tokens também ocupa o total. Imagens, definições de ferramentas e mensagens históricas entram na entrada; perto do limite, deixe margem.

4. Como configurar timeout em requisições de streaming?

O timeout de leitura normalmente limita o intervalo entre dois fragmentos, não a duração total. O timeout de conexão é configurado separadamente, e a aplicação define um orçamento total e um timeout de inatividade. Buffer de proxy atrasa a entrega dos fragmentos.

5. Por que os erros aumentaram depois de adotar um gateway?

Primeiro confirme se o erro vem do gateway ou do upstream: observe o formato do corpo, o status e o request ID. Guarde o trace ID e compare requisições mínimas por conexão direta e pelo gateway. A maioria dos problemas está em montagem de caminho, encaminhamento do cabeçalho de autenticação ou timeout.

Próximos passos

Crie primeiro uma Key de teste no ZSQH AI Gateway, ensaie uma vez cada caso — 401, 404, 429 e timeout — e só então conecte o monitoramento de produção. Modelos disponíveis e formas de cobrança estão na página de preços.

Leitura relacionada