Ajustes no agente de contas

This commit is contained in:
2026-08-31 18:02:54 -03:00
parent d6c136017a
commit 1a31478bfe
72 changed files with 131 additions and 0 deletions

View File

@@ -1,6 +1,8 @@
from app.agents.prompting import apply_agent_profile_prompt from app.agents.prompting import apply_agent_profile_prompt
from app.agents.contas_prompting import load_domain_prompt from app.agents.contas_prompting import load_domain_prompt
from app.agents.runtime import AgentRuntimeMixin from app.agents.runtime import AgentRuntimeMixin
import re
import unicodedata
class FaturasAgent(AgentRuntimeMixin): class FaturasAgent(AgentRuntimeMixin):
@@ -31,6 +33,74 @@ class FaturasAgent(AgentRuntimeMixin):
self.guardrail_pipeline = guardrail_pipeline self.guardrail_pipeline = guardrail_pipeline
@staticmethod
def _normalize_text(value):
text = unicodedata.normalize("NFKD", str(value or ""))
return "".join(ch for ch in text if not unicodedata.combining(ch)).lower().strip()
@classmethod
def _is_live_internet_balance_request(cls, state):
text = cls._normalize_text(
state.get("sanitized_input") or state.get("user_text") or state.get("input") or ""
)
if not text:
return False
internet = bool(re.search(r"\b(internet|dados moveis|dados|franquia)\b", text))
live_balance = bool(
re.search(
r"\b(quanto.*(?:resta|restou|tenho|sobrou|disponivel)|"
r"saldo|restante|restam|ainda tenho|ainda posso usar|consumo atual)\b",
text,
)
)
return internet and live_balance
@classmethod
def _has_live_internet_balance_evidence(cls, tool_context):
# Não bloqueia uma integração futura que passe a devolver o saldo em tempo real.
evidence_keys = {
"saldo_internet",
"internet_balance",
"remaining_data",
"remaining_data_mb",
"remaining_data_gb",
"saldo_dados",
"franquia_disponivel",
"consumo_atual",
"current_usage",
}
def walk(value):
if isinstance(value, dict):
for key, nested in value.items():
if cls._normalize_text(key).replace(" ", "_") in evidence_keys and nested not in (None, "", [], {}):
return True
if walk(nested):
return True
elif isinstance(value, list):
return any(walk(item) for item in value)
return False
for item in tool_context or []:
if not isinstance(item, dict) or item.get("ok") is False:
continue
if walk(item.get("result")):
return True
return False
@classmethod
def _live_internet_balance_guidance(cls, state, tool_context):
if not cls._is_live_internet_balance_request(state):
return None
if cls._has_live_internet_balance_evidence(tool_context):
return None
return (
"Não consigo consultar o saldo de internet em tempo real por aqui. "
"Para ver quanto ainda resta neste mês, consulte o app Meu TIM, "
"onde você acompanha o consumo atual da sua franquia."
)
@staticmethod @staticmethod
def _handoff_patch_from_tool_context(tool_context): def _handoff_patch_from_tool_context(tool_context):
for item in tool_context or []: for item in tool_context or []:
@@ -93,6 +163,17 @@ class FaturasAgent(AgentRuntimeMixin):
} }
return result return result
live_balance_guidance = self._live_internet_balance_guidance(state, tool_context)
if live_balance_guidance:
return {
"answer": f"[FaturasAgent] {live_balance_guidance}",
"next_state": state.get("next_state") or "ACTIVE",
"mcp_results": tool_context,
"rag": {"enabled": False, "skipped": True, "reason": "live_internet_balance_not_available"},
**self.transaction_state_patch(state),
**handoff_patch,
}
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="FaturasAgent") direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="FaturasAgent")
if direct_answer: if direct_answer:
return { return {

View File

@@ -0,0 +1,29 @@
# Correção do cenário 23 — saldo de internet em tempo real
## Problema
O FaturasAgent respondia que não havia informação nos "dados consultados" sobre o saldo de internet restante mesmo quando nenhuma tool de consumo em tempo real havia sido executada. Isso criava uma alegação de consulta sem evidência funcional.
## Solução
A correção foi mantida no agente, sem alteração do framework. Para perguntas explícitas de saldo/franquia restante em tempo real, o FaturasAgent:
1. verifica se alguma tool bem-sucedida trouxe evidência de saldo/consumo atual;
2. se houver evidência, deixa o fluxo normal responder com os dados reais;
3. se não houver evidência, não afirma que consultou dados e orienta o cliente ao Meu TIM.
Resposta de fallback:
> Não consigo consultar o saldo de internet em tempo real por aqui. Para ver quanto ainda resta neste mês, consulte o app Meu TIM, onde você acompanha o consumo atual da sua franquia.
A regra não intercepta perguntas genéricas sobre internet/plano e não bloqueia uma integração futura que passe a devolver saldo real.
## Arquivo alterado
- `app/agents/faturas_agent.py`
## Testes
- `tests/migration/test_scenario_23_live_internet_balance_guidance.py`
- 3/3 testes novos PASS
- regressão `tests/migration`: 821 PASS / 2 FAIL preexistentes

View File

@@ -0,0 +1,21 @@
from app.agents.faturas_agent import FaturasAgent
def test_s23_live_balance_without_backend_uses_meu_tim_guidance():
state = {"user_text": "quanto de internet eu ainda tenho pra usar esse mês?"}
msg = FaturasAgent._live_internet_balance_guidance(state, [])
assert msg is not None
assert "Não consigo consultar o saldo de internet em tempo real por aqui" in msg
assert "Meu TIM" in msg
assert "dados consultados" not in msg
def test_s23_generic_internet_question_is_not_intercepted():
state = {"user_text": "como funciona a internet do meu plano?"}
assert FaturasAgent._live_internet_balance_guidance(state, []) is None
def test_s23_real_time_backend_evidence_takes_precedence():
state = {"user_text": "quanto de internet eu ainda tenho pra usar esse mês?"}
tools = [{"ok": True, "result": {"remaining_data_gb": 7.5}}]
assert FaturasAgent._live_internet_balance_guidance(state, tools) is None