Fallback entre modelos: quando sua IA precisa de um plano B

Rate limit e indisponibilidade de provedor de IA não são exceção rara. São parte esperada de depender de um serviço externo que, além de tudo, está sob demanda alta o tempo inteiro. A pergunta não é se um provedor de LLM vai responder 429 ou 503 em algum momento. É o que a aplicação faz quando isso acontecer.

Os dois erros que mais aparecem

429 Too Many Requests indica que o limite de taxa daquele provedor foi excedido, geralmente por um período curto e temporário. 503 Service Unavailable indica que o serviço está indisponível, por sobrecarga ou manutenção, também tipicamente temporário. Os dois casos compartilham uma característica importante: não são erro de aplicação, são erro de dependência externa, e retry ingênuo, sem estratégia nenhuma, tende a piorar exatamente o problema que gerou o erro em primeiro lugar (mais chamadas batendo num serviço já sobrecarregado).

Fallback como parte da pipeline, não como tratamento de exceção solto

Polly v8 trata fallback como mais uma estratégia dentro da mesma resilience pipeline usada para retry, circuit breaker e timeout, em vez de um bloco try/catch separado espalhado pela aplicação. Isso permite compor: tentar de novo algumas vezes com backoff, e só se as tentativas continuarem falhando, cair para um provedor alternativo.

var pipeline = new ResiliencePipelineBuilder<ChatResponse>()
    .AddRetry(new RetryStrategyOptions<ChatResponse>
    {
        ShouldHandle = new PredicateBuilder<ChatResponse>()
            .Handle<HttpRequestException>(ex =>
                ex.StatusCode is HttpStatusCode.TooManyRequests
                    or HttpStatusCode.ServiceUnavailable),
        MaxRetryAttempts = 2,
        BackoffType = DelayBackoffType.Exponential
    })
    .AddFallback(new FallbackStrategyOptions<ChatResponse>
    {
        ShouldHandle = new PredicateBuilder<ChatResponse>()
            .Handle<HttpRequestException>(),
        FallbackAction = async args =>
            Outcome.FromResult(await clienteSecundario.GetResponseAsync(prompt))
    })
    .Build();

A ordem importa: retry tenta se recuperar do provedor principal primeiro, e só quando essas tentativas se esgotam o fallback assume, chamando o provedor secundário. Sem essa ordem, o fallback dispararia na primeira falha isolada, mesmo quando um simples retry teria resolvido sem precisar trocar de provedor.

Escolhendo quando vale a pena ter um plano B

Nem toda aplicação precisa de um segundo provedor configurado, esperando para assumir. Isso tem custo: manter integração com dois provedores, garantir que o comportamento dos dois seja compatível o suficiente para a troca ser transparente, e aceitar que o provedor secundário pode ter qualidade ou latência diferente do principal. Faz sentido quando a indisponibilidade do provedor principal tem custo real e imediato (uma funcionalidade crítica ficando fora do ar) e existe outro provedor cuja resposta, mesmo que não idêntica, ainda seja aceitável para aquele caso de uso.

Fallback degradado: nem sempre é outro modelo

Cair para um provedor secundário é uma forma de fallback, mas não a única. Outra opção, mais barata de manter, é degradar a resposta: devolver uma resposta em cache mais antiga, uma versão simplificada gerada sem IA nenhuma, ou uma mensagem clara de indisponibilidade temporária, em vez de propagar a exceção para quem fez a chamada original. Qual dessas opções faz sentido depende de quão crítica é aquela funcionalidade específica e de quão aceitável é uma resposta “boa o suficiente” no lugar da resposta ideal.

O plano B só funciona se for testado

Um fallback que nunca foi exercitado de propósito, só configurado e esquecido, corre o risco de falhar exatamente no momento em que é mais necessário. Vale simular a indisponibilidade do provedor principal deliberadamente, de tempos em tempos, para confirmar que o caminho alternativo realmente assume sem quebrar, em vez de descobrir isso pela primeira vez durante uma indisponibilidade real.