Files
DeskcommCRM/tests
betoartsandClaude Opus 5 29eb60b78e fix(ia): falha do classificador auxiliar deixa de calar o agente (recorte do #714)
Recorte do #714, de @betoarts (Humberto Moura Neto). A ideia e o diagnostico
sao dele; o que muda em relacao ao patch original e o LUGAR onde a guarda
entra, porque o arquivo alvo andou muito desde que ele escreveu.

=== O DEFEITO

`classifyStage` e `classifyJailbreak` rodam ANTES de o agente responder e sao
os dois ADVISORIOS do turno: um sugere o estagio do funil (quem confirma e o
modelo do agente, via `update_lead_state`), o outro so FLAGRA a mensagem no
trace e nunca vetou um inbound sozinho.

A excecao de `runModelCall` subia dos dois assim mesmo. Como eles sao os dois
elementos do `Promise.all` que abre `executarTurnoDoAgente`, qualquer falha
deles matava o turno inteiro e o cliente ficava sem resposta. O caso dominante
nao e o provedor cair: e o ponto auxiliar, em Configuracoes > Provedores de IA,
apontar para um modelo que saiu do painel ou para uma credencial revogada — o
modelo do agente de pe, o atendimento parado.

Os dois classificadores ja degradavam saida ILEGIVEL sem bloquear
(`parseStageSuggestion` devolve null, `parseJailbreakClassification` devolve
`none`). A excecao era o unico caminho que fugia dessa regra, subindo.

=== ONDE A GUARDA ENTRA, E POR QUE NAO ONDE ELE POS

O patch do #714 envolvia os dois CALL SITES em `inbound-turn.ts`, com dois
`await` em serie — era a forma daquele arquivo na epoca. Hoje o turno roda os
dois em `Promise.all`, e `tests/unit/classificadores-auxiliares-em-paralelo.test.ts`
exige, por AST, que as duas chamadas sejam elementos DIRETOS do mesmo array.
Aplicar o patch como escrito reintroduziria a latencia em serie que aquele
teste existe para impedir, e reprovaria o gate.

A guarda entra dentro de `classifyStage` e `classifyJailbreak`, que e o lugar
certo por tres razoes: e onde a regra de degradacao dessas funcoes ja mora
(a de parse), cobre todo call site futuro sem lista a manter, e deixa o turno
intocado — nenhum dos dois testes de AST do `inbound-turn.ts` precisou mudar.

=== O QUE CONTINUA SUBINDO

`LlmBudgetExceededError`, exatamente como o @betoarts escreveu. Quem a espera e
`comHandoffSeOrcamentoAcabar`: os auxiliares sao os PRIMEIROS a estourar o teto
do mes, e e essa escolta que passa a conversa para uma pessoa em vez de deixar
o lead no vacuo. Engolir o erro de orcamento aqui trocaria o handoff por um
turno que segue gastando.

=== O DEGRADE NAO E SILENCIOSO

`runModelCall` grava a chamada falha em `llm_calls` (purposes
`stage_classifier` / `jailbreak_detect`) ANTES de relancar, e o `warn` carimba
o run. O laco de retorno e essa linha — "a camada auxiliar parou de rodar" tem
onde ser lido, em Uso de IA. Nos campos do log vai so a mensagem do
fornecedor, truncada em 200: a mensagem do lead nunca entra (regra dura 8),
e ha caso medindo isso.

=== PROVA

`tests/unit/classificador-auxiliar-nao-derruba-o-turno.test.ts`, 5 casos.
Controle negativo rodado: com os dois arquivos revertidos ao estado da main,
3 dos 5 reprovam (os tres do degrade) e 2 passam — os de orcamento, que medem
a propriedade que NAO podia ser quebrada pelo `catch` novo.

A METADE do #714 que NAO entrou: `app/api/v1/ai/agents/[id]/versions/[vid]/test/route.ts`,
onde ele trocava `status: "error"` por `"failed"`. A main ja resolveu, e melhor
— o caminho de erro daquela rota ja grava `failed`, `error_message` e um
`logger.error`, e o `catch` vazio que tornava a falha indiagnosticavel tambem
ja foi fechado. Trazer o pedaco dele ali nao mudaria byte nenhum.

Refs: #714
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHzAhMNW4yRWfgR5Ack89m
2026-09-20 02:57:37 -03:00
..
2026-09-17 23:52:44 -03:00