131 lines
4.9 KiB
Python
131 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from types import SimpleNamespace
|
|
|
|
from app.agents.faturas_agent import FaturasAgent
|
|
from app.domain.contas.workflow_actions import build_contas_workflow_actions
|
|
from contas_mcp.servers.contas_mcp_server import main as mcp_main
|
|
|
|
|
|
def test_invoice_explanation_handoff_action_is_structural_and_uses_configured_message():
|
|
class Service:
|
|
pass
|
|
|
|
action = build_contas_workflow_actions(Service()).get("preparar_handoff_invoice_explanation")
|
|
result = action(
|
|
{
|
|
"mensagem": "Para continuar com a sua solicitação, aguarde um instante.",
|
|
"reason": "invoice_explanation_not_resolved",
|
|
},
|
|
{"input": {}},
|
|
)
|
|
assert result["mensagem"] == "Para continuar com a sua solicitação, aguarde um instante."
|
|
assert result["session_control"] == "HUMAN_HANDOFF"
|
|
assert result["human_handoff_requested"] is True
|
|
assert result["handoff"] is True
|
|
assert result["session_ended"] is True
|
|
assert result["terminal_status"] == "human_handoff"
|
|
|
|
|
|
def test_result_payload_promotes_invoice_explanation_handoff_control():
|
|
result = {
|
|
"execution_id": "exec-1",
|
|
"status": "COMPLETED",
|
|
"output": {
|
|
"handoff_pos_explicacao_nao": {
|
|
"success": True,
|
|
"mensagem": "Para continuar com a sua solicitação, aguarde um instante.",
|
|
"session_control": "HUMAN_HANDOFF",
|
|
"handoff_reason": "invoice_explanation_not_resolved",
|
|
}
|
|
},
|
|
"state": {"current_node": "handoff_pos_explicacao_nao"},
|
|
"trace": [{"node": "handoff_pos_explicacao_nao", "status": "COMPLETED"}],
|
|
}
|
|
payload = mcp_main._result_payload(result, workflow_name="invoice_explanation")
|
|
assert payload["mensagem"] == "Para continuar com a sua solicitação, aguarde um instante."
|
|
assert payload["session_control"] == "HUMAN_HANDOFF"
|
|
assert payload["human_handoff_requested"] is True
|
|
assert payload["handoff"] is True
|
|
assert payload["session_ended"] is True
|
|
|
|
|
|
def test_faturas_agent_promotes_handoff_from_tool_context_to_graph_state():
|
|
context = [{
|
|
"tool_name": "retomar_workflow",
|
|
"ok": True,
|
|
"result": {
|
|
"workflow_name": "invoice_explanation",
|
|
"status": "COMPLETED",
|
|
"mensagem": "Para continuar com a sua solicitação, aguarde um instante.",
|
|
"session_control": "HUMAN_HANDOFF",
|
|
"human_handoff_requested": True,
|
|
"session_ended": True,
|
|
"terminal_status": "human_handoff",
|
|
"handoff_reason": "invoice_explanation_not_resolved",
|
|
},
|
|
}]
|
|
patch = FaturasAgent._handoff_patch_from_tool_context(context)
|
|
assert patch == {
|
|
"session_control": "HUMAN_HANDOFF",
|
|
"human_handoff_requested": True,
|
|
"session_ended": True,
|
|
"terminal_status": "human_handoff",
|
|
"handoff_reason": "invoice_explanation_not_resolved",
|
|
}
|
|
|
|
|
|
def test_completed_terminal_workflow_short_circuits_llm_composition_directive():
|
|
agent = FaturasAgent.__new__(FaturasAgent)
|
|
context = [{
|
|
"tool_name": "retomar_workflow",
|
|
"ok": True,
|
|
"result": {
|
|
"status": "COMPLETED",
|
|
"workflow_name": "generic_workflow",
|
|
# Earlier node still carries a composition directive. It must not win
|
|
# over the terminal last node.
|
|
"requires_llm_composition": True,
|
|
"response_instruction": "Componha uma nova resposta.",
|
|
"output": {
|
|
"formatar": {
|
|
"requires_llm_composition": True,
|
|
"mensagem": "mensagem anterior",
|
|
},
|
|
"final": {
|
|
"mensagem": "Encaminhando para atendimento humano.",
|
|
"session_control": "HUMAN_HANDOFF",
|
|
"handoff": True,
|
|
"session_ended": True,
|
|
"terminal_status": "human_handoff",
|
|
},
|
|
},
|
|
"state": {"current_node": "final"},
|
|
},
|
|
}]
|
|
answer = agent.build_direct_mcp_answer({}, context, agent_label="AnyAgent")
|
|
assert answer == "Encaminhando para atendimento humano."
|
|
|
|
|
|
def test_result_payload_promotes_generic_terminal_last_node_contract():
|
|
result = {
|
|
"execution_id": "exec-generic",
|
|
"status": "COMPLETED",
|
|
"output": {
|
|
"final": {
|
|
"mensagem": "Sessão encerrada.",
|
|
"session_control": "END_SESSION",
|
|
"session_ended": True,
|
|
"terminal_status": "done",
|
|
}
|
|
},
|
|
"state": {"current_node": "final"},
|
|
"trace": [{"node": "final", "status": "COMPLETED"}],
|
|
}
|
|
payload = mcp_main._result_payload(result, workflow_name="generic_example")
|
|
assert payload["terminal"] is True
|
|
assert payload["terminal_action"] == "end_session"
|
|
assert payload["mensagem"] == "Sessão encerrada."
|
|
assert payload["session_ended"] is True
|