Problema
Triagem operacional de tickets costuma depender de leitura manual ou de um classificador opaco: sem decisão estruturada, sem trilha de auditoria e sem medição honesta de onde a automação acerta e onde precisa de revisão humana.
O Ops Triage AI trabalha esse problema de forma delimitada: recebe tickets e produz classificação estruturada (categoria, prioridade, risco, time sugerido) mais uma decisão auditável sobre o que pode seguir automático e o que exige revisão humana. O repositório é público, então cada decisão descrita aqui pode ser inspecionada no código.
Arquitetura
O fluxo principal do sistema:
HTTP → validação → PersistedTriageService → TriageTicket → DeterministicTriageClassifier → OllamaTriageClassifier → HybridPolicy → TriageDecision → Prisma/PostgreSQL → Feedback/Audit Trail.
Cada peça tem uma responsabilidade explícita:
- DeterministicTriageClassifier: baseline previsível, barato e reproduzível, com sinais e confiança heurística.
- OllamaTriageClassifier: interpretação semântica com LLM local (qwen2.5:7b-instruct-q5_K_S) e structured output validado.
- HybridPolicy: decide human review, fallback e origem da decisão. É uma camada de decisão pura — não faz field-level merge entre classificadores.
- PersistedTriageService: orquestra o lifecycle persistido: IDs, runs, decisões e feedback.
- Persistência: Ticket → TriageRun → TriageDecision → Feedback, com feedback append-only — a decisão original nunca é sobrescrita.
Decisões de engenharia
- O determinístico é o guardrail e o sinal de comparação; o LLM é o classificador semântico. Ambos recebem o mesmo ticket e não são tratados como estatisticamente independentes.
- Falhas conhecidas do LLM (timeout, indisponibilidade, resposta inválida) produzem decisão de fallback determinística, sempre com revisão humana. Falhas desconhecidas continuam propagando — degradação graciosa sem alegar equivalência entre os resultados.
- Segurança e operação desde o início: API key, limites de corpo e
concorrência, X-Request-Id, logs JSON redigidos,
/metrics,/health,/ready, graceful shutdown e reconciliação de runs abandonados.
Avaliação
A avaliação oficial é um benchmark held-out congelado: 70 tickets sintéticos,
execução única, sem ajuste de regras contra os erros do held-out. Parâmetros
congelados — commit f36e8ef8fcc70be09ab5ba9883efc7c34f3591a2, prompt
ollama-triage-v3, modelo qwen2.5:7b-instruct-q5_K_S, timeout de 120000ms.
A tabela oficial abaixo corresponde ao benchmark histórico do baseline
determinístico, Ollama e HybridPolicy. Uma avaliação posterior e isolada
adicionou Jev 1.13 como terceiro paradigma no mesmo held-out congelado; Jev
não foi integrado à HybridPolicy. Laya foi avaliado depois, primeiro zero-shot
e depois com domain adaptation, também fora do runtime.
O harness compara baseline determinístico, LLM standalone e híbrido em qualidade, recall de severidade, divergência, fallback, motivos de revisão e latência. Deliberadamente não emite: precisão/recall de human review (o held-out não tem ground truth independente para isso), suggested-team correctness, latência standalone do LLM nem latência separada de persistência/HTTP.
Resultados oficiais
| Métrica | Determinístico | LLM | Híbrido |
|---|---|---|---|
| Acurácia de categoria | 0.8286 | 0.9571 | 0.9571 |
| Macro-F1 de categoria | 0.8512 | 0.9550 | não emitido |
| Acurácia de prioridade | 0.9000 | 0.9143 | 0.9143 |
| Acurácia de risco | 0.9571 | 0.9143 | 0.9143 |
| Recall de prioridade HIGH/CRITICAL | 0.7857 | 1.0000 | 1.0000 |
| Recall de risco HIGH | 0.5714 | 0.7143 | 0.7143 |
| Acurácia exata da tupla | não emitido | não emitido | 0.8286 |
Operação no held-out: review rate de 0.5143 (motivos: severidade, divergência e confiança), divergência baseline/LLM de 0.3286, fallback 0, nenhuma falha de LLM, 70/70 decisões com origem híbrida. Latência híbrida: p50 de 6257.7ms, p95 de 7078.6ms, máximo de 7556.9ms.
Em um benchmark held-out congelado com 70 tickets, o LLM local aumentou a acurácia de categoria de 82,9% para 95,7% e o recall de prioridade HIGH/CRITICAL de 78,6% para 100%, enquanto o baseline determinístico manteve maior acurácia geral de risco (95,7% contra 91,4%). A política híbrida preservou a qualidade de classificação do LLM e encaminhou 51,4% dos casos para revisão humana com base em sinais de severidade, divergência e confiança.
Trade-offs
- O híbrido preserva a qualidade do LLM e paga por isso com revisão humana em mais da metade dos casos e latência de 6 a 7 segundos — adequado para triagem assíncrona, não para caminho síncrono crítico.
- O baseline venceu na acurácia geral de risco (95,71% contra 91,43%): o LLM não foi melhor em tudo, e a regressão está publicada acima.
- Um baseline adversarial em DEV (fora da distribuição) derrubou o determinístico para 0.5 de categoria, 0.5 de prioridade e 0.75 de risco — evidência de que regras explícitas são frágeis fora do vocabulário conhecido, o que justifica a combinação com o LLM em vez da troca pura.
Limites do benchmark híbrido histórico
Limites desta avaliação histórica:
- Dataset sintético, em inglês, com 70 exemplos.
- Apenas um modelo local avaliado.
- Uma única execução oficial — sem medida de variância do LLM.
- Confidence é heurística, não probabilidade calibrada.
- O held-out não possui ground truth independente para human review.
- Review rate não é precision/recall.
- Suggested-team correctness não foi emitido.
- Latência standalone do LLM não foi emitida.
- Latência de persistência/HTTP separada não foi emitida.
- O sistema não foi validado sob tráfego real de produção.
- O baseline teve risk accuracy geral superior.
- A latência híbrida ficou aproximadamente entre 6 e 7 segundos.
- O baseline adversarial em DEV mostrou category accuracy de 0.5, priority accuracy de 0.5 e risk accuracy de 0.75.
Experimento com Jev 1.13
Este experimento avaliou um terceiro paradigma para a mesma taxonomia:
regras determinísticas, LLM generativo e um modelo probabilístico tipado.
Foi uma avaliação isolada: Jev não foi integrado à HybridPolicy e não
substituiu o Ollama.
O adapter de TriageClassifier usou Bun native fetch e a OpenRouter Decisions
API com typesafe/jev-1.13; todas as 73 respostas reais resolveram para
typesafe/jev-1.13-20260917. Não foi adicionada dependência. Três chamadas DEV
ocorreram antes do freeze (1b6d7ff); o commit 4c41e0b congelou a
configuração e executou uma única avaliação held-out nos mesmos 70 labels
sintéticos usados na comparação histórica.
| Métrica | Determinístico | Ollama | Jev 1.13 |
|---|---|---|---|
| Acurácia de categoria | 0.8286 | 0.9571 | 1.0000 |
| Macro-F1 de categoria | 0.8512 | 0.9550 | 1.0000 |
| Acurácia de prioridade | 0.9000 | 0.9143 | 0.9857 |
| Acurácia de risco | 0.9571 | 0.9143 | 0.9571 |
| Recall de prioridade HIGH/CRITICAL | 0.7857 | 1.0000 | 1.0000 |
| Recall de risco HIGH | 0.5714 | 0.7143 | 0.8571 |
Jev standalone acertou a tupla categoria/prioridade/risco em 66/70 casos (0.9429). O benchmark histórico não emitiu essa métrica para os classificadores determinístico e Ollama standalone; a acurácia exata de 0.8286 publicada acima é do caminho híbrido histórico, que tem outro fluxo de decisão.
Nas 70 chamadas held-out do Jev, a latência standalone média foi 569.4 ms, p50 545.5 ms, p95 712.2 ms e máximo 1,142.2 ms. Foram reportados 90,229 tokens de entrada e custo de US$ 0.003789618; não houve falha de API ou schema. O benchmark Ollama histórico não emitiu latência standalone nem custo. A latência de 6–7 segundos daquele benchmark é do caminho híbrido e não é uma comparação equivalente com esta chamada Jev standalone.
Jev preservou distribuições de probabilidade, confidence contínua e margens top-1/top-2. Ainda assim, um risco HIGH classificado incorretamente teve confidence 0.86: confidence alta não garante correção semântica.
Automação seletiva e análise de calibração
A análise exploratória abaixo exige que as três confidence values atinjam o limiar. São resultados observados no mesmo dataset pequeno, não uma escolha de limiar para produção:
| Limiar | Cobertura | Acurácia exata na cobertura |
|---|---|---|
| 0.00 | 70/70 | 94.29% |
| 0.50 | 65/70 | 95.38% |
| 0.70 | 60/70 | 96.67% |
| 0.80 | 54/70 | 100% |
| 0.90 | 51/70 | 100% |
| 0.95 | 45/70 | 100% |
| 0.99 | 37/70 | 100% |
A análise exploratória de calibração mediu Brier de 0.0005 para categoria, 0.0386 para prioridade e 0.0800 para risco; ECE top-1 de 0.0054, 0.0329 e 0.0226, respectivamente. São estimativas descritivas sobre 70 tickets sintéticos, com bins esparsos; não demonstram calibração real deste domínio.
No mesmo held-out sintético congelado, Jev apresentou os melhores resultados
medidos em categoria e prioridade neste experimento, além de distribuições
tipadas e latência standalone abaixo de um segundo na média. O dataset pequeno
e sintético não estabelece superioridade em produção. A decisão arquitetural
permanece benchmark/evaluation only; não houve integração à HybridPolicy.
Laya: de zero-shot a domain adaptation
Depois de Jev, avaliei o checkpoint geral convaiinnovations/laya, um decision
model não-autoregressivo com código e pesos abertos sob Apache 2.0. Usei
laya==0.3.23 e a revision congelada no protocolo, mantendo o mesmo held-out.
Esse checkpoint base é distinto de laya-typed-decisions, já especializado em
outros workflows.
Zero-shot, Laya acertou 8/70 tuples (11,43%). O padrão mais claro foi 40/40 prioridades LOW previstas como MEDIUM. A auditoria das opções, índices, mapping, normalização e outputs parseados não encontrou bug de integração que invalidasse esse comportamento. A hipótese seguinte foi que o modelo geral precisava de adaptação à nossa taxonomia e domínio.
Protocolo de adaptação
Separei 1.120 tickets TRAIN para alterar os pesos e 280 VALIDATION para selecionar o checkpoint. Os 70 tickets HELD-OUT permaneceram fora da geração de dados, training, validation, seleção, tuning, thresholds e calibration. TRAIN e VALIDATION também são sintéticos; compartilham famílias de cenários, embora tenham textos distintos.
O fine-tuning partiu do mesmo modelo base. A seleção por VALIDATION escolheu a época 4 pela maior exact tuple, com desempates predefinidos. O Freeze 2 fixou dados, pesos, código e configuração antes da única avaliação oficial adaptada. O histórico segue: baseline → Ollama → HybridPolicy → Jev isolado → Laya zero-shot → adaptação → seleção → Freeze 2 → held-out adaptado.
Infraestrutura do treinamento
O host de inferência, com quatro vCPUs e cerca de 4 GB de RAM, não era adequado para esse treinamento. Usei uma GPU temporária na RunPod: NVIDIA RTX A5000, 24 GB de VRAM, Python 3.11, PyTorch 2.5.1 e CUDA 12.1. Foram quatro epochs com mixed precision (FP16 autocast / FP32 master), gradient accumulation e gradient checkpointing para controlar memória.
Na primeira execução, o scheduler avançava mesmo quando o GradScaler pulava
um optimizer update. Preservei os artifacts, rejeitei a execução por validade
metodológica e corrigi a coordenação antes de repetir o treino e abrir o
held-out adaptado. A execução oficial registrou 420 tentativas, 413 updates
aplicados, sete skips e 413 scheduler steps. Sua escolha não dependeu de uma
accuracy mais favorável.
O checkpoint .safetensors foi preservado localmente fora do Pod; SHA-256,
tamanho, seleção e configuração estão nos artifacts públicos. Os pesos
adaptados não foram publicados. Esse ciclo fornece minha primeira evidência
prática em ML Systems / AI Infrastructure, delimitada a treinamento,
checkpoints e avaliação experimental.
A avaliação final rodou em CPU, sem a GPU: média de 1.619 ms por ticket, contra 1.414 ms no zero-shot. Jev havia registrado 569 ms via API/rede em outro contexto. Local não significou automaticamente menor latência nessa CPU; os caminhos não permitem uma comparação equivalente de velocidade.
As tarefas GPU medidas somaram 1.523,51 segundos. À taxa registrada de aproximadamente US$ 0,28/h com disco, US$ 0,1185 é uma estimativa dessas tarefas, excluindo setup e tempo ocioso, e não a cobrança total do Pod. Laya teve US$ 0 de cobrança externa por token/request, com infraestrutura e operação ainda sob nossa responsabilidade. O relatório não registra teardown: o Pod não havia sido destruído ao finalizar a avaliação.
Resultado e regressão de severidade
Acurácia por campo e da tupla, no mesmo held-out:
| Métrica | Zero-shot | Adaptado |
|---|---|---|
| Categoria | 84,29% | 97,14% |
| Prioridade | 31,43% | 94,29% |
| Risco | 62,86% | 92,86% |
| Exact tuple | 11,43% | 85,71% |
As tuples corretas passaram de 8/70 para 60/70, e LOW → MEDIUM caiu de 40/40 para 0/40. O recall de risco HIGH subiu de 57,14% para 100% (4/7 → 7/7), mas o de prioridade HIGH/CRITICAL regrediu de 92,86% para 85,71% (13/14 → 12/14). A melhora agregada não eliminou os erros de severidade.
Jev havia acertado 66/70 tuples (94,29%) sem receber nossos exemplos TRAIN; Laya adaptado recebeu 1.120. A comparação é assimétrica: Jev mostrou forte generalização out-of-the-box neste benchmark, enquanto Laya mostrou o controle oferecido pelos pesos abertos para adaptar o comportamento ao domínio.
Confidence e limites de generalização
A confidence do Laya adaptado ficou próxima de 1, mas 10/70 tuples ainda estavam erradas. Thresholds até 0,90 mantiveram todos os tickets; 0,95 e 0,99 removeram apenas um, com exact tuple ainda abaixo de 90%. Confidence alta não se tornou uma estimativa confiável de correção, e sua semântica não é idêntica à confidence reportada por Jev.
Exact tuple foi 100% em TRAIN → 92,50% em VALIDATION → 85,71% no HELD-OUT. O fit forte e a confidence saturada são sinais compatíveis com memorização e overconfidence; a amostra pequena não determina a gravidade desse efeito.
As avaliações usam inglês, um domínio específico e dados sintéticos, com uma execução oficial por protocolo e nenhuma validação em tráfego real. Jev e Laya continuam evaluation-only, fora da HybridPolicy e do runtime. O experimento exercitou avaliação e adaptação, sem deployment de um serviço adaptado em produção.
O artigo sobre Jev detalha a avaliação inicial; Laya vs Jev explica os protocolos, calibration e o custo do controle. O
relatório final de adaptação
reúne as métricas e aponta os artifacts para auditoria.
Evidências
- Repositório: ops-triage-ai no GitHub
- Relatório oficial do held-out: official-held-out-f36e8ef.md
- Artefato legível por máquina: official-held-out-f36e8ef.json
- Relatório do experimento Jev 1.13: jev-1.13-held-out-4c41e0b.md
- Artefato do held-out Jev: jev-1.13-held-out-4c41e0b.json