Pular para o conteúdo
Case Agents

P2 · Engenharia de Confiança · Arquitetura Produtiva & Roteamento Determinístico

A tool errada não é uma aproximação aceitável

285 ferramentas, 30 queries, um domínio bancário de missão crítica. Como uma barreira pré-execução de quatro camadas levou 7 execuções incorretas a zero — e como a solução evoluiu de uma prova de conceito (PoC) para um blueprint de arquitetura produtiva de alta performance com desacoplamento Cold/Hot Path e SLAs sub-2ms.

Medido no benchmark do MVP · 30 queries · 53 exemplos de treino · tools mockadas · offline

100% Accuracy do Router · benchmark
100% Hit Rate@2 · benchmark
0 Execuções Incorretas · 20 queries
77,8% Economia de LLM · benchmark
72 Testes · 0 skips

Saída determinística de python -m candidate_starter.run_case medida no benchmark do MVP — não é precisão do sistema em produção nem economia financeira total. A capacidade é escolher a ferramenta certa; a confiança é saber quando não executar nenhuma.

Em uma frase
Um roteador para agente bancário que escolhe 2 ferramentas entre 285 e bloqueia a execução quando a decisão não é confiável o bastante. A capacidade é escolher a ferramenta certa; a confiança é saber quando não executar nenhuma.
Formato
Case técnico
Profundidade
Leitura aprofundada · ~3 min para a ideia central · ~31 min completo

Prefere o essencial antes? Ler a síntese (~1 min)

Resumo em vídeo

O case em um vídeo

"A Máquina da Confiança: Estudo de Caso em Desenvolvimento Agêntico Bancário" — visão geral antes do aprofundamento técnico: o problema, o Crash Silencioso, a barreira de quatro guardas e o que os números provam — e o que não provam.

01 · O Enunciado

O projeto: o que foi pedido num cérebro de agente bancário

Construir o cérebro de roteamento de um agente de atendimento de um banco digital. Antes de qualquer chamada a LLM caro, o sistema decide o caminho mais barato e seguro para responder cada mensagem.

Os três pilares da arquitetura

Pilar Problema de Engenharia Componente no Código
1 · Router Classificar a query em FAST_PATH (saudação/FAQ) ou AGENT (precisa de tool + raciocínio). candidate_starter/router.py
2 · Tool Retrieval Selecionar as 2 ferramentas mais relevantes entre 285 antes de montar o prompt do LLM. candidate_starter/retrieval.py
3 · Evaluation Harness Medir qualidade, custo, latência e segurança de forma determinística offline. candidate_starter/harness.py

A restrição inegociável do domínio

"Este é um domínio bancário. Uma tool incorreta não é uma aproximação aceitável. Nunca execute uma tool se a decisão estiver abaixo dos limiares de segurança. Nunca remova os quality gates apenas para aumentar recall ou economia. Não use expected_tool do dataset dentro da lógica de produção. Esse campo só pode ser usado pelo harness para avaliar offline."

A Fronteira: produção não vê o gabarito

O que separa engenharia de ajuste ao gabarito é uma linha só: expected_tool vive na camada de avaliação e nunca na lógica de produção.

Lógica de produção router · retrieval · taxonomy

Decide com sinais disponíveis no momento do pedido: confiança do router, score do retriever, margem relativa e o verbo da query. Nada mais.

Camada de avaliação harness.py

expected_tool do eval_dataset.json só é lido aqui, offline, para pontuar o Hit Rate depois que a decisão já foi tomada.

Prova da Fronteira verificação léxica estática
grep -n "expected_tool" candidate_starter/retrieval.py candidate_starter/router.py candidate_starter/taxonomy.py
# Saída: zero resultados.

02 · Ciclo de Vida & Decisão de Engenharia

Duas Estratégias, Um Domínio: Agilidade de Prototipagem vs Robustez Enterprise

Em engenharia de software sênior, uma solução técnica não existe no vácuo. O repositório oficial case-agents implementa e documenta duas abordagens arquiteturais ativas em branches paralelas, demonstrando a transição deliberada de uma Prova de Conceito (PoC) exploratória para um sistema de governança de missão crítica.

Abordagem 1: Solução Enxuta (Lean PoC)

Branch: feature/solucao-enxuta
Agilidade & MVP

Foco estrito em velocidade de experimentação e footprint mínimo. Implementa o roteador e o retriever em Python direto: calibração Platt, colapso por capacidade e guarda de direção existem, mas embutidos em router.py e retrieval.py, sem módulo de taxonomia e sem ADRs. Ideal para validação rápida de viabilidade de produto e benchmark de partida.

Cobertura de Testes 15 testes unitários
Governança de Catálogo Dicionário embutido (12 capacidades)
Calibração do Router Platt Scaling (cv=3)
Guarda Direcional Parcial (leitura→escrita, por prefixo)

Prós & Trade-offs:

  • Pró: Código enxuto (~560 linhas em quatro módulos e um script), fácil de auditar e entregar em poucos dias.
  • Pró: Baixo atrito cognitivo para times em estágio inicial de produto.
  • Contra: O harness da branch imprime 26,3% de Precision@2, porque compara o nome da variante com o nome canônico; recontado por capacidade, são 20/20 no top-2 (ver Estatística no Código).
  • Contra: 16,7% de abstenções (5 de 30), e a guarda de direção só cobre leitura→escrita nas ferramentas que o prefixo do nome revela (167 das 285 ferramentas).

Abordagem 2: SDD Enterprise & Confiança

Branch: main (antiga solucao-sdd-vibe)
Produção & Escala

Foco em confiabilidade bancária, conformidade e ciclo de vida industrial. Adota Spec-Driven Development (SDD), 9 ADRs formais, taxonomia canônica de capacidades, calibração Platt Scaling, barreira de 4 camadas (incluindo Guarda de Direção) e o blueprint para inferência compilada de alta performance.

Cobertura de Testes 72 testes / 0 skips
Governança de Catálogo 14 capacidades + colapso
Calibração do Router Platt Scaling Sigmoid
Guarda Direcional G4 Ativa (Read != Write)

Prós & Trade-offs:

  • Pró: 100% de Hit Rate@2 e zero execuções incorretas garantidas formalmente.
  • Pró: Proteção comprovada contra danos ao cadastro bancário do cliente (G4).
  • Pró: Pronta para escala industrial: manifesto APM.yml e ADR-009 (Rust/Go).
  • Contra: Exige disciplina contínua de governança do catálogo e testes de paridade.

A lição da dualidade: Na fase de validação (PoC), a Solução Enxuta é tentadora pela velocidade. Contudo, em finanças ou saúde, o custo de uma execução incorreta em produção supera infinitamente o custo da governança. A branch main existe para demonstrar como transpor essa barreira com rigor de engenharia.

03 · A Falha Medida

O Crash Silencioso: a falha que 54 testes não pegaram

Numa iteração intermediária (commit c8f30d2), o projeto exibia: 54 testes no verde, router 100%, Hit Rate@2 de 100%, zero execuções incorretas e status aprovado com louvor. Uma revisão externa, porém, solicitou um teste de colisão entre vocabulário de leitura e escrita.

Query do Cliente: "Qual e o email cadastrado na minha conta?" — uma LEITURA.

✕ Antes — 3 guardas Executa a escrita
Top-1 · atualizar_email — ESCRITA 0.4795
Top-2 · confirmar_email_cadastrado 0.2554

Margem (0.4795 − 0.2554) / 0.4795 = 0.467 >> 0.25 → a guarda de margem não pega. Confiança alta, score alto, margem folgada: EXECUÇÃO INCORRETA — o agente altera o cadastro de quem só perguntou.

✓ Depois — guarda de direção (G4) Executa a leitura
Top-1 · consultar_email_vinculado_conta — LEITURA 0.3891
atualizar_email — ESCRITA, DESCARTADA —

A query é de leitura; a capacidade de escrita é descartada antes do ranking. Margem (0.3891 − 0.1792) / 0.3891 = 0.539 > 0.25 → executa a consulta, corte seco, sem transição.

Nenhuma das três guardas antigas pegou: o erro não é um empate — é uma decisão confiante e errada. Isso ilustra a tese central do pilar Engenharia de Confiança: o erro mais perigoso em IA não gera exceção de código. Ele passa no verde e toma uma decisão ruinosa com margem folgada.

04 · Diagnóstico Estrutural

Por que nenhum peso de vetorização resolve o problema

Quando o problema apareceu, a primeira tentativa foi ajustar a combinação de pesos entre busca lexical e busca por intenção (score = (1−α)·lexical + α·intent). A demonstração matemática abaixo prova por que isso é impossível:

Análise Aritmética Query de escrita vs Ferramenta de leitura
Query: "Quero mudar o e-mail vinculado a minha conta" (ESCRITA)

consultar_email_vinculado_conta  -> lexical = 0.8457 | intent = 0.4400
atualizar_email                  -> lexical = 0.1983 | intent = 0.3200

Para a escrita vencer por ajuste de α:
(1 - α)·0.1983 + α·0.3200 > (1 - α)·0.8457 + α·0.4400
α·(0.3200 - 0.4400 - 0.1983 + 0.8457) > 0.8457 - 0.1983
0.5274·α > 0.6474  =>  α > 1.227 (IMPOSSÍVEL, pois α ∈ [0, 1])

A lição estrutural: Leitura e escrita sobre o mesmo objeto compartilham todos os substantivos (e-mail, conta, cadastro). A diferença entre consultar e alterar é uma só palavra: o verbo. Num saco de palavras com TF-IDF, esse verbo é diluído entre dez tokens. A direção da operação não é recuperável por similaridade textual — precisa ser uma restrição semântica declarada.

05 · A Solução Rígida

A Barreira Pré-Execução de 4 Camadas

Quatro guardas determinísticas encadeadas. A query só avança para o LLM e para a execução de tools se passar por todas as quatro travas. Se qualquer guarda reprovar, o sistema desvia para abstenção ou atendimento humano.

G1

Confiança de Roteamento

Confiança calibrada do Router < 0.75? Se reprovar, o retriever nem é invocado.

HUMAN_FALLBACK_LOW_CONFIDENCE
G2

Score Mínimo do Candidato

Nenhum candidato no retriever atinge o score lexical/intent mínimo de 0.10?

ABSTAIN_LOW_SCORE
G3

Margem Relativa entre Candidatos

Margem (s₁ − s₂)/s₁ < 0.25? Top-1 e top-2 estão próximos demais (empate técnico).

AMBIGUOUS_CONFIRMATION
G4

Guarda de Direção (Leitura vs Escrita)

Direção extraída do verbo da query é diferente da direção (mode: read|write) declarada na ferramenta?

Descarte (Leitura→Escrita) ou Rebaixamento

A assimetria deliberada da Guarda 4

  • Query de LEITURA + Tool de ESCRITA: Descarte total da tool. Alterar o cadastro de quem apenas perguntou é dano irreversível ao cliente.
  • Query de ESCRITA + Tool de LEITURA: Rebaixamento de score, não descarte. Consultar para quem pediu alteração é tarefa não cumprida — recuperável na mensagem seguinte.

06 · O Pipeline do MVP

A Arquitetura em 4 Camadas (Validação Matemática em Python)

O fluxo completo do pedido até a autorização de execução no motor de validação:

  1. Query do usuário
  2. Router — TF-IDF + LogisticRegression + Platt scaling FAST_PATH → resposta local, custo zero de LLM AGENT segue abaixo
  3. Taxonomia — governança do catálogo: capacidade ≠ endpoint
  4. Retriever — top-k capacidades distintas
  5. Barreira — 4 guardas em ordem: G1 · G2 · G3 · G4 falha em qualquer guarda → abstenção ou atendimento humano
  6. LLM — chamado só depois que a barreira autorizou
  7. Execução da ferramenta
Router, taxonomia, retriever e guardas são determinísticos; o LLM entra apenas depois da autorização.
1 · Calibração do Router router.py

TfidfVectorizer(ngram_range=(1,2)) + LogisticRegression calibrado via CalibratedClassifierCV(method="sigmoid") em Platt scaling. Resolve o problema de subconfiança por regularização L2 em dataset pequeno sem inflar artificialmente as probabilidades.

2 · Taxonomia & Governança taxonomy.py

Declaração de 14 capacidades canônicas e 45 variantes com modo de operação (read ou write). Mapeia o vocabulário do usuário (aliases) para intenções e resolve duplicações semânticas do catálogo.

3 · Retriever Multi-campo retrieval.py

Combinação de score BM25F: score = 0.5·lexical + 0.5·intent. O glossário de aliases é indexado em campo separado para evitar que a normalização L2 dilua os termos originais. Realiza colapso por capacidade devolvendo top-k canônicos distintos.

4 · Barreira & Harness harness.py

Quality Gate tri-estado (APROVADO, REPROVADO, INDETERMINADO). Valida as 4 guardas antes do mock de execução. Se não houver query transacional no benchmark, o resultado é marcado como INDETERMINADO por segurança.

07 · Estatística no Código

As cinco técnicas estatísticas do case e o Python que as implementa

Quem decide, neste pipeline, não é um modelo de linguagem. São cinco técnicas clássicas de estatística e de recuperação de informação, cada uma respondendo a um problema concreto do atendimento bancário. Cada técnica abaixo tem duas camadas: o conceito, em linguagem simples, e o trecho de Python que o implementa.

Os trechos são da branch feature/solucao-enxuta (commit e2dcd7f), a versão simplificada: quatro módulos e um script de execução, cerca de 560 linhas, sem taxonomy.py e sem ADRs. Onde a main faz diferente, uma nota avisa. Os números vêm da saída de python -m candidate_starter.run_case nessa branch e de recontagens offline sobre o mesmo relatório. Não são os números da main citados no resto da página.

Técnica Problema operacional Onde no código
1 · TF-IDF + regressão logística Decidir, barato e em milissegundos, se a mensagem precisa de agente. router.py · QueryRouter
2 · Platt scaling + validação cruzada estratificada Fazer o número "0,75" significar uma probabilidade de acerto. router.py · fit()
3 · Cosseno multi-campo + colapso por capacidade Achar 2 ferramentas entre 285, sem devolver duas duplicatas. retrieval.py · ToolRetriever.search
4 · Limiares, margem relativa e guarda de direção Saber quando não executar. harness.py · run_harness; retrieval.py
5 · Métricas de avaliação e economia Provar que funciona sem contar como ganho o que é só abstenção. harness.py · compute_*

1 · Classificação supervisionada: TF-IDF e regressão logística

Problema operacional. Saudações e perguntas de FAQ ("Qual o horário de atendimento?") não precisam de LLM nem de ferramenta. Responder isso localmente tem custo zero de LLM. O router precisa separar FAST_PATH de AGENT em milissegundos, antes de qualquer chamada cara.

Conceito. Classificação supervisionada é ensinar com exemplos já rotulados: aqui, 53 mensagens marcadas FAST_PATH (25) ou AGENT (28). O modelo aprende o que separa as duas classes e aplica isso a mensagens novas, em dois passos: transformar texto em números e aprender uma fronteira entre as classes.

O TF-IDF dá a cada termo um peso que cresce com a frequência dele na mensagem (TF) e diminui com o número de mensagens do conjunto que o contêm (IDF). "De" aparece em quase tudo e pesa pouco; "bloquear" aparece em poucas e pesa muito. Com n-grams de 1 a 2 termos, pares como "segunda via" também viram termos. Cada mensagem vira um vetor esparso (quase tudo zero) de comprimento 1.

A regressão logística transforma uma soma ponderada dos termos em uma probabilidade, entre 0 e 1, de a mensagem ser AGENT. A regularização L2 penaliza pesos grandes, para o modelo não decorar 53 exemplos; C controla a força (1,0 é o padrão do scikit-learn e é o valor usado). É um classificador linear e leve, e cada termo tem um coeficiente que dá para ler, o que importa numa auditoria.

Fórmulas o que o scikit-learn calcula por baixo
tf-idf(t, d) = tf(t, d) × idf(t)          idf(t) = ln((1 + n) / (1 + df(t))) + 1
vetor(d)     = tf-idf(d) / ‖tf-idf(d)‖₂             (norma L2 = 1)
P(AGENT | x) = σ(w·x + b) = 1 / (1 + e^−(w·x + b))
o treino minimiza:  ½‖w‖² + C · Σ log-loss(yᵢ, P(xᵢ))       (C = 1,0)
router.py linhas 18–23 ↗
self._vectorizer = TfidfVectorizer(
    ngram_range=(1, 2),
    min_df=1,
    preprocessor=normalize,
)
self._base_clf = LogisticRegression(C=1.0, max_iter=1000, random_state=42)
normalization.py linhas 22–42 ↗
def normalize(text: str) -> str:
    if not text:
        return ""

    # 1. Lowercase
    text = text.lower()

    # 2. Remover acentos
    text = remove_accents(text)

    # 3. Variantes ortográficas do domínio (antes de remover pontuação se houver hífens)
    for pattern, replacement in DOMAIN_VARIANTS:
        text = re.sub(pattern, replacement, text)

    # 4. Remover caracteres especiais (manter apenas letras, números e espaços)
    text = re.sub(r"[^a-z0-9\s]", " ", text)

    # 5. Colapsar múltiplos espaços
    text = re.sub(r"\s+", " ", text).strip()

    return text

normalize() é o preprocessor do vetorizador, então o mesmo tratamento (minúsculas, sem acento, variantes de domínio como "segunda via" → segundavia, sem pontuação) vale no treino e na consulta. Se os dois fossem normalizados de jeitos diferentes, o vocabulário aprendido não casaria com a pergunta real.

Medido nesta branch:

  • Vocabulário de 378 termos (unigramas e bigramas) sobre os 53 exemplos de treino.
  • Router 30/30 no benchmark; matriz de confusão FAST_PATH 10 · 0 / AGENT 0 · 20.
  • predict(), já com a calibração da técnica 2: mediana de 2,2 ms e p95 de 4,4 ms em 200 chamadas locais (Windows, scikit-learn 1.9).

2 · Calibração de probabilidades: Platt scaling e validação cruzada estratificada

Problema operacional. O limiar de 0,75 da guarda G1 (seção A Barreira) só é uma política de risco se "0,75" significar "o modelo acerta em cerca de 75% dos casos". Uma regressão logística regularizada, com 53 exemplos e 378 termos, encolhe os pesos e devolve probabilidades tímidas, perto de 0,5, mesmo quando a classe está certa. Sem calibração, nesta branch, 20 das 20 mensagens AGENT do benchmark ficam abaixo de 0,75 (a maior confiança é 0,713), embora o router acerte todas. O limiar deixaria de separar decisão boa de decisão ruim e só descartaria cobertura.

Conceito. Calibrar é corrigir a escala do número sem mudar o que o modelo decide. O escalonamento de Platt treina uma segunda regressão logística, de uma única variável, sobre o score bruto f do modelo. Os parâmetros A e B são estimados por máxima verossimilhança em previsões feitas sobre exemplos que o modelo não viu no treino (out-of-fold: previsões sobre a parte dos dados deixada de fora em cada rodada). Estimá-los nos próprios exemplos de treino mediria a memória do modelo, não a capacidade de generalizar.

A validação cruzada estratificada divide os 53 exemplos em 3 dobras que preservam a proporção de classes; cada dobra serve uma vez de conjunto não visto. Sem embaralhar (shuffle=False), as dobras são as mesmas em qualquer máquina, e os testes são reproduzíveis. O código não chama StratifiedKFold diretamente: passar cv=3 a um classificador faz o scikit-learn usar esse divisor (conferido na versão 1.9). O número de dobras vem da menor classe: 3 se ela tem ao menos 3 exemplos, 2 se tem 2, e sem calibração abaixo disso, para o fit() não quebrar em conjuntos minúsculos de teste.

Por que sigmoide e não isotônica. A calibração isotônica é não paramétrica: com poucos dados ela sobreajusta e satura a confiança perto de 1, o problema oposto. A sigmoide tem dois parâmetros e é estável. A main mediu essa comparação no ADR-007.

Fórmulas escalonamento de Platt e dobras
Platt:  p(AGENT | f) = 1 / (1 + exp(A·f + B))      A, B por máxima verossimilhança, em dados fora da dobra
Dobras: StratifiedKFold(n_splits=3, shuffle=False)   → 3 modelos calibrados; a probabilidade final é a média
router.py linhas 31–47 ↗
# Contar menor número de amostras por classe
unique_labels, counts = np.unique(labels, return_counts=True)
min_class_count = int(np.min(counts))

if min_class_count >= 3:
    cv = 3
elif min_class_count >= 2:
    cv = 2
else:
    cv = None

if cv is not None:
    self._clf = CalibratedClassifierCV(estimator=self._base_clf, method="sigmoid", cv=cv)
else:
    self._clf = self._base_clf

self._clf.fit(X, labels)
router.py linhas 59–63 ↗
X_q = self._vectorizer.transform([query])
probs = self._clf.predict_proba(X_q)[0]
best_idx = np.argmax(probs)
route = str(self.classes_[best_idx])
confidence = float(probs[best_idx])

Medido nesta branch:

  • Sem calibração, 20 de 20 mensagens AGENT ficam abaixo de 0,75. Com calibração, 1 de 20 (0,732).
  • No benchmark inteiro, G1 barrou 2 mensagens: "Troquei de número, preciso atualizar o telefone cadastrado" (AGENT, 0,732) e "Quais documentos preciso para abrir uma conta?" (FAST_PATH, 0,718). Nesta branch, G1 vale para as duas rotas.
  • A menor confiança aceita é 0,753: folga de 0,003 sobre o limiar. A calibração corrigiu a escala, mas a margem continua estreita.
  • O repositório não mede a calibração em si (Brier score, erro esperado de calibração, curva de confiabilidade), só o efeito dela no limiar. Com 20 queries AGENT, uma curva de confiabilidade teria poucos pontos por faixa.

3 · Recuperação vetorial: similaridade de cosseno, score multi-campo e colapso por capacidade

Problema operacional. O catálogo tem 285 ferramentas. Pôr todas no prompt de um LLM estoura o contexto, confunde o modelo e custa caro, então o retriever escolhe as 2 mais relevantes antes de qualquer chamada. E o catálogo tem duplicatas: 8 ferramentas diferentes fazem "obter a fatura".

Conceito. A similaridade de cosseno mede o ângulo entre dois vetores: 1 quando têm o mesmo vocabulário ponderado, 0 quando não compartilham nenhum termo. Como o TF-IDF já entrega vetores de comprimento 1, o cosseno é só o produto escalar, ou seja, uma multiplicação de matriz esparsa entre a query e as 285 ferramentas.

O score combina dois campos com peso igual. O documento da ferramenta é nome + descrição + categoria; o campo de "intenção" é nome + descrição. Manter campos separados, em vez de concatenar tudo, existe para que a normalização L2 não dilua os termos originais quando se acrescentam sinônimos ao mesmo texto (o ADR-006 da main documenta esse problema). Uma lista de palavras funcionais (a, de, para, quero…) sai do vocabulário antes, para uma query curta não pontuar só por preposições.

Um limite desta branch. Os dois campos saem do mesmo texto: diferem só pela categoria. Medido, a correlação entre as duas similaridades é de pelo menos 0,97, e o top-1 do score combinado é idêntico ao do campo único nas 30 queries. Aqui, α = 0,5 praticamente não muda o resultado. Um segundo campo só ajuda quando traz informação nova, como o glossário escrito à mão da main.

O colapso por capacidade agrupa as duplicatas com um dicionário embutido (CANONICAL_TOOL_MAP: 49 ferramentas em 12 capacidades) e guarda, por grupo, o maior score (max-pooling, uma agregação por máximo). Assim k=2 devolve duas capacidades distintas, não duas variantes da mesma. Depois, o código ordena por score decrescente e corta em k. Candidatos com score 0 são descartados: uma query sem nenhuma palavra em comum com o catálogo devolve lista vazia. Empates seguem a ordem do catálogo, porque o sorted do Python é estável. Isso é determinístico para o mesmo arquivo, mas não é um desempate explícito (a main desempata pelo nome da capacidade).

Fórmulas cosseno, score e colapso
cos(q, d)          = (q · d) / (‖q‖ · ‖d‖) = q · d          (vetores TF-IDF com ‖v‖₂ = 1)
score(ferramenta)  = (1 − α) · cos(q, documento) + α · cos(q, intenção)        α = 0,5
score(capacidade)  = max { score(t) : t pertence à capacidade }                (colapso por máximo)
doc_texts = []
intent_texts = []

for tool in tools:
    # Document text: name + description + category
    doc_text = f"{tool.name} {tool.description} {tool.category}"
    doc_texts.append(doc_text)

    # Intent text: name.replace("_", " ") + description
    intent_text = f"{tool.name.replace('_', ' ')} {tool.description}"
    intent_texts.append(intent_text)

self._doc_matrix = self._doc_vectorizer.fit_transform(doc_texts)
self._intent_matrix = self._intent_vectorizer.fit_transform(intent_texts)
doc_sims = cosine_similarity(q_doc_vec, self._doc_matrix)[0]
intent_sims = cosine_similarity(q_intent_vec, self._intent_matrix)[0]

combined_scores = (1 - self.alpha) * doc_sims + self.alpha * intent_sims

# Apply direction guard & canonical collapsing
canonical_best: Dict[str, Tuple[str, float]] = {}

for idx, tool in enumerate(self._tools):
    score = float(combined_scores[idx])
    # … (guarda de direção: técnica 4)
    if score <= 0:
        continue

    canon_group = get_canonical_name(tool.name)
    if canon_group not in canonical_best or score > canonical_best[canon_group][1]:
        canonical_best[canon_group] = (tool.name, score)

# Sort matches by score descending
sorted_matches = sorted(canonical_best.values(), key=lambda item: item[1], reverse=True)
top_k_matches = [ToolMatch(name=name, score=score) for name, score in sorted_matches[:k]]

Medido nesta branch: recontando por capacidade (get_canonical_name sobre o resultado), a capacidade esperada está no top-1 em 19 das 20 queries transacionais e no top-2 em 20 das 20. O relatório impresso mostra outro número. A explicação está na técnica 5.

4 · Decisão sob incerteza: limiares, margem relativa e guarda de direção

Problema operacional. Escolher a melhor ferramenta não basta: o sistema precisa saber quando não executar. Um erro que passa em silêncio (ver O Crash Silencioso) custa mais do que uma pergunta de confirmação.

Conceito. Em vez de agir sempre sobre o melhor candidato, o pipeline testa três sinais estatísticos, em ordem, e só executa se todos passarem. Nenhum deles lê o expected_tool do gabarito.

  • G1 · confiança: a probabilidade calibrada do router é ≥ 0,75.
  • G2 · relevância mínima: o score do top-1 é ≥ 0,10, um piso contra casamentos por acaso.
  • G3 · margem relativa: (s₁ − s₂) / s₁ ≥ 0,25. Mede quanto o primeiro candidato vence o segundo em proporção do próprio score. É relativa para não depender da escala: uma diferença de 0,03 pesa diferente quando o top-1 vale 0,18 e quando vale 0,80.
# Guard Rail 1: Router confidence >= 0.75
confidence = route_result.confidence if route_result.confidence is not None else 1.0
if confidence < 0.75:
    row["final_status"] = "HUMAN_FALLBACK_LOW_CONFIDENCE"
    abstentions += 1
elif route_result.route == "FAST_PATH":
    fast_path_answer(query)
else:
    retrieval_result = retriever.search(query, k=k)
    # …
    # Guard Rail 2: Top-1 score >= 0.10
    top1_score = top_k_matches[0].score if top_k_matches else 0.0
    if top1_score < 0.10:
        row["final_status"] = "ABSTAIN_LOW_SCORE"
        abstentions += 1
    else:
        # Guard Rail 3: Margin relative >= 0.25
        top2_score = top_k_matches[1].score if len(top_k_matches) > 1 else 0.0
        margin = (top1_score - top2_score) / top1_score if top1_score > 0 else 0.0
        if margin < 0.25:
            row["final_status"] = "AMBIGUOUS_CONFIRMATION"
            abstentions += 1
        else:
            # Executa a tool top-1
            mock_tool_execution(top_k_names[0], query)

A margem em três queries reais, todas desviadas por G3 no benchmark:

Query Top-1 e top-2 (score) Margem Resultado
"Perdi meu cartão na rua, preciso bloquear agora" bloquear_cartao 0,180 · bloquear_cartao_temporariamente 0,177 0,017 AMBIGUOUS_CONFIRMATION
"Mudei de endereço, como atualizo minha entrega?" alterar_endereco 0,474 · consultar_endereco_cadastrado 0,434 0,084 AMBIGUOUS_CONFIRMATION
"Chegou meu cartão novo, como faço para desbloquear?" instrucoes_desbloqueio_cartao_novo 0,546 · confirmar_recebimento_cartao_novo 0,420 0,231 AMBIGUOUS_CONFIRMATION

A terceira falha por 0,019: 0,25 é uma política, não uma constante da natureza. Nesta branch os três limiares são literais no código; na main são constantes nomeadas, e a margem é parâmetro do harness.

G4 · guarda de direção. Ela não é uma medida de similaridade, e o motivo é estatístico: leitura e escrita sobre o mesmo dado dividem quase todos os termos (a seção Por que nenhum peso resolve mostra a aritmética). Então a direção entra como sinal declarado: verbos e palavras-chave da query dizem se ela é de leitura ou de escrita, e o prefixo do nome da ferramenta (consultar_, atualizar_…) diz de que tipo ela é. Query de leitura com ferramenta de escrita: score zerado.

retrieval.py linhas 119–137 ↗ e 207–209 ↗
def infer_query_direction(query_norm: str) -> str:
    """Inferir direção da query: 'read', 'write', ou 'unknown'."""
    has_read = any(re.search(rf"\b{kw}\b", query_norm) for kw in READ_QUERY_KEYWORDS)
    has_write = any(re.search(rf"\b{kw}\b", query_norm) for kw in WRITE_QUERY_KEYWORDS)

    if has_read and not has_write:
        return "read"
    if has_write and not has_read:
        return "write"
    return "unknown"


def infer_tool_direction(tool_name: str) -> str:
    """Inferir direção da ferramenta pelo prefixo."""
    if tool_name.startswith(READ_TOOL_PREFIXES):
        return "read"
    if tool_name.startswith(WRITE_TOOL_PREFIXES):
        return "write"
    return "unknown"

# …
# Direction guard rule: read query + write tool -> score = 0
if query_direction == "read" and tool_direction == "write":
    score = 0.0

Medido e limitado nesta branch:

  • Leitura protegida. "Qual e o email cadastrado na minha conta?" resolve confirmar_email_cadastrado (leitura) em primeiro. atualizar_email não aparece entre os três primeiros.
  • Escrita→leitura não é tratada. A especificação enxuta previa rebaixar a ferramenta de leitura, mas o código não faz nada nesse sentido. "Quero mudar o e-mail vinculado à minha conta" executa consultar_email_vinculado_conta (score 0,664): não altera nada, mas não cumpre o pedido.
  • Só enxerga o que o nome revela. Por prefixo, 82 das 285 ferramentas são de leitura, 85 são de escrita e 118 ferramentas (41%) não revelam direção pelo nome e passam sem restrição.
  • Leitura agrupada sob escrita. O dicionário de colapso põe consultar_email_vinculado_conta e confirmar_email_cadastrado, ferramentas de leitura, sob atualizar_email, que é de escrita. A main proíbe isso por invariante: leitura e escrita não se agrupam.

5 · Métricas de avaliação e economia

Problema operacional. Sem medição, "funciona" é opinião. E uma métrica mal lida é pior que nenhuma: a economia de custo sobe quando o sistema se abstém mais, mesmo que atenda menos.

Conceito. A acurácia é a proporção de acertos: acertos divididos pelo total. A matriz de confusão 2×2 separa os dois erros possíveis, que custam coisas diferentes: mandar uma FAQ ao agente gasta LLM à toa; mandar um pedido transacional ao FAST_PATH dá ao cliente uma resposta genérica no lugar da operação. O código inicializa todas as células com zero, então a estrutura fica completa mesmo sem erros cruzados.

O hit rate@k é a fração das queries em que a ferramenta esperada aparece entre as k primeiras. Não é a Precision@K clássica (relevantes entre as k, divididos por k): com uma só ferramenta relevante e k = 2, a Precision@2 máxima seria 0,5. A função se chama compute_precision_at_k, mas o que ela calcula é hit rate.

A economia é a redução percentual contra o baseline (mandar toda mensagem ao LLM, com todas as ferramentas no prompt), com guarda para baseline zero. No MVP, custos e latências vêm de constantes de mock (US$ 0,03 por chamada do baseline, US$ 0,01 por chamada do agente), então o percentual mede a estrutura do pipeline, não um preço real.

Fórmulas métricas do harness
acurácia     = acertos / total
hit rate@k   = (queries com a ferramenta esperada no top-k) / (queries avaliadas)
economia %   = (baseline − pipeline) / baseline × 100            (0 se baseline = 0)
harness.py linhas 22–28 ↗, 36–40 ↗ e 52–54 ↗
correct = sum(1 for yt, yp in zip(y_true, y_pred) if yt == yp)
accuracy = correct / len(y_true)

matrix = {l1: {l2: 0 for l2 in labels} for l1 in labels}
for yt, yp in zip(y_true, y_pred):
    if yt in matrix and yp in matrix[yt]:
        matrix[yt][yp] += 1
# …
def compute_precision_at_k(hits: List[int]) -> float:
    """Calcula Precision@K a partir de uma lista de 0/1 (acertou ou não a tool)."""
    if not hits:
        return 0.0
    return float(sum(hits) / len(hits))
# …
cost_savings = 0.0
if baseline_cost_usd > 0:
    cost_savings = ((baseline_cost_usd - smart_cost_usd) / baseline_cost_usd) * 100.0

Saída de python -m candidate_starter.run_case na branch enxuta, 30 queries:

Métrica Valor Origem
Acurácia do router 100% (30/30) · matriz 10 · 0 / 0 · 20 impresso
precision_at_k (k = 2) 26,3% (5/19) impresso
Capacidade esperada, por capacidade top-1 19/20 · top-2 20/20 recontagem offline
Abstenções 5 (16,7%): 2 por G1, 0 por G2, 3 por G3 impresso; divisão por guarda, recontagem
Execuções 16, das quais 15 na capacidade esperada recontagem offline
Custo: pipeline / baseline US$ 0,16003 / US$ 0,90000 → economia de 82,2% impresso

Por que 26,3% não é a qualidade da recuperação. O ToolMatch.name que o retriever devolve é o nome da variante que casou (por exemplo verificar_saldo_conta_corrente), e o expected_tool do dataset é o nome canônico (consultar_saldo). A comparação por string falha mesmo com a capacidade certa. Recontando por capacidade, o resultado é 19/20 e 20/20. O denominador também é 19, não 20: a query barrada por G1 nunca chegou ao retriever e ficou fora da conta.

Por que 82,2% é maior que os 77,8% do resto da página. Cinco mensagens foram desviadas por abstenção e não chamaram LLM. É o mesmo efeito descrito em Economia sem taxa de sucesso é risco disfarçado: a economia sobe quando o sistema faz menos. A execução que sai da capacidade esperada é "Manda o pdf da minha fatura atual" → reenviar_fatura_email, que o dicionário não agrupa em consultar_fatura. E as 15 certas incluem a query de e-mail, cuja ferramenta de leitura o dicionário agrupa sob atualizar_email. A branch enxuta não calcula execuções incorretas nem o ponto de equilíbrio do custo de atendimento humano: essas duas contas só existem na main.

Branch enxuta × main, técnica por técnica

Ponto Branch enxuta main
Dobras da calibração cv = 3 (menor classe ≥ 3) até 5: min(5, menor classe)
Segundo campo do score derivado de nome e descrição: quase redundante glossário escrito à mão por capacidade, indexado em campo separado
Colapso por capacidade dicionário embutido: 49 variantes, 12 capacidades taxonomy.py: 14 capacidades, 45 variantes, com invariantes testadas
Desempate ordem estável do catálogo direção, depois −score, depois nome da capacidade
Piso de score (G2) no harness; o retriever só descarta score ≤ 0 min_score no retriever, com 0,10 passado por quem o monta
Direção (G4) prefixo do nome; só leitura→escrita zera mode declarado (59 de 285); leitura→escrita descarta, escrita→leitura rebaixa
Métricas precision_at_k por nome exato hit rate@1 e @k, execuções corretas e incorretas, ponto de equilíbrio, quality gate tri-estado

As cinco técnicas são simples e baratas nas duas branches. O que separa a enxuta da main não é uma técnica mais sofisticada, e sim o que cerca cada uma: um campo com informação nova, a direção declarada, um contrato de métrica que compara capacidade com capacidade e um desempate explícito.

08 · Da Prova de Conceito à Produção

Arquitetura Multi-Linguagem de Alta Performance (ADR-009 & RFC)

O pipeline em Python do MVP provou a solidez matemática do motor: 100% de precisão e zero execuções incorretas. Entretanto, transferir pods Python diretamente para o caminho crítico de produção bancária (atendendo dezenas de milhares de requisições por segundo com SLA p99 < 1-3 ms) esbarra em limitações incontornáveis: o Global Interpreter Lock (GIL), o jitter imprevisível de Garbage Collection (GC) e o custo de dispatch dinâmico.

A resposta de engenharia formalizada no ADR-009 e no RFC de Arquitetura é o desacoplamento estrito entre Cold Path e Hot Path:

Cold Path: Pesquisa & MLOps

Python 3.12 · Offline / CI

Mantém o ecossistema Python onde ele é imbatível: engenharia de features, treino com scikit-learn, calibração via CalibratedClassifierCV (Platt Scaling), SHAP e geração de artefatos determinísticos.

  • Exportação ONNX: Grafo de inferência linear compilado (modelo_router.onnx).
  • Matriz Esparsa CSR: Os 285 documentos do catálogo são pré-computados e serializados em doc_matrix.csr.
  • Vocabulário Determinístico: Dicionário TF-IDF indexado com pesos IDF (vocab_tfidf.json).
  • Imutabilidade Bancária: Artefatos versionados em Object Storage assinados com hash SHA-256.
  • Isolamento Rígido: O runtime de produção nunca importa Python, numpy ou scikit-learn.

Hot Path: OmniRoute Gateway

Rust / Go · Online Stateless

Ponto de entrada único stateless via gRPC ou HTTP/2. Motor de inferência embutido (in-process), eliminando saltos de rede, serialização intermediária ou chamadas IPC.

  • Zero Garbage Collection: Rust opera sem coletor de lixo, eliminando jitter e garantindo latência p99 determinística (< 1-3 ms).
  • Zero-Allocation no Hot Path: Pools estáticos e buffers pré-alocados via tokio e arenas de memória.
  • Guarda de 4 Camadas em RAM: Router, Retriever (multiplicação de matriz esparsa paralela) e Guard Rails executados em < 10 µs.
  • Container Distroless: Binário estático único (~20 MB) com superfície de ataque mínima e memory safety comprovada pelo compilador.
  • Slot-Filling Enxuto: O LLM só é invocado quando a ferramenta já foi isolada e autorizada.

Matriz de Decisão Tecnológica: Rust vs Go

O documento de arquitetura compara detalhadamente as opções de engenharia para o OmniRoute Gateway:

Critério de Avaliação Rust (Recomendado Primário) Go (Alternativa Secundária)
Gerenciamento de Memória Zero GC (Ownership & Borrow Checker). Sem pausas imprevisíveis. Garbage Collector com pausas sub-milissegundos, mas com risco de jitter sob alto volume de strings.
Latência p99 Sub-milissegundo (< 1 ms) determinística e previsível. 2 ms a 5 ms (pequenas oscilações durante coletas do GC).
Concorrência tokio (I/O não-bloqueante) + rayon (paralelismo de dados). Goroutines nativas e canais. Excelente concorrência I/O.
Álgebra Linear & ONNX Crate ort (bindings oficiais ONNX Runtime de zero-overhead) + sprs (CSR). onnxruntime-go via CGo (custo de troca de contexto Go ↔ C).
Conformidade e Segurança Prevenção formal de data races e buffer overflows em tempo de compilação. Memory safe, porém com risco de alocações excessivas no heap.
Artefato de Entrega Binário estático (~15-25 MB), container distroless. Binário estático (~20-30 MB), container distroless.

O Ecossistema de Decisões de Arquitetura (9 ADRs)

Cada escolha estrutural foi registrada segundo o padrão Architecture Decision Record (ADR), garantindo que qualquer novo membro do time compreenda o contexto, alternativas descartadas e consequências:

10 · Evolução Medida

Economia sem taxa de sucesso é risco disfarçado

Conforme a Lei de Goodhart ("quando uma métrica vira meta, ela deixa de ser uma boa métrica"), otimizar cegamente a economia de custos levou o pipeline inicial a abster-se de quase metade das chamadas, inflando artificialmente a porcentagem de economia.

Métrica / Iteração Commit inicial (fc830dc) Iteração com Crash (c8f30d2) Versão publicada (1f780c0)
Testes Automatizados 35 54 72 passed, 0 skipped
Hit Rate@2 35% 100% 100%
Execução Correta Top-1 20% (4/20) 95% (19/20) 100% (20/20)
Execuções Incorretas 7 incorretas 0 (mas vulnerável) 0 incorretas
Leitura Resolvendo Escrita Não medido Sim (margem 0.47) Impossível (G4)
Fallback por Baixa Confiança 9 abstenções 0 0
Economia de LLM medida (benchmark) 87,8% (inflada) 78,9% 77,8% (líquida real)
Status do Harness REPROVADO APROVADO APROVADO NO BENCHMARK

Por que a economia caiu de 87,8% para 77,8%? Na primeira versão, 9 abstenções indevidas evitavam chamadas de LLM, inflando a economia enquanto entregavam apenas 20% de sucesso. A versão final resolve 20 de 20 queries transacionais com 100% de acerto e paga LLM por todas elas.

O que 77,8% mede — e o que não mede. É a redução do custo de chamadas de LLM entre o pipeline inteligente e o baseline "tudo pro LLM", no benchmark offline. Não é economia financeira total do sistema: o custo do fallback humano, por exemplo, é premissa e não medição — por isso o relatório publica cost_savings_pct_on_resolved, deferred_to_human e human_fallback_breakeven_cost_usd lado a lado.

📄 Baixar candidate_report.json (Relatório Oficial de Benchmark)

11 · O Processo

Como foi desenvolvido: o método agêntico com crítica incorporada

O desenvolvimento não seguiu uma linha reta. Duas rodadas foram impulsionadas por revisão adversarial externa, onde cada apontamento gerou um ciclo de diagnóstico, medição em ADR (Architecture Decision Record) e suíte de testes estendida. A terceira não teve revisor: apareceu porque o estado estacionário foi testado em vez de aceito no verde.

Rodada 1

Crítica ao Commit fc830dc

Diagnóstico de recall de 35%, 7 execuções incorretas e economia inflada. Produziu as ADRs 0006 e 0007, elevando a suíte de 35 para 54 testes.

Rodada 2

Reavaliação Adversarial do Commit c8f30d2

Mapeamento de 8 riscos remanescentes. O teste de colisão leitura/escrita recomendado expôs o Crash Silencioso. Gerou a ADR-0008 (guarda de direção) e somou os testes de colisão à suíte.

Rodada 3

A saga da procedência do relatório — commits 0d2c9ae … 1f780c0

Regenerar o candidate_report.json e rodar a suíte de novo revelou a contagem com um 1 skipped a mais. O skip não era condicional benigno — era um teste desistindo em silêncio. Corrigido, a suíte fecha em 72 passed, 0 skipped.

A procedência que contaminava a própria medição

O relatório de métricas é versionado — e um JSON versionado envelhece em silêncio: o código muda, o arquivo fica, e quem lê acredita em números que o commit atual não produz mais. O projeto tem um teste que recomputa as decisões contra o código vigente. Esse teste teve três defeitos da mesma família, todos com o relatório sujando a medição que deveria auditá-lo:

  1. Asserção insatisfazível. Exigia snapshot.git_commit == HEAD. Como commitar o relatório cria um commit novo, o snapshot sempre aponta para o anterior — a asserção nunca poderia passar. A propriedade realmente auditável é outra: entre o commit que gerou o relatório e o HEAD, nada fora de reports/ mudou — e a falha passa a nomear os arquivos atrasados.
  2. git_dirty se auto-sujava. build_snapshot() contava reports/ na verificação de árvore suja. Como o relatório é escrito pela própria função, da segunda execução em diante ele se declarava irreprodutível por causa do arquivo que acabara de gerar.
  3. _git().strip() comia o espaço do prefixo de status. O filtro fatiava line[3:] do --porcelain, mas o .strip() na saída remove o espaço inicial da primeira linha: o caminho saía deslocado (eports/...), o filtro nunca casava e o teste voltava a pular em silêncio. Resolvido delegando o filtro ao próprio git (pathspec ':(exclude)reports'), sem parsing.

Uma suíte verde com 1 skipped parecia saudável. Não era — o skip era um teste desistindo em silêncio, e só apareceu porque o estado estacionário foi testado (regenerar o relatório, rodar a suíte de novo) em vez de aceitar o verde. É a terceira instância da tese desta página: métricas verdes não são cobertura.

Princípios que governaram as decisões

  • Medir antes de editar: Nenhuma alteração foi feita sem antes registrar a tabela de métricas antes/depois na ADR.
  • Nunca mover a trave: Soluções fáceis (como subir parâmetros de probabilidade para mascarar erros) foram sumariamente rejeitadas.
  • Testes de Ausência de Efeito: Testes que garantem que mock_tool_execution nunca é chamada se qualquer guarda reprovar.
  • Testar o estado estacionário: Não basta a suíte passar uma vez — ela tem de continuar passando depois que o sistema escreve os próprios artefatos.

12 · Especificação Executável

A Suíte de 72 Testes como Especificação do Sistema

Oito arquivos de teste divididos por invariantes de segurança e integridade. A parte mais valiosa não verifica um valor retornado; verifica que nada aconteceu:

test_taxonomy.py A invariante de segurança mais cara do domínio, verificada no ranking completo
def test_a_read_request_can_never_resolve_to_a_write_capability(retriever):
    """Antes da guarda de direcao, "Qual e o email cadastrado na minha conta?"
    devolvia atualizar_email (ESCRITA) em 1o lugar com margem relativa de 0.47 —
    acima do limiar de 0.25: a guarda de margem NAO pegava."""
    for query in read_requests:            # 5 consultas puras
        for m in retriever.search(query, k=3).matches:
            assert CAPABILITY_MODE.get(m.name) != "write"

Categorias da Suíte de Testes

  • Ausência de efeito: mock_tool_execution nunca é chamada quando qualquer guarda reprova (executed.assert_not_called()).
  • Integridade da Taxonomia: 7 invariantes de autoria (existência no catálogo, ausência de colisão, leitura ≠ escrita, direção declarada e herdada).
  • Generalização sem Ajuste ao Gabarito: 12 paráfrases escritas com vocabulário totalmente ausente do dataset oficial (9/12 no top-1, 11/12 no top-2).
  • Estabilidade e Procedência: Embaralhar a ordem do catálogo não altera decisão alguma; o relatório em JSON corresponde exatamente ao commit e código vigentes — e essa checagem não pode pular (foi o defeito da rodada 3).

13 · Transparência Técnica

Limitações Reais do Projeto (O que o benchmark não cobre)

Publicar limitações é o que separa engenharia de propaganda. As 7 restrições abaixo estão registradas no README do repositório oficial:

  1. Aprovado neste benchmark, não para produção bancária viva: 30 queries, 53 exemplos de treino e tools mockadas. Tráfego real com adversários exige camadas de MFA, autorização e idempotência fora deste escopo.
  2. A guarda de direção só protege o que está declarado: 59 das 285 ferramentas têm mode (14 capacidades canônicas + 45 variantes). As outras 226 passam sem restrição de direção. O lugar arquitetural correto para mode é o próprio registry — é metadado do endpoint, não da taxonomia.
  3. Teto de recuperação lexical em paráfrase: O recall de paráfrases puras sem palavras-chave atinge 9/12 no top-1. O próximo degrau exige embeddings densos/reranker.
  4. Taxonomia mantida manualmente: 14 capacidades e 45 variantes configuradas em código Python sobre 285 ferramentas.
  5. As guardas 2 e 3 não são mais exercitadas pelo benchmark oficial: Com 20/20 e zero abstenções, elas passam a ser provadas só pelos testes de ausência de efeito. Um benchmark que nunca aciona a rede de segurança não demonstra que ela funciona.
  6. Folga de calibração do router é estreita: A query AGENT menos confiante registra 0.7674 contra o limiar de 0.75 — 0.017 de folga, registrada como risco. (Já as margens das decisões corretas ficam entre 0.592 e 0.741 contra o limiar de 0.25: essa, sim, folgada.)
  7. Custo de fallback humano é premissa, não medição: O número confiável é o ponto de equilíbrio, que é derivado das medições.

14 · Ecossistema & Links

Referências Conceituais do Site

Engenharia de Confiança

Fundação & Método

Repositório & Arquitetura

Em síntese

O que é o projeto Case Agents

O case-agents é a implementação e benchmark de um cérebro de roteamento para agente bancário: decide se uma query necessita de LLM, seleciona 2 ferramentas entre 285 registradas e bloqueia a execução quando a decisão não é confiável o bastante.

Caso de estudo da Engenharia da Confiança e Engenharia de Agentes de IA.

Perguntas frequentes

Perguntas frequentes

O que é o projeto Case Agents?

É o desenvolvimento e avaliação do cérebro de roteamento e seleção de ferramentas de um agente de atendimento bancário digital. Ele classifica a mensagem em FAST_PATH ou AGENT, recupera as 2 ferramentas mais relevantes entre 285 e aplica uma barreira pré-execução de 4 camadas antes de chamar qualquer LLM.

O que é o Crash Silencioso medido no projeto?

Uma falha em que uma query de consulta ("Qual é o e-mail cadastrado na minha conta?") resolveu com alta confiança e margem folgada (0.47) para a ferramenta de alteração de cadastro (atualizar_email). A falha passou por 54 testes e 3 guardas tradicionais sem gerar exceção.

Como a Guarda de Direção funciona?

A Guarda de Direção (G4) analisa o verbo da query e compara com o modo de operação (read/write) declarado na ferramenta. Se a query for de leitura e a ferramenta for de escrita, o sistema descarta totalmente a ferramenta para evitar danos irreversíveis ao cadastro do cliente.

Como alcançar 100% de sucesso com economia de custo?

Declarando a governança do catálogo de ferramentas (colapso de duplicatas semânticas em capacidades canônicas) e aplicando a barreira pré-execução de 4 camadas (confiança do router, score mínimo, margem relativa e direção). A economia de custo líquida ficou em 77,8%.

Qual é a estratégia de duas branches do projeto?

O repositório mantém duas abordagens no Git: a branch feature/solucao-enxuta, focada em prototipagem rápida e validação de hipóteses com código enxuto, e a branch main (solucao-sdd-vibe), focada em robustez enterprise com Spec-Driven Development, 9 ADRs, taxonomia canônica, calibração Platt Scaling e blueprint de arquitetura produtiva.

Como funciona a Arquitetura Produtiva de Alta Performance (ADR-009)?

A arquitetura produtiva separa rigidamente o Cold Path (treinamento e calibração em Python 3.12 offline/CI) do Hot Path (Gateway de inferência OmniRoute em Rust/Go stateless online). Com motor embutido in-process, o hot path atinge latência p99 inferior a 1-3 ms, elimina pausas de Garbage Collection e executa os guard rails diretamente na memória RAM.