new feature: LOCALE pt-BR/en

This commit is contained in:
T3782834
2026-10-27 11:45:37 -03:00
parent 4ca6c8a274
commit ccf509dd21
137 changed files with 12177 additions and 1018 deletions

View File

@@ -1285,11 +1285,14 @@ async def test_prevalidation_parameter_message_is_used_once_without_changing_tra
assert runtime.transaction_clarification_message(state) != expected
assert "transaction_parameter_message_override" not in state
class _TemporalFallbackRuntime(AgentRuntimeMixin):
class _SingleExtractorValidationRuntime(AgentRuntimeMixin):
def __init__(self):
from types import SimpleNamespace
self.calls = []
self.llm = None
self.extractions = iter([
{"subject": "dezenove e noventa e nove"},
{"subject": "Tamboro Mensal"},
])
self.tool_router = SimpleNamespace(
registry=SimpleNamespace(
get_tool=lambda name: SimpleNamespace(
@@ -1308,72 +1311,63 @@ class _TemporalFallbackRuntime(AgentRuntimeMixin):
},
)
async def _extract_transaction_parameters_current_only(self, state, *, tool_name, missing_parameters, known_arguments=None):
# Simula o collector tradicional interpretando literalmente a fala atual.
return {"subject": "dezenove e noventa e nove"}
async def _reconcile_transaction_parameters(self, state, *, tool_name, parameter_names, known_arguments=None):
# Simula o reconciliador temporal usando tools.yaml + contexto newest->oldest.
return {
"values": {"subject": "Tamboro Mensal"},
"decisions": {"subject": "resolved"},
"provenance": {"subject": "history:2"},
"clear_fields": [],
}
async def _extract_transaction_parameters(self, state, *, tool_name, missing_parameters, known_arguments=None):
# Simula a saída do extractor por turno; não interpreta frases do prompt.
return next(self.extractions)
async def _call_mcp_tool(self, tool_name, arguments, state):
self.calls.append((tool_name, dict(arguments)))
if tool_name == "validar_vas_subject":
subject = arguments.get("subject")
if subject == "Tamboro Mensal":
if arguments.get("subject") == "Tamboro Mensal":
return {"ok": True, "tool_name": tool_name, "result": {"eligible": True, "status": "ELIGIBLE"}}
return {"ok": True, "tool_name": tool_name, "result": {
"eligible": False,
"status": "NEEDS_PARAMETER",
"parameter": "subject",
"reason": "subject_not_resolved",
"eligible": False, "status": "NEEDS_PARAMETER",
"parameter": "subject", "reason": "subject_not_resolved",
}}
return {"ok": True, "tool_name": tool_name, "result": {"status": "OK"}}
raise AssertionError("A operação não pode executar antes da confirmação")
@pytest.mark.asyncio
async def test_temporal_reconciler_is_fallback_after_traditional_candidate_fails_prevalidation():
runtime = _TemporalFallbackRuntime()
async def test_prevalidation_rejection_recollects_next_turn_without_hidden_retry():
# Histórico: 1711575 (2026-09-07) removeu candidatos estruturais;
# 82636ec (2026-09-08) adotou single_extractor. O antigo teste exigia
# uma segunda reconciliação/validação escondida no mesmo turno.
# Contrato atual: respeitar NEEDS_PARAMETER, aguardar nova informação e
# validar novamente antes de pedir confirmação, sem executar a operação.
runtime = _SingleExtractorValidationRuntime()
state = {
"user_text": "é a de dezenove e noventa e nove",
"sanitized_input": "é a de dezenove e noventa e nove",
"route": "contestacao_agent",
"intent": "state:COLLECTING_CONTESTACAO_PARAMETERS",
"route": "contestacao_agent", "intent": "state:COLLECTING_CONTESTACAO_PARAMETERS",
"transaction_status": "COLLECTING_PARAMETERS",
"selected_tool_call": {"tool_name": "cancelar_vas_avulso", "arguments": {}},
"active_transaction": {
"transaction_id": "tx-1",
"tool_name": "cancelar_vas_avulso",
"arguments": {},
"status": "COLLECTING_PARAMETERS",
"started_from_intent": "contas_vas_cancel",
"requires": ["subject"],
"transaction_id": "tx-1", "tool_name": "cancelar_vas_avulso",
"arguments": {}, "status": "COLLECTING_PARAMETERS",
"started_from_intent": "contas_vas_cancel", "requires": ["subject"],
"parameter_schema": {"subject": {"type": "string", "description": "Nome do serviço ou VAS alvo do cancelamento."}},
"tool_description": "Cancela o serviço VAS selecionado pelo cliente.",
"parameter_conversational_context": (
"user: não reconheço esse Tamboro Mensal na minha fatura\n"
"assistant: identifiquei Tamboro Mensal por R$ 14,99 e R$ 19,99"
),
},
"context": {},
}
result = await runtime.execute_tools_for_intent(state, tools=[])
await runtime.execute_tools_for_intent(state, tools=[])
assert [(name, args["subject"]) for name, args in runtime.calls] == [
("validar_vas_subject", "dezenove e noventa e nove")
]
assert state["transaction_status"] == "COLLECTING_PARAMETERS"
assert "subject" in state["missing_parameters"]
assert not state["active_transaction"]["arguments"].get("subject")
assert not state.get("confirmation_received")
validations = [(name, args.get("subject")) for name, args in runtime.calls if name == "validar_vas_subject"]
assert validations == [
state["user_text"] = state["sanitized_input"] = "o serviço Tamboro Mensal"
result = await runtime.execute_tools_for_intent(state, tools=[])
assert [(name, args["subject"]) for name, args in runtime.calls] == [
("validar_vas_subject", "dezenove e noventa e nove"),
("validar_vas_subject", "Tamboro Mensal"),
]
assert state["transaction_parameter_collection"]["mode"] == "current_turn"
assert state["transaction_parameter_reconciliation"]["trigger"] == "prevalidation_needs_parameter"
assert state["pending_tool_call"]["arguments"]["subject"] == "Tamboro Mensal"
assert state["transaction_status"] == "AWAITING_CONFIRMATION"
assert not state.get("confirmation_received")
assert result[-1]["awaiting_confirmation"] is True
@pytest.mark.asyncio