Blog

Fallback entre provedores de LLM sem parar o atendimento

Um sistema de atendimento com IA depende de um serviço que você não controla: a API do provedor de LLM. Ela cai, degrada, entra em manutenção e passa por incidentes regionais, e quando isso acontece o seu atendimento para junto, mesmo que todo o resto da sua infraestrutura esteja saudável. A tentação é resolver com retry: a chamada falhou, tenta de novo. Mas retry no mesmo provedor que está fora não ajuda, e retry num provedor com rate limit só piora o quadro. A resposta de engenharia é o fallback entre provedores: manter um segundo, e às vezes um terceiro, fornecedor de LLM pronto para assumir quando o primário falha, de forma que a queda de um vire só uma troca invisível de rota, não uma queda do atendimento. O que parece simples de fora esconde três decisões difíceis: distinguir a falha que vale trocar de provedor da falha que trocar não resolve, normalizar o prompt e a resposta entre APIs que não são idênticas, e evitar que a tentativa de socorro derrube o provedor de socorro junto. Este artigo mostra por que só retry não basta, quais erros disparam o fallback e quais não, o circuit breaker que protege o secundário, a normalização entre provedores, o cuidado com o custo e a qualidade de cada rota e como testar que a troca acontece sem o cliente perceber.

2026-07-20 / IA Aplicada / 13 min

01

Por que retry no mesmo provedor não é fallback

Retry e fallback resolvem problemas diferentes e são confundidos com frequência. Retry é insistir na mesma porta: a chamada falhou por um soluço transitório, você espera um pouco e tenta de novo no mesmo provedor. Funciona para erro pontual, um pacote perdido, uma instância que reiniciou. Fallback é trocar de porta: o provedor primário não está respondendo de forma consistente, então você manda a mesma requisição para outro provedor. Quando o primário está em incidente amplo, retry só bate na mesma parede mais vezes, some latência ao pedido e ainda pode piorar, porque cada retry é mais carga sobre um serviço que já está sofrendo. O sinal de que você precisa de fallback e não de mais retry é a falha persistir depois de algumas tentativas: aí o problema não é transitório, é o provedor, e a solução é outro provedor.

O erro clássico é tratar retry e fallback como a mesma coisa e empilhar retries agressivos esperando que a nona tentativa dê certo. Num incidente do provedor, isso transforma uma falha rápida numa espera longa: o cliente fica olhando o "digitando..." por dezenas de segundos até o sistema desistir, quando poderia ter recebido a resposta do provedor secundário em um segundo. A ordem certa é retry curto e limitado no provedor atual para absorver o soluço transitório, e fallback para outro provedor assim que fica claro que não é soluço, é queda. Um sem o outro deixa buraco: só retry não sobrevive à queda do provedor, só fallback troca de rota por qualquer soluço bobo que o retry resolveria mais barato.

AspectoRetry (mesmo provedor)Fallback (outro provedor)
ResolveSoluço transitório, erro pontualQueda ou degradação persistente do provedor
AçãoEspera e repete na mesma portaManda a mesma requisição para outra porta
Custo do erroBaixo se limitado, alto se agressivoTroca de rota, exige normalizar prompt e resposta
RiscoInsistir num provedor que já caiuDerrubar o secundário se ele receber toda a carga

02

Quais erros disparam o fallback e quais não

Nem toda falha justifica trocar de provedor. A pergunta que separa é: a culpa é do pedido ou do provedor? Se o prompt é inválido, excede o limite de contexto, viola a política de conteúdo ou usa parâmetros errados, o segundo provedor vai recusar exatamente igual, porque o problema viajou junto com a requisição. Trocar de rota aqui só gasta duas chamadas para receber o mesmo erro. Já se a falha é do provedor, indisponibilidade, timeout, erro interno, sobrecarga, o segundo provedor não carrega esse problema e tem chance real de responder. O fallback deve disparar só na segunda categoria, e o rate limit fica no meio: é do provedor, mas trocar por trocar pode empurrar a carga para um secundário que também vai estourar, então ele pede um cuidado extra que os próximos blocos tratam.

  • Dispara fallback: provedor indisponível (5xx), timeout de conexão ou de resposta, erro interno do provedor, sobrecarga temporária. A causa está do lado do fornecedor e não viaja com a requisição.
  • Dispara fallback com cuidado: rate limit (429). É do provedor, mas trocar sem controle pode saturar o secundário; respeite o Retry-After do primário e considere se o secundário aguenta a carga desviada.
  • Não dispara fallback: prompt inválido, contexto excedido, violação de política de conteúdo, autenticação errada, parâmetro não suportado. A culpa é do pedido, o segundo provedor recusa igual.
  • Caso ambíguo: resposta veio, mas malformada ou vazia. Antes de trocar de provedor, tente uma vez no mesmo; se persistir, pode ser degradação do primário e aí o fallback ajuda.

Classificar o erro certo é o que impede o fallback de virar desperdício ou mascarar bug. Se você trocar de provedor diante de um prompt malformado, o sistema faz duas chamadas caras, recebe dois erros e ainda esconde de você que o prompt está errado, porque o log só mostra "tentou os dois provedores e falhou". A classificação explícita, culpa do pedido versus culpa do provedor, é a fronteira que decide entre corrigir a origem e trocar de rota. Sem ela, o fallback engole erros que você precisava enxergar.

03

O roteador: primário, secundário e a mesma requisição

O coração do fallback é um roteador que fica na frente dos provedores. Ele recebe a requisição do seu sistema uma vez, escolhe o provedor primário, e diante de uma falha que classifica como culpa do provedor, tenta o próximo da lista com a mesma requisição normalizada. O ponto crítico é que a requisição precisa ser a mesma em significado, ainda que a forma mude entre APIs: o mesmo prompt, as mesmas instruções, o mesmo comportamento esperado. Se o roteador manda um prompt para o primário e um prompt diferente para o secundário, o fallback deixa de ser uma rota alternativa e vira um comportamento imprevisível, em que a resposta que o cliente recebe depende de qual provedor estava de pé.

// llm/router.js
// Roteador de fallback: tenta os provedores em ordem com a MESMA requisicao.
// So troca de provedor quando o erro e culpa do provedor, nao do pedido.

export async function callWithFallback(request, providers, { isProviderFault }) {
  let lastError;

  for (const provider of providers) {
    // Circuit breaker: pula o provedor que esta marcado como fora.
    if (provider.isOpen()) continue;

    try {
      // A mesma requisicao normalizada vai para cada provedor da lista.
      // O que muda e so a traducao para o formato de cada API, nao o conteudo.
      const response = await provider.call(request);
      provider.recordSuccess();
      // Devolve QUAL provedor respondeu: essencial para log, custo e metrica.
      return { response, provider: provider.name };
    } catch (err) {
      lastError = err;

      // Culpa do pedido (prompt invalido, contexto excedido): trocar nao ajuda,
      // o proximo provedor recusa igual. Falha rapido em vez de gastar chamadas.
      if (!isProviderFault(err)) throw err;

      // Culpa do provedor: marca a falha e deixa o loop tentar o proximo.
      provider.recordFailure();
    }
  }

  // Todos os provedores da lista falharam por culpa deles: sem rota de saida.
  throw new AllProvidersFailedError('nenhum provedor respondeu', lastError);
}

Repare no que o roteador devolve: não só a resposta, mas qual provedor respondeu. Isso não é detalhe. Sem saber qual rota atendeu cada requisição, você não consegue medir com que frequência o fallback dispara, quanto o provedor secundário está custando, nem se a qualidade da resposta muda quando a rota troca. O provedor que respondeu é a informação que transforma o fallback de uma caixa-preta que "às vezes funciona" num mecanismo observável, com métrica de quantas requisições cada provedor absorveu e alerta quando o primário passa a falhar demais.

04

Circuit breaker: não insistir no provedor que já caiu

Se o provedor primário está em incidente, tentar cada requisição nele antes de cair para o secundário adiciona a latência da falha a toda chamada. Mil clientes esperam o timeout do primário mil vezes antes de o roteador desistir e ir ao secundário. O circuit breaker resolve isso: depois de um número de falhas seguidas de um provedor, o roteador abre o circuito daquele provedor e para de tentá-lo por um tempo, mandando as requisições direto para o próximo da lista. Passado o intervalo, ele deixa uma requisição de teste passar; se voltar, fecha o circuito e retoma o provedor; se falhar de novo, mantém aberto. É o que evita que a queda de um provedor contamine a latência de todas as requisições que ainda tentam bater nele.

Fallback com circuit breaker por provedor

  requisicao
     |
     v
  [ primario ]  circuito FECHADO -> tenta
     |                                  \
     | sucesso                           \ N falhas seguidas
     v                                    v
  responde                          circuito ABERTO
  (provedor: primario)              (pula o primario por um tempo)
                                          |
                                          v
                                    [ secundario ]  -> tenta
                                          |
                                          | sucesso
                                          v
                                    responde
                                    (provedor: secundario)

  passado o intervalo: 1 requisicao de teste no primario
    volta  -> circuito FECHA, retoma o primario
    falha  -> circuito segue ABERTO

  erro culpa-do-pedido: NAO abre circuito, NAO faz fallback -> falha rapido

O circuit breaker precisa ser por provedor, não global, porque a razão de existir do fallback é que os provedores falham de forma independente. Um breaker único que abre para todo mundo joga fora justamente a redundância que você montou. E o breaker só deve contar as falhas de culpa do provedor: um prompt inválido não é sinal de que o provedor caiu, então não pode abrir o circuito. Misturar as duas contas faz o breaker abrir por erros do seu próprio sistema, tirando de operação um provedor que estava saudável. A separação entre culpa do pedido e culpa do provedor, que decide o fallback, também é a que alimenta o breaker.

05

Normalização: a mesma resposta de APIs diferentes

Provedores de LLM não têm a mesma API. Mudam o formato da requisição, o nome dos parâmetros, a forma de passar instruções de sistema, a estrutura da resposta, os nomes dos modelos e a maneira de fazer tool use. Se o seu sistema fala direto o dialeto de um provedor, trocar de rota no fallback obriga a reescrever a requisição na hora, e a resposta que volta tem um formato diferente que o resto do código não sabe ler. A saída é uma camada de normalização: o seu sistema fala um formato interno único, e cada provedor tem um adaptador que traduz esse formato para o dialeto do provedor na ida e traduz a resposta de volta para o formato interno na volta. Assim o roteador troca de provedor sem que o restante do código perceba que a rota mudou.

A normalização tem um limite honesto: nem tudo se traduz um para um. Um provedor pode ter um recurso que o outro não tem, um formato de tool use diferente, um comportamento de recusa distinto. O adaptador precisa mapear o que dá e ter uma degradação clara para o que não dá, e o fallback só deve incluir na lista provedores capazes de atender a mesma requisição em termos práticos. Colocar como secundário um provedor que não suporta o tool use que a sua aplicação exige não é redundância, é uma rota que vai falhar de outro jeito na hora do incidente. A regra é que os provedores da lista de fallback precisam ser intercambiáveis para o caso de uso, não idênticos em tudo.

O que muda entre provedoresSem normalizaçãoCom camada de adaptador
Formato da requisiçãoReescrever a chamada em cada rotaFormato interno único, adaptador traduz na ida
Estrutura da respostaCódigo a jusante quebra ao trocar de rotaAdaptador traduz de volta ao formato interno
Nomes de modeloModelo do primário não existe no secundárioMapa de modelo equivalente por provedor
Tool use e recursosSecundário não suporta o que o primário faziaSó entram na lista provedores intercambiáveis

06

Custo, qualidade e o cuidado com o rate limit

A rota de fallback não é gratuita nem neutra. O provedor secundário pode custar mais por token, pode ter latência diferente e pode dar respostas de qualidade diferente para o mesmo prompt, porque é outro modelo. Isso tem duas consequências práticas. A primeira é de custo: se o fallback dispara com frequência, você está gastando na rota cara sem perceber, e a métrica de qual provedor respondeu é o que revela isso. A segunda é de qualidade: um prompt calibrado para o modelo primário pode se comportar diferente no secundário, então o fallback que salva a disponibilidade pode degradar a resposta. O secundário existe para manter o atendimento de pé durante o incidente, não necessariamente para dar a melhor resposta possível, e essa é uma troca consciente, não um acidente.

O rate limit merece um parágrafo próprio porque é o erro que mais engana. Ele é culpa do provedor, então tenta se qualificar para o fallback, mas trocar cegamente diante de um 429 tem um risco específico: se o primário está com rate limit por causa de um pico de tráfego seu, desviar todo esse tráfego para o secundário provavelmente vai estourar o rate limit do secundário também, e agora os dois estão fora. O fallback por rate limit precisa respeitar o Retry-After que o primário informa, e o padrão mais seguro combina retry com backoff no primário e fallback só quando o secundário tem folga para absorver a carga. Trocar de provedor não cria capacidade do nada; se a demanda excede os dois, o fallback só espalha a fila.

  1. Classifique o erro antes de agir: culpa do pedido falha rápido, culpa do provedor tenta o fallback, rate limit respeita o Retry-After.
  2. Mantenha retry curto e limitado no provedor atual para o soluço transitório, e reserve o fallback para a falha persistente.
  3. Use circuit breaker por provedor, contando só as falhas de culpa do provedor, para não adicionar a latência da queda a cada requisição.
  4. Normalize entrada e saída com adaptadores, e só inclua na lista de fallback provedores intercambiáveis para o caso de uso.
  5. Devolva e registre qual provedor respondeu; meça a taxa de fallback, o custo por rota e a qualidade da resposta secundária.

07

Testar que a troca acontece sem o cliente perceber

Fallback é código que só executa quando algo dá errado, então é o tipo de mecanismo que passa despercebido até o incidente real, quando você descobre que ele nunca funcionou. A única forma de confiar nele é forçar a falha em teste. Você simula o provedor primário devolvendo 5xx, timeout e 429, e verifica que o roteador cai para o secundário e devolve uma resposta válida com o provedor secundário registrado. Simula um prompt inválido e verifica que o roteador não faz fallback, falha rápido e não gasta a segunda chamada. Simula falhas seguidas e verifica que o circuit breaker abre e para de tentar o primário. Cada caminho do fallback precisa de um teste que o exercite de propósito, porque em produção ele fica quieto até o dia em que precisa salvar o atendimento.

// llm/router.test.js
// Testa os caminhos do fallback forcando cada falha de proposito.

test('cai para o secundario quando o primario retorna 5xx', async () => {
  const primario = fakeProvider({ name: 'primario', fail: 'server_error' });
  const secundario = fakeProvider({ name: 'secundario', ok: true });

  const { response, provider } = await callWithFallback(
    request,
    [primario, secundario],
    { isProviderFault },
  );

  // A troca precisa ser invisivel para quem chamou: resposta valida...
  expect(response).toBeValid();
  // ...mas o log precisa revelar que foi o secundario que atendeu.
  expect(provider).toBe('secundario');
});

test('NAO faz fallback com prompt invalido: falha rapido', async () => {
  const primario = fakeProvider({ name: 'primario', fail: 'invalid_prompt' });
  const secundario = fakeProvider({ name: 'secundario', ok: true });

  // Culpa do pedido: o roteador nao gasta a segunda chamada, propaga o erro.
  await expect(
    callWithFallback(request, [primario, secundario], { isProviderFault }),
  ).rejects.toThrow('invalid_prompt');
  expect(secundario.callCount).toBe(0);
});

O teste do caminho negativo, o de que o fallback não dispara para culpa do pedido, é tão importante quanto o positivo. É ele que garante que o mecanismo de socorro não vira um encobridor de bug, gastando duas chamadas e escondendo do log que o prompt estava errado. Um fallback bem testado é aquele em que você tem confiança tanto de que a troca acontece quando o provedor cai, quanto de que ela não acontece quando o erro é seu. Os dois lados juntos são o que faz a queda de um provedor virar uma linha no log em vez de uma queda no atendimento.

FAQ

Perguntas frequentes

Qual a diferença entre retry e fallback entre provedores de LLM?

Retry é insistir no mesmo provedor: a chamada falhou por um soluço transitório, você espera um pouco e repete na mesma porta. Serve para erro pontual e não sobrevive à queda do provedor, porque insistir num serviço fora só bate na mesma parede. Fallback é trocar de provedor: diante de uma falha persistente que é culpa do provedor, você manda a mesma requisição para outro fornecedor de LLM, que não carrega aquele incidente. Os dois se complementam: retry curto e limitado absorve o soluço transitório barato, e o fallback assume quando fica claro que não é soluço, é queda. Só retry deixa o atendimento cair junto com o provedor; só fallback troca de rota por qualquer erro bobo que o retry resolveria mais barato.

Todo erro do provedor deve disparar o fallback?

Não. A pergunta que separa é de quem é a culpa. Erros de culpa do provedor, indisponibilidade, timeout, erro interno, sobrecarga, disparam o fallback, porque o segundo provedor não carrega esse problema e tem chance real de responder. Erros de culpa do pedido, prompt inválido, contexto excedido, violação de política, autenticação errada, não devem disparar, porque o problema viajou com a requisição e o segundo provedor vai recusar exatamente igual: trocar só gasta duas chamadas para receber o mesmo erro e ainda esconde do log que a origem estava errada. O rate limit fica no meio: é do provedor, mas trocar sem controle pode saturar o secundário, então ele pede respeitar o Retry-After e checar se o secundário tem folga antes de desviar a carga.

O provedor secundário dá a mesma resposta que o primário?

Nem sempre, e essa é uma troca consciente. O secundário costuma ser outro modelo, com custo por token, latência e comportamento diferentes, então um prompt calibrado para o primário pode se comportar de outro jeito no secundário. Uma camada de normalização, com adaptadores que traduzem a requisição e a resposta entre o formato interno e o dialeto de cada API, mantém o resto do código sem perceber a troca de rota, mas ela não iguala a qualidade das respostas, só o formato. Por isso o fallback existe para manter o atendimento de pé durante o incidente, não necessariamente para dar a melhor resposta possível, e só devem entrar na lista provedores intercambiáveis para o caso de uso, capazes de atender a mesma requisição em termos práticos, incluindo tool use e limites de contexto.

Fallback transforma a queda de um provedor numa troca invisível de rota

A disponibilidade do seu atendimento com IA não pode depender de um único fornecedor que você não controla. Classificar a falha entre culpa do pedido e culpa do provedor, cair para um secundário com a mesma requisição normalizada, proteger cada rota com circuit breaker e respeitar o rate limit transforma a queda de um provedor numa linha no log em vez de uma queda no atendimento. Posso desenhar essa camada de fallback no seu sistema de IA, escolhendo os provedores intercambiáveis, calibrando os gatilhos de troca e medindo custo e qualidade de cada rota, para que um incidente do fornecedor não vire um incidente do seu negócio.