ajuste no guardraild COE

This commit is contained in:
T3782834
2026-08-31 21:11:51 -03:00
parent 10a6f357dd
commit e24963abe1
51 changed files with 50 additions and 120 deletions

View File

@@ -1,148 +1,78 @@
"""Prompt do rail COER (coerência do input do cliente). """Prompt do rail COER (coerência semântica do input do cliente).
Roda no INPUT, em paralelo com PINJ (mesmo pool), num 20b. Decide se a fala do O COER responde uma pergunta estreita: existe significado conversacional recuperável
cliente é aproveitável. Saída BINÁRIA (`1` passa / `0` descarta) — o `reason` é na fala do cliente? Ele não decide intenção, completude de parâmetros, executabilidade,
texto fixo; pedir motivo antes do dígito foi medido e não paga (+170 ms, empate). escopo de negócio ou se uma solicitação deve ser aceita. Essas decisões pertencem ao
router, aos contratos transacionais, aos validadores e ao mecanismo de clarification.
Descarta SÓ por três motivos: A classificação continua totalmente delegada ao LLM. Não há listas de frases,
regexes ou exceções de domínio para liberar/bloquear entradas específicas.
(a) incompreensível — transcrição quebrada, palavra solta, conversa paralela;
(b) negação ambígua — "não" colado num pedido de AÇÃO do atendente, sem a vírgula
que decidiria a leitura ("não quero cancelar" × "não, quero cancelar");
(c) idioma (2026-08-10) — frase INTEIRA em inglês é STT quebrado, não cliente
bilíngue: descarta mesmo se ela se entende ou responde à pergunta pendente.
Ressalva: passa quando o agente pediu o NOME do item — nome de serviço É em
inglês (`coer_ok_0023`). ⚠️ A regra só funciona no ENQUADRAMENTO, acima do
gate de histórico (dentro de (a): 0/9 nos casos de inglês; no topo: 9/9),
porque o gate concede 1 a quem responde e o catch-all a quem pede algo
legível. Travado em `tests/guardrails/test_coerencia.py`.
O resto passa e é tratado adiante (matcher, TOX, OOS, orquestrador): referência
vaga, nome deformado, xingamento, assunto fora de fatura, resposta curta. O
histórico entra no prompt porque é ele que resolve fala curta e negação sem vírgula.
Dois bugs de produção fechados, ambos com a mesma assinatura — o modelo reconhece
a fala e escapa por uma regra de allow antes de aplicar (b):
- 2026-08-07, "não" seco no degrau 2 da retenção: (b) disparava só por começar
com "não" e o modelo COMPLETAVA a elipse com a ação que o AGENTE ofereceu.
Conserto: (b) exige que a fala PEÇA algo, e o teste da subtração proíbe
completar com a oferta do agente (`coer_ok_0027`: 161/220 → 340/340);
- 2026-08-10, "não gostaria de falar com a atendente" (`coer_ambig_0014`, 2/9):
a causa é o VERBO, não o gate nem o histórico (sonda 2×2 — condicional +
histórico curto 2/10 × "não quero" + o histórico longo do trace 10/10).
Conserto: gate vale só para a fala que "SÓ responde a ela"; (b) diz que
entender o pedido não dispensa o teste; a glosa do 1º exemplo cobre o
condicional. Alvo → 7/9, suíte 176,0 → 180,7/189.
⚠️ Protocolo: decida por BATCH (3 amostras de `--repeat 3` da suíte inteira, banda
de ruído ±4). `--repeat` focado engana nos dois sentidos — a mesma variante deu
7/10 focado × 0/9 batch, e o prompt atual dá 7/9 batch × 3/9 focado.
Variantes medidas e REJEITADAS (não retentar sem motivo novo) — a suíte está numa
fronteira zero-soma, cada cláusula compra um caso e vende outro:
- "a recusa soar clara não fecha" → CONTRADIZ a exceção "a fala segue dizendo
qual leitura vale": mata `coer_ok_0003` (7/9 → 0-1/9) em 3 variantes;
- exceção no GATE ("fala com 'não' ainda passa por (b)") → mata `coer_ruido_0011`
(9/9 → 0/9): exceção explícita REFORÇA o gate para todo o resto;
- "gostaria" na lista de modais de (b) → 169,7/189;
- few-shot NÃO é mais alavanca (era em 2026-08-05, +3,4 p.p.): +3 exemplos = empate
exato por +132 tokens; só o do NOME em inglês = 189,7/201 (arrasta a regra (c));
tirar exemplos custa mais do que os tokens que ocupam — inclusive o "não quero
entender porque…", que o controle FOCADO media como "sem efeito" e em batch vale
`coer_ok_0010` inteiro (9/9 → 1/9).
Tamanho: 1289 → 1334 (2026-08-07) → **1451 tokens** (cl100k). Suíte: **191,7/201
(95,4%)**, 67 casos. Detalhe por caso e histórico: `tests/llm_tests/README.md`.
Remedido em 2026-08-12 ao desfazer o revert (41979c4d): 193,7/204 (95,0%), 68 casos
— o novo `coer_ruido_0022` ("um" respondendo "sanei sua dúvida?", STT que não pegou
o "sim" → golden 0, reperguntar) sai de 3/10 no prompt antigo para 9/9 em batch só
com o gate "SÓ responde a ela", sem mudança extra de prompt.
""" """
from __future__ import annotations from __future__ import annotations
def build_coer_prompt(text: str, context: str = "") -> str: def build_coer_prompt(text: str, context: str = "") -> str:
"""Monta o prompt do rail COER. """Monta o prompt semântico do rail COER.
Args: Args:
text: fala do cliente a classificar. text: fala do cliente a classificar.
context: bloco de histórico já formatado por context: histórico já formatado, incluindo a pergunta pendente do agente
``prompts._context.format_context_block`` (para este rail a última quando disponível.
fala do agente é PRESERVADA — é a pergunta pendente).
Returns: Returns:
Prompt cuja resposta esperada é um único caractere: ``1`` ou ``0``. Prompt cuja resposta esperada é um único caractere: ``1`` ou ``0``.
""" """
return f"""Você filtra a fala do CLIENTE no atendimento de fatura do provedor. A fala vem de return f"""Você é o guardrail de COERÊNCIA SEMÂNTICA da fala do CLIENTE em uma conversa.
transcrição de voz e pode chegar truncada ou trocada. O atendimento é em português:
frase inteira em INGLÊS é STT quebrado, não cliente bilíngue — responda 0 mesmo que
ela se entenda ou responda à pergunta do agente; só não vale quando o agente pediu o
NOME do item, que é em inglês.
PRIMEIRO olhe o histórico. Se o agente terminou com uma pergunta e a fala SÓ responde a ela Sua única responsabilidade é decidir se a fala contém significado conversacional
(sim/não, "ainda não", nome de serviço, valor, uma das opções oferecidas), responda 1 recuperável o suficiente para que as próximas camadas do sistema possam trabalhar.
— mesmo curta, estranha ou com o nome deformado pelo STT. Se não há pergunta pendente,
julgue a fala sozinha pelos casos abaixo, sem dar desconto.
Responda 0 (descartar) SÓ nestes dois casos: NÃO tente decidir aqui:
- qual é a intenção do cliente;
- se a intenção mudou em relação ao turno anterior;
- se uma transação deve continuar, ser abandonada ou encerrada;
- se faltam parâmetros para executar uma ação;
- se um valor, nome, data ou outro parâmetro é válido;
- se a solicitação pertence ao escopo do atendimento;
- se uma ação é permitida por regra de negócio;
- se a fala precisa de clarification ou desambiguação posterior.
(a) NÃO DÁ PARA ENTENDER — você não conseguiria dizer em uma frase, SEM INVENTAR, o Essas responsabilidades pertencem ao router, ao estado transacional, aos validadores
que o cliente quer, responde ou reclama: transcrição quebrada, frase cortada no e ao mecanismo de clarification. Portanto, uma fala pode ser compreensível mesmo
meio, palavra ou letra solta, frase que soa completa mas cujo pedido não faz sendo incompleta para execução, contendo negação, discordância, reclamação, múltiplas
sentido, ou fala dirigida a OUTRA PESSOA (o cliente conversando com quem está do intenções, informalidade, erro gramatical ou referência que precise ser resolvida pelo
lado, sem falar com o atendimento). Palavra do domínio (plano, fatura, valor, contexto.
cpf) dentro de frase sem sentido não salva a fala. Fala VAGA não é
incompreensível: se ela aponta para o que está na tela ("esse aí", "isso aqui",
"esse negócio", "os valores"), responda 1 — perguntar qual item é do fluxo.
E se a última fala do agente pediu um NOME de item/serviço, nenhuma fala curta
é incompreensível: ela é a tentativa de dizer o nome, por mais estranha que
soe → 1 (reconhecê-lo é da etapa seguinte, que tem a fatura).
(b) NEGAÇÃO AMBÍGUA — a fala começa com "não" E PEDE ALGO depois; entender o que ela Use o histórico somente para interpretar elipses, respostas curtas e referências ao
pede não a salva, quem decide é o teste. Faça o teste: tire turno anterior. Nunca complete a fala inventando uma intenção que não esteja apoiada
esse "não" do início e olhe SÓ o que sobra na fala — nunca complete com a ação pela própria fala ou pelo contexto imediato.
que o agente ofereceu. Se não sobra pedido nenhum ("não", "não sanou"), é
resposta ao agente → 1, seja qual for a pergunta pendente. Se o que sobra é
pedido de ação do atendente (cancelar, tirar cobrança,
ajustar/diminuir a fatura, transferir para atendente, encerrar a conta,
parcelar), sobram duas leituras opostas — recusa ("não quero cancelar") ou
pedido ("não, quero cancelar") — e a vírgula que decidiria não veio na
transcrição: responda 0. Vale para qualquer verbo ("não quero/preciso/posso",
"não quero que vocês...", "não cancela").
Responda 1 se: vem vírgula, "porque" ou "mas" depois do "não"; há sujeito antes
do "não" ("eu não quero cancelar"); a fala segue dizendo qual leitura vale; ou o
que sobra sem o "não" não é ação do atendente (pagar, reconhecer, entender,
mudar de plano).
Responda 1 em TODO o resto, inclusive: Responda 1 quando for possível identificar, sem inventar, pelo menos um conteúdo
- pedido, queixa, dúvida ou desabafo que você entende, mesmo com erro de transcrição, conversacional útil: uma intenção, pergunta, resposta, afirmação, negação, reclamação,
gíria, xingamento, número solto ou assunto fora de fatura (outros filtros cuidam); referência, escolha, valor, nome, pedido de esclarecimento, encerramento ou mudança de
- nome de serviço estranho ou deformado, inclusive quando o agente pediu para repetir assunto. Não exija que esse conteúdo já seja suficiente para executar uma ferramenta.
o nome do serviço;
- pedido de tempo, "alô?", agradecimento, despedida.
Dúvida se entendeu a fala → 1. Pergunta ou pedido claro dirigido ao atendimento, mesmo Responda 0 somente quando, mesmo considerando o contexto imediato, não houver
fora do assunto de fatura → 1. Dúvida entre as duas leituras da negação → 0. significado conversacional recuperável com segurança — por exemplo, transcrição
fragmentada, palavras desconexas, fala cortada antes de formar qualquer relação
semântica, ou conversa paralela sem solicitação dirigida ao atendimento.
Exemplos (ilustram a regra, não são lista de falas): Critério decisivo:
- "não quero parcelar a fatura" → 0 (sem a vírgula, pode ser "não, quero parcelar"); - compreensível mas incompleto/ambíguo para a regra de negócio -> 1;
idem no condicional, "não gostaria de parcelar a fatura" - compreensível mas com possível mudança de intenção -> 1;
- "eu não quero parcelar a fatura" → 1 (o "eu" antes do "não" fecha a leitura) - compreensível mas sem todos os parâmetros -> 1;
- "não quero parcelar, quero só entender o valor" → 1 (a fala diz qual leitura vale) - compreensível e contendo negação/discordância -> 1;
- "não vou pagar essa multa" → 1 (pagar não é ação do atendente: a queixa é a mesma) - impossível determinar qualquer conteúdo conversacional sem inventar -> 0.
- "não", depois de "sanou sua dúvida?" → 1 (responde a pergunta pendente)
- "deixe zero", depois de "qual o nome do serviço?" → 1 (pode ser o nome que o STT Na dúvida entre "há significado, mas outra camada precisa esclarecer" e "não há
deformou — "Deezer"; reconhecer o nome é da etapa seguinte, que tem a fatura) significado recuperável", escolha 1. O COER deve bloquear apenas incompreensibilidade
- "não quero entender porque a conta subiu tanto" → 1 (entender é dúvida, não ação) semântica real, não incerteza de negócio.
- "olha o menino ali pegando o negócio lá" → 0 (não dá para dizer o que o cliente quer)
- "bota dois planos um em cima do outro pra cá" → 0 (soa ordem, não quer dizer nada)
- "está cobrando um" → 0 (cortada no meio: não dá para saber de quê)
------------------------------------{context} ------------------------------------{context}
Fala do cliente: Fala do cliente:
{text} {text}
------------------------------------ ------------------------------------
Responda APENAS um caractere: 1 (aproveitável) ou 0 (descartar). Responda APENAS um caractere: 1 (semanticamente compreensível) ou 0
(semanticamente incompreensível).
""" """