Skip to content

fix(ia): o erro para de apontar pro provider errado - #44

Merged
BarryBits merged 1 commit into
mainfrom
fix/erro-de-ia-para-de-mentir
Aug 5, 2026
Merged

fix(ia): o erro para de apontar pro provider errado#44
BarryBits merged 1 commit into
mainfrom
fix/erro-de-ia-para-de-mentir

Conversation

@BarryBits

Copy link
Copy Markdown
Owner

Nasceu de um caso real: a geração do Plano de Voo da Vertímetal falhou e o erro mandou a investigação pro lugar errado.

O que a tela mostrou

[plano-voo-v2] IA retornou narrativas inválidas (length=0): resposta vazia da IA
[provider=gemini, modelo=gemini-2.5-flash-lite, latência 19723ms, retries 6, fallback=openai]
— causa: openai_http_429: "You have no credits remaining. Add credits to continue..."

Lê-se como problema de fatura da OpenAI. Não era. provider=gemini é o primário — a causa exibida era do fallback. O Gemini falhou primeiro, e o motivo dele foi apagado antes de chegar na tela.

A causa raiz era ambiental (variável de ambiente na Vercel só entra em deployment criado depois dela — o deployment antigo nunca viu a GEMINI_API_KEY), mas o erro escondeu isso e custou uma rodada inteira de investigação.

As duas mentiras

1. A causa do primário era sobrescritaai-client.ts:

attempt.error = fbAttempt.error ?? attempt.error;   // ← apaga a causa do primário

Agora as duas sobrevivem, nomeadas: gemini: <causa> | fallback(openai): <causa>. O primário é quem deveria ter respondido — a causa dele é a que importa.

2. O contador de retries era fixo. O retorno de falha do tryProvider usava sempre RETRY_DELAYS_MS.length, então um erro não-retriable (chave inválida, modelo inexistente) — que sai na primeira tentativa — aparecia como retries 3. Isso faz configuração quebrada parecer instabilidade passageira, e apaga o sinal de que não adianta tentar de novo. Agora conta as tentativas reais.

Risco

Zero mudança de comportamento: nenhuma chamada nova, nenhum retry a mais, mesmo resultado em caso de sucesso. Muda só o que o erro conta quando os dois providers caem.

Prova

Os 3 testes novos mockam o SDK do Gemini e o fetch da OpenAI pra falharem juntos. 2 deles falham contra o código antigo (conferido rodando a suíte nova contra o ai-client.ts do HEAD); o terceiro trava o caminho em que o fallback responde — se todos passassem nos dois lados, não provariam nada.

Verificação em produção

O caminho está saudável hoje, conferido no banco depois da geração de 03:13:

ai_calls / plano_voo_v2_generate
  provider=gemini  modelo=gemini-2.5-flash-lite  retries=0  fallback=-  valid=true  7012ms

Gemini respondeu de primeira, sem tocar na OpenAI. Este PR é rede para a próxima vez.

Gate

1495/1495 testes (+3) · tsc limpo · 0 erro/warning novo de eslint.

🤖 Generated with Claude Code

Caso real (Vertimetal, 2026-08-05). A geracao do Plano de Voo falhou e a
tela mostrou:

  provider=gemini, modelo=gemini-2.5-flash-lite, retries 6, fallback=openai
  — causa: openai_http_429: "You have no credits remaining"

A causa exibida era do FALLBACK. O Gemini havia falhado antes, e o motivo
dele foi apagado — mandando a depuracao pra fatura da OpenAI enquanto o
bloqueio real estava no Gemini, invisivel. Custou uma rodada inteira de
investigacao. (A causa raiz era ambiental: variavel de ambiente na Vercel
so entra em deployment criado DEPOIS dela; o deployment antigo nunca viu
a GEMINI_API_KEY.)

Duas mentiras no relatorio de erro:

1. `attempt.error = fbAttempt.error ?? attempt.error` SOBRESCREVIA a causa
   do primario. Agora as duas sobrevivem, nomeadas:
   `gemini: <causa> | fallback(openai): <causa>`. O primario e quem deveria
   ter respondido — a causa dele e a que importa.

2. O retorno de falha do `tryProvider` usava sempre `RETRY_DELAYS_MS.length`,
   entao um erro NAO-retriable (chave invalida, modelo inexistente) — que
   sai na PRIMEIRA tentativa — aparecia como "retries 3". Isso faz
   configuracao quebrada parecer instabilidade passageira e apaga o sinal
   de que nao adianta tentar de novo. Agora conta as tentativas reais.

Zero mudanca de comportamento: nenhuma chamada nova, nenhum retry a mais,
mesmo resultado em caso de sucesso. Muda so o que o erro conta.

Os 3 testes novos mockam o SDK do Gemini e o fetch da OpenAI pra falhar
juntos; 2 deles falham contra o codigo antigo (conferido com stash), o
terceiro trava o caminho em que o fallback responde.

Gate: 1495/1495 testes, tsc limpo, 0 erro/warning novo de eslint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
atr-os Ready Ready Preview Aug 5, 2026 3:27am

@supabase

supabase Bot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project tdlxqqgechxhkygdmsxq because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BarryBits
BarryBits merged commit b03d237 into main Aug 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant