Decisões
ADR-002 · Multi-LLM com fallback automático
Por que cada agente fala com 2-3 providers, não com um só
Status: vigente Data: 2026-05-01 Decisor: Rubens (CEO)
Contexto
Agentes em produção fazem chamadas LLM em laços críticos da operação (atendimento WhatsApp, geração de réplicas Reclame Aqui, briefing diário CEO). Indisponibilidade de 15 minutos num provider durante horário comercial significa:
- Cláudio para de responder clientes → vergonha imediata
- RA1000 não consegue gerar réplica em janela de SLA → multa Reclame Aqui
- Briefing 06h não chega → Rubens começa o dia no escuro
A pergunta não é "se" Anthropic/OpenAI/Google vão ter incident, é "quando".
Decisão
Todo agente chama LLM via core/llm_client.complete() que:
- Roteia tarefa via
core/llm_router.route()→ escolhe modelo principal - Cada
LLMConfigtemfallback_chain: list[str]com 1-2 modelos backup - Em qualquer exceção do principal (
RateLimitError,APIConnectionError,AuthenticationError, etc.), itera pra próxima opção - Trace no Langfuse marca
fallback_used=truequando aciona
Alternativas consideradas
-
Single-vendor (só Anthropic)
- A favor: um SDK, uma billing, um prompt format
- Contra: SPOF inaceitável pra operação 24/7
-
Round-robin entre providers (load balance)
- A favor: distribui carga, reduz latência média
- Contra: comportamento inconsistente entre modelos, debug viraria pesadelo
-
Failover só no cliente (sem agente saber qual usou)
- A favor: simplicidade, agente não pensa nisso
- Contra: difícil pra observabilidade — não sabemos quanto rodou via fallback
Consequências
Ganhos:
- Validado em produção: forçamos Anthropic key inválida → Gemini 2.5 Flash
serviu o request com
fallback_used=trueno trace - Custo zero quando primário funciona (fallback só ativa em erro)
- Confiança pra operar 24/7 sem alertas existenciais
Perdas:
- Pricing tables duplicadas (entry por modelo) — mantém em
core/llm_client.py:PRICING+zyon-saude/src/app/api/models/route.ts:PRICING - Quando troca preço de um provider, precisa atualizar 2 lugares (mitigado pelo doc-auditor)
- Prompts às vezes precisam ajustes finos por provider (Gemini é mais verboso)
Em aberto:
- Não testamos fallback do Sonar (Perplexity) — research_web tem fallback pra Claude mas Claude não tem web search nativo
- OpenAI Whisper não tem fallback (não há Whisper-equivalent no Anthropic)
Quando revisitar
- Se Anthropic atingir 99.99%+ de SLA medido por nós em 6 meses → talvez remover fallback pra tasks low-criticality (economia de complexidade)
- Se aparecer modelo dramaticamente melhor num task type específico → ajustar routing antes de pensar em fallback