bugfix: route stickness precedences (transaction in the same intent)
This commit is contained in:
BIN
app/__pycache__/state.cpython-313.pyc
Normal file
BIN
app/__pycache__/state.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/agents/__pycache__/contas_prompting.cpython-313.pyc
Normal file
BIN
app/agents/__pycache__/contas_prompting.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/agents/__pycache__/contestacao_agent.cpython-313.pyc
Normal file
BIN
app/agents/__pycache__/contestacao_agent.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/agents/__pycache__/faturas_agent.cpython-313.pyc
Normal file
BIN
app/agents/__pycache__/faturas_agent.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/agents/__pycache__/prompting.cpython-313.pyc
Normal file
BIN
app/agents/__pycache__/prompting.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/agents/__pycache__/runtime.cpython-313.pyc
Normal file
BIN
app/agents/__pycache__/runtime.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/agents/__pycache__/suporte_contas_agent.cpython-313.pyc
Normal file
BIN
app/agents/__pycache__/suporte_contas_agent.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/agents/__pycache__/vas_agent.cpython-313.pyc
Normal file
BIN
app/agents/__pycache__/vas_agent.cpython-313.pyc
Normal file
Binary file not shown.
@@ -3,5 +3,8 @@ from __future__ import annotations
|
||||
# Compatibilidade local do template/backend.
|
||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||
from app.presentation import register_tool_renderers
|
||||
|
||||
register_tool_renderers()
|
||||
|
||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
||||
|
||||
BIN
app/domain/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
app/domain/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/client.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/client.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/contestation_rules.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/contestation_rules.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/ic_tags.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/ic_tags.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/domain/contas/__pycache__/invoice_context.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/invoice_context.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/invoice_models.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/invoice_models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/invoice_resolver.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/invoice_resolver.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/item_matcher.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/item_matcher.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/pro_rata_rules.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/pro_rata_rules.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/protocol_triplets.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/protocol_triplets.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/rct_policy.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/rct_policy.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/service.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/service.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/string_metrics.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/string_metrics.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/domain/contas/__pycache__/vas_variation.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/vas_variation.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/domain/contas/__pycache__/workflow_actions.cpython-313.pyc
Normal file
BIN
app/domain/contas/__pycache__/workflow_actions.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/domain/contas/parsers/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
app/domain/contas/parsers/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -37,6 +37,47 @@ class ContasDomainService:
|
||||
def consultar_faturas(self, *, msisdn: str, **_: Any) -> Any:
|
||||
return self.client.consultar_faturas(msisdn)
|
||||
|
||||
def consultar_plano(self, *, msisdn: str, **args: Any) -> dict[str, Any]:
|
||||
"""Retorna somente os planos presentes na evidência de billing analysis.
|
||||
|
||||
A consulta é deliberadamente determinística: não usa VAS, RAG nem o
|
||||
workflow de invoice_explanation para responder qual é o plano do cliente.
|
||||
"""
|
||||
billing = args.get("billing_analysis")
|
||||
if not isinstance(billing, dict):
|
||||
billing = self.client.billing_analysis(
|
||||
msisdn,
|
||||
invoice_id=args.get("invoice_id"),
|
||||
customer_id=args.get("customer_id"),
|
||||
)
|
||||
|
||||
plans: list[dict[str, Any]] = []
|
||||
for section in billing.get("currentInvoice") or [] if isinstance(billing, dict) else []:
|
||||
if not isinstance(section, dict):
|
||||
continue
|
||||
section_type = str(section.get("type") or "").strip().casefold()
|
||||
section_desc = str(section.get("desc") or "").strip().casefold()
|
||||
if section_type not in {"plano", "planos"} and section_desc not in {"plano", "planos"}:
|
||||
continue
|
||||
for item in section.get("items") or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
name = str(item.get("desc") or item.get("name") or "").strip()
|
||||
if not name:
|
||||
continue
|
||||
plans.append({
|
||||
"name": name,
|
||||
"value": item.get("value"),
|
||||
"type": str(item.get("type") or section.get("type") or "plano"),
|
||||
"contestable": item.get("contestable"),
|
||||
})
|
||||
|
||||
return {
|
||||
"plans": plans,
|
||||
"count": len(plans),
|
||||
"source": "billing_analysis.currentInvoice",
|
||||
}
|
||||
|
||||
def invoice_explanation(self, *, msisdn: str, **args: Any) -> dict[str, Any]:
|
||||
# Reuse framework-prefetched evidence when available; the domain never owns
|
||||
# a second cache/session implementation. Explanation itself is produced by
|
||||
|
||||
BIN
app/observability/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
app/observability/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/observability/__pycache__/telemetry_observer.cpython-313.pyc
Normal file
BIN
app/observability/__pycache__/telemetry_observer.cpython-313.pyc
Normal file
Binary file not shown.
3
app/presentation/__init__.py
Normal file
3
app/presentation/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .tool_renderers import register_tool_renderers
|
||||
|
||||
__all__ = ["register_tool_renderers"]
|
||||
BIN
app/presentation/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
app/presentation/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/presentation/__pycache__/tool_renderers.cpython-313.pyc
Normal file
BIN
app/presentation/__pycache__/tool_renderers.cpython-313.pyc
Normal file
Binary file not shown.
108
app/presentation/tool_renderers.py
Normal file
108
app/presentation/tool_renderers.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.presentation import register_tool_response_renderer
|
||||
|
||||
|
||||
def _money_brl(value: Any) -> str:
|
||||
try:
|
||||
return f"{float(value):.2f}".replace(".", ",")
|
||||
except (TypeError, ValueError):
|
||||
return str(value)
|
||||
|
||||
|
||||
def render_contas_plan(
|
||||
*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str
|
||||
) -> str | None:
|
||||
plans = result.get("plans")
|
||||
if not isinstance(plans, list) or not plans:
|
||||
return None
|
||||
|
||||
lines: list[str] = []
|
||||
for plan in plans:
|
||||
if not isinstance(plan, dict):
|
||||
continue
|
||||
name = plan.get("name")
|
||||
if not name:
|
||||
continue
|
||||
value = plan.get("value")
|
||||
line = f"- {name}"
|
||||
if value is not None:
|
||||
line += f": R$ {_money_brl(value)}"
|
||||
lines.append(line)
|
||||
|
||||
if not lines:
|
||||
return None
|
||||
if len(lines) == 1:
|
||||
return f"[{agent_label}] Seu plano é:\n{lines[0]}"
|
||||
return f"[{agent_label}] Existem {len(lines)} planos associados à sua conta:\n" + "\n".join(lines)
|
||||
|
||||
|
||||
|
||||
def _service_value(service: dict[str, Any]) -> Any:
|
||||
details = service.get("details") if isinstance(service.get("details"), dict) else {}
|
||||
for candidate in (
|
||||
details.get("valor"),
|
||||
details.get("value"),
|
||||
service.get("valor"),
|
||||
service.get("value"),
|
||||
service.get("price"),
|
||||
):
|
||||
if candidate not in (None, ""):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _money_brl_preserve(value: Any) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
text = str(value).strip()
|
||||
if not text:
|
||||
return ""
|
||||
# TIM APIs may already return BRL decimal strings such as "10,00".
|
||||
# Normalize only when needed and never invent a value.
|
||||
if "," in text and "." not in text:
|
||||
try:
|
||||
return f"{float(text.replace(',', '.')):.2f}".replace(".", ",")
|
||||
except ValueError:
|
||||
return text
|
||||
return _money_brl(value)
|
||||
|
||||
|
||||
def render_contas_vas(
|
||||
*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str
|
||||
) -> str | None:
|
||||
services = result.get("products") or result.get("services")
|
||||
if not isinstance(services, list) or not services:
|
||||
return None
|
||||
|
||||
lines: list[str] = []
|
||||
for service in services:
|
||||
if not isinstance(service, dict):
|
||||
continue
|
||||
name = service.get("name") or service.get("description") or service.get("billDescription")
|
||||
if not name:
|
||||
continue
|
||||
|
||||
details = service.get("details") if isinstance(service.get("details"), dict) else {}
|
||||
can = service.get("can") if isinstance(service.get("can"), dict) else {}
|
||||
details_can = details.get("can") if isinstance(details.get("can"), dict) else {}
|
||||
cancelable = bool(can.get("cancel", details_can.get("cancel", False)))
|
||||
category = service.get("classe") or details.get("classe") or ("avulso" if cancelable else None)
|
||||
value = _service_value(service)
|
||||
|
||||
line = f"- {name}"
|
||||
if category:
|
||||
line += f" ({category})"
|
||||
if value not in (None, ""):
|
||||
line += f": R$ {_money_brl_preserve(value)}"
|
||||
lines.append(line)
|
||||
|
||||
if not lines:
|
||||
return None
|
||||
return f"[{agent_label}] Você possui os seguintes serviços ativos:\n" + "\n".join(lines)
|
||||
|
||||
def register_tool_renderers() -> None:
|
||||
register_tool_response_renderer("contas.plan", render_contas_plan)
|
||||
register_tool_response_renderer("contas.vas", render_contas_vas)
|
||||
Binary file not shown.
Reference in New Issue
Block a user