Pular para o conteúdo

ADR-0014: Fallback em cascata de provedores de LLM

  • Status: Accepted
  • Data: 2026-05-27 (registrada retroativamente como parte do polimento pós-lançamento; a cascata foi entregue em uma versão anterior)
  • Responsáveis pela decisão: Waldemar Szemat

O agente da demo roda como um serviço de worker único (ADR-0007). O provedor primário de LLM é o OpenAI gpt-4o-mini. A cota do lado do provedor é finita, então uma rajada curta de visitantes curiosos pode limitar por taxa o primário enquanto um visitante está no meio de uma conversa. Um 429 Too Many Requests retornado a um visitante de demo ao vivo é inaceitável.

Como degradamos graciosamente quando o provedor primário retorna um erro transitório, sem queimar a cota do secundário em falhas não transitórias e sem mentir no livro-razão de custo sobre qual provedor de fato respondeu?

O escopo é: a etapa de conclusão de LLM dentro do grafo do agente. A cascata é invisível ao esquema de estado do agente e ao guardrail de citação.

  • Preservação de cota: um 4xx que não seja um 429 (por exemplo, 400 malformado, 401 chave inválida) NÃO pode disparar a cascata. Queimar a cota do secundário em um 400 determinístico desperdiça capacidade que tráfego legítimo futuro precisará.
  • Atribuição de custo: o livro-razão de custo precisa registrar o provedor que de fato respondeu, não aquele que tentamos primeiro. Caso contrário, os painéis de custo mentem.
  • Portabilidade de provedor: a ADR-0002 definiu o Protocol de cliente de LLM. A cascata precisa compor no nível do cliente, não vazar para o grafo do agente ou para o arcabouço de avaliação.
  • Transparência de falha: quando todos os provedores falham, o usuário vê um HTTP 503 amigável e retentável com um Retry-After, não um stack trace.
  • Opção A: Provedor único com retentativa apenas em erros transitórios. Sem cascata.
  • Opção B: Cascata que retenta todo erro no próximo provedor (ingênua).
  • Opção C: Cascata com classificador tipado de erro transitório; 4xx que não seja 429 NÃO é retentado; provedor que respondeu marcado nos metadados.
  • Opção D: Serviço de gateway externo (Portkey, LiteLLM Router, OpenRouter) que trata a cascata à frente do agente.

Opção escolhida: Opção C — Cascata com classificador tipado de erro transitório e atribuição ao provedor que respondeu. A razão estrutural mais forte é a preservação de cota: o projeto demonstra uma postura consciente de custo (gates de custo na CI, orçamento por turno), e uma cascata ingênua dobraria silenciosamente o gasto de tokens de entrada em falhas 4xx determinísticas. A Opção D foi rejeitada porque adicionar um serviço externo para uma demo de processo único é superengenharia operacional nesta escala; a abstração no nível de Protocol da ADR-0002 torna a cascata em processo trivial de escrever.

A cadeia da cascata é:

  1. Primário: OpenAI gpt-4o-mini
  2. Fallback: Anthropic claude-haiku-4-5-20251001 (escotilha de escape ante erro transitório tipado quando o primário está limitado por taxa ou com erros)

O terceiro espaço de provedor é deixado vazio por design: a cascata é uma cascata deliberada de dois níveis entre dois provedores, não uma cadeia profunda.

O classificador de transitórios reconhece três classes como retentáveis:

  • HTTP 429 (limite de taxa)
  • HTTP 5xx (erro de servidor)
  • Falha de transporte (sem status HTTP, por exemplo, reset de conexão)

Todo o resto (4xx que não seja 429) levanta exceção imediatamente. A não retentativa de 4xx que não seja 429 é a decisão estrutural de preservação de cota.

O provedor que respondeu é marcado nos metadados do resultado da conclusão para que o acumulador de custo registre o gasto contra o provedor correto.

  • Testes unitários cobrem: OpenAI 429 → sucesso na Anthropic, OpenAI 5xx → sucesso na Anthropic, falha de transporte no OpenAI → sucesso na Anthropic, OpenAI 401 → sem fallback (levanta exceção), todos-os-provedores-falham → 503 ao chamador, provedores mistos com atribuição correta.
  • O teste do livro-razão de custo afirma que o provedor registrado em cada unidade de custo é o provedor que respondeu, não o provedor requisitado.
  • A demo sobrevive a uma rajada de limite de taxa do OpenAI sem intervenção do operador.
  • O fallback da Anthropic mantém respondível um turno limitado por taxa, a um pequeno valor delimitado em USD por turno (a partir da tabela de preços por provedor).
  • A atribuição de custo é honesta de ponta a ponta.
  • O código do agente e o arcabouço de avaliação são alheios à cascata.
  • Preservação de cota: um erro 4xx determinístico não desperdiça a cota do fallback.
  • Dois provedores precisam ser configurados em produção. Operadores que querem apenas um provedor podem fixar um único provedor e pular a cascata, mas a superfície padrão é a cascata completa.
  • Ambos os provedores são cobrados por token, então cada turno respondido tem um custo medido; o gate de custo protege contra gasto descontrolado falhando a CI quando a média do corpus excede o orçamento por turno.
  • Erros cientes de localidade no caminho da cascata ainda não estão localizados — o HTTP 503 retorna corpo em inglês. (Rastreado como trabalho futuro.)
  • O classificador de transitórios é uma superfície de manutenção; novas semânticas HTTP de provedor (por exemplo, um hipotético 425 Too Early) precisam ser adicionadas manualmente.
  • A cascata adiciona um round trip HTTP adicional no pior caso. O orçamento de latência por turno configurado acomoda isso.
  • Bom, porque é operacionalmente simples — um provedor, uma configuração.
  • Bom, porque a retentativa é bem compreendida na camada de transporte HTTP.
  • Ruim, porque não cobre 429 sustentado (esgotamento de cota).
  • Ruim, porque a demo morre quando o limite de taxa do provedor primário é atingido.
  • Bom, porque a demo sobrevive a erros transitórios.
  • Ruim, porque um 400/401 determinístico desperdiça a cota do secundário tentando “retentar” algo que nunca terá sucesso.
  • Ruim, porque a atribuição de custo torna-se ambígua (qual orçamento de provedor este turno atingiu?).

Opção C (escolhida): Cascata tipada com atribuição

Seção intitulada “Opção C (escolhida): Cascata tipada com atribuição”
  • Bom, porque preserva cota (4xx que não seja 429 permanece no primário).
  • Bom, porque é honesta quanto a custo (os metadados registram o provedor que respondeu).
  • Bom, porque é portável entre provedores na costura de Protocol (ADR-0002).
  • Ruim, porque adiciona uma superfície de manutenção (o classificador de transitórios).
  • Bom, porque remove a lógica de cascata do projeto.
  • Bom, porque alguns gateways adicionam observabilidade de graça.
  • Ruim, porque adiciona uma dependência externa, salto de rede e custo operacional para uma demo de processo único.
  • Ruim, porque conflita com a postura de implantação de $0/mês.
  • ADR-0002 — abstração de fornecedor de LLM (o Protocol sobre o qual a cascata compõe)
  • ADR-0007 — alvo de implantação e os fatores por trás da camada de resiliência
  • ADR-0016 — o limitador de taxa em processo complementar e o cache de respostas
  • MADR 4.0.0: https://adr.github.io/madr/