102 lines
3.7 KiB
Python
102 lines
3.7 KiB
Python
import os
|
|
|
|
import pytest
|
|
|
|
os.environ.setdefault("USE_MOCK_LLM", "true")
|
|
|
|
from agent_framework.guardrails.calibrated import ausencia_oferta_proativa
|
|
from agent_framework.guardrails.calibrated._compat import RailResult
|
|
from agent_framework.guardrails.calibrated.prompts.ausencia_oferta_proativa import (
|
|
build_aoferta_prompt,
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Contrato de saída (2026-08-05): {allowed, reason}, reason vazio se allowed #
|
|
# --------------------------------------------------------------------------- #
|
|
def test_prompt_aoferta_nao_pede_label():
|
|
# O `label` nunca existiu no prompt real (só no mock); o contrato é
|
|
# {allowed, reason}, como o FRASEOLOGIA.
|
|
prompt = build_aoferta_prompt("Gostaria de cancelar esse serviço?")
|
|
assert '"label"' not in prompt
|
|
assert '"allowed": true ou false' in prompt
|
|
|
|
|
|
def test_prompt_aoferta_exige_reason_vazio_quando_allowed():
|
|
prompt = build_aoferta_prompt("Gostaria de cancelar esse serviço?")
|
|
assert "se allowed=true: string vazia" in prompt
|
|
assert "se allowed=true: explicacao curta" not in prompt
|
|
|
|
|
|
def test_prompt_aoferta_injeta_texto_e_contexto():
|
|
prompt = build_aoferta_prompt("posso cancelar?", "\nHistorico:\n[user] oi\n")
|
|
assert "posso cancelar?" in prompt
|
|
assert "[user] oi" in prompt
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _force_mock(monkeypatch):
|
|
monkeypatch.setenv("USE_MOCK_LLM", "true")
|
|
|
|
|
|
def test_resposta_neutra_eh_permitida():
|
|
r = ausencia_oferta_proativa("Posso te ajudar com mais alguma coisa?")
|
|
|
|
assert isinstance(r, RailResult)
|
|
assert r.allowed is True
|
|
assert r.code == "AOFERTA"
|
|
assert r.mechanism == "llm_supervisor"
|
|
assert r.data is not None
|
|
assert r.data.get("label") == "OFERTA_OK"
|
|
|
|
|
|
def test_oferta_proativa_indevida_bloqueia():
|
|
r = ausencia_oferta_proativa(
|
|
"Ja que esta cancelando um, quer aproveitar e cancelar os outros?",
|
|
)
|
|
|
|
assert r.allowed is False
|
|
assert r.code == "AOFERTA"
|
|
assert r.mechanism == "llm_supervisor"
|
|
assert r.data.get("label") == "OFERTA_PROATIVA_INDEVIDA"
|
|
|
|
|
|
def test_padrao_aproveita_e_bloqueia():
|
|
r = ausencia_oferta_proativa("Quer aproveitar e cancelar os outros?")
|
|
|
|
assert r.allowed is False
|
|
assert r.code == "AOFERTA"
|
|
|
|
|
|
def test_confirmacao_cancelamento_apos_nao_reconhecimento_eh_permitida():
|
|
# Cliente nao-reconheceu os servicos em foco ("Nao contratei esses
|
|
# servicos") e o agente confirma o cancelamento dos MESMOS itens. Per
|
|
# classificador v9 + orquestrador, isso e action_request -> a confirmacao
|
|
# deve ser OFERTA_OK, nao oferta proativa.
|
|
#
|
|
# NOTA: em USE_MOCK_LLM=true o mock decide AOFERTA por string-match no texto
|
|
# do agente (sem gatilho de upsell -> OFERTA_OK), ignorando o contexto. Este
|
|
# teste documenta a intencao; a validacao real do prompt e via LLM/cenarios
|
|
# (automated_conversation_test cy0004/cy0005/cy0008).
|
|
r = ausencia_oferta_proativa(
|
|
"Podemos seguir com o cancelamento dos serviços VOD + Canais Abertos, "
|
|
"no valor de R$ 19,90, e Aluguel de Filme 1, no valor de R$ 19,90, "
|
|
"e providenciar a retirada dos valores da fatura, você confirma?",
|
|
)
|
|
|
|
assert r.allowed is True
|
|
assert r.code == "AOFERTA"
|
|
assert r.data.get("label") == "OFERTA_OK"
|
|
|
|
|
|
def test_upsell_continua_bloqueando_apos_mudanca():
|
|
# Contraprova: a mudanca para reconhecer nao-reconhecimento NAO pode liberar
|
|
# marcadores de upsell explicitos.
|
|
r = ausencia_oferta_proativa(
|
|
"Ja que esta cancelando esse, que tal tambem cancelar o outro servico?",
|
|
)
|
|
|
|
assert r.allowed is False
|
|
assert r.code == "AOFERTA"
|
|
assert r.data.get("label") == "OFERTA_PROATIVA_INDEVIDA"
|