81 lines
2.8 KiB
Python
81 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.extensions.tim_guardrails import TimPrematureActionRail
|
|
|
|
|
|
class FakeLLM:
|
|
def __init__(self, content: str):
|
|
self.content = content
|
|
|
|
async def ainvoke(self, messages, **kwargs):
|
|
return self.content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_revprec_allows_structured_insufficient_evidence_without_calling_llm():
|
|
text = "Identifiquei dados de desconto, mas os dados disponíveis não informam o motivo da retirada ou do término do desconto."
|
|
|
|
class MustNotRunLLM:
|
|
async def ainvoke(self, *args, **kwargs):
|
|
raise AssertionError("LLM não deveria ser chamada no bypass estrutural")
|
|
|
|
ctx = {
|
|
"guardrail_llm": MustNotRunLLM(),
|
|
"mcp_results": [
|
|
{
|
|
"tool_name": "termino_desconto",
|
|
"ok": True,
|
|
"result": {
|
|
"output": {
|
|
"formatar": {
|
|
"mensagem": text,
|
|
"epistemic_status": "insufficient_evidence",
|
|
"discount_reason_grounded": False,
|
|
}
|
|
}
|
|
},
|
|
}
|
|
],
|
|
}
|
|
out = await TimPrematureActionRail().evaluate(text, ctx)
|
|
assert out.allowed is True
|
|
assert out.reason == "insufficient_evidence_non_assertive"
|
|
assert out.metadata["mechanism"] == "deterministic_epistemic_bypass"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_revprec_does_not_bypass_if_message_does_not_match_structured_result():
|
|
structured = "Os dados disponíveis não informam o motivo."
|
|
text = "Cancelei o serviço com sucesso."
|
|
ctx = {
|
|
"guardrail_llm": FakeLLM("1"),
|
|
"mcp_results": [{"result": {"output": {"mensagem": structured, "epistemic_status": "insufficient_evidence"}}}],
|
|
}
|
|
out = await TimPrematureActionRail().evaluate(text, ctx)
|
|
assert out.allowed is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_revprec_still_blocks_real_premature_action_without_structured_marker():
|
|
out = await TimPrematureActionRail().evaluate(
|
|
"Cancelei o serviço com sucesso.",
|
|
{"guardrail_llm": FakeLLM("1"), "mcp_results": []},
|
|
)
|
|
assert out.allowed is False
|
|
|
|
|
|
def test_termino_desconto_declares_epistemic_status():
|
|
from app.domain.contas.service import ContasDomainService
|
|
from app.domain.contas.workflow_actions import build_contas_workflow_actions
|
|
|
|
action = build_contas_workflow_actions(ContasDomainService()).get("formatar_capability_resposta")
|
|
no_reason = action({"tipo": "termino_desconto"}, {"input": {}})
|
|
with_reason = action(
|
|
{"tipo": "termino_desconto", "discount_evidence": {"discount_reason": "fim da campanha"}},
|
|
{"input": {}},
|
|
)
|
|
assert no_reason["epistemic_status"] == "insufficient_evidence"
|
|
assert with_reason["epistemic_status"] == "grounded_fact"
|