bugfixes: transaction parameter collector, generic formatting messages, guardrails, prompts. Testing contas

This commit is contained in:
2026-08-20 00:23:50 -03:00
parent f9c66b4792
commit 762f6fb024
231 changed files with 3999 additions and 141 deletions

View File

@@ -1,6 +1,7 @@
pyproject.toml
src/agent_framework/__init__.py
src/agent_framework/gateway_policy_context.py
src/agent_framework/idempotency.py
src/agent_framework/observer.py
src/agent_framework/runtime_mcp_gateway_adapter.py
src/agent_framework.egg-info/PKG-INFO
@@ -28,6 +29,8 @@ src/agent_framework/channels/__init__.py
src/agent_framework/channels/adapters.py
src/agent_framework/channels/base.py
src/agent_framework/channels/gateway.py
src/agent_framework/channels/interruption.py
src/agent_framework/channels/transcription.py
src/agent_framework/checkpoints/__init__.py
src/agent_framework/checkpoints/checkpoint_repository.py
src/agent_framework/checkpoints/langgraph_saver.py
@@ -73,9 +76,11 @@ src/agent_framework/guardrails/calibrated/pipeline.py
src/agent_framework/guardrails/calibrated/prompts/__init__.py
src/agent_framework/guardrails/calibrated/prompts/_context.py
src/agent_framework/guardrails/calibrated/prompts/ausencia_oferta_proativa.py
src/agent_framework/guardrails/calibrated/prompts/coerencia.py
src/agent_framework/guardrails/calibrated/prompts/dlex_in.py
src/agent_framework/guardrails/calibrated/prompts/dlex_out.py
src/agent_framework/guardrails/calibrated/prompts/fallback.py
src/agent_framework/guardrails/calibrated/prompts/fraseologia.py
src/agent_framework/guardrails/calibrated/prompts/out_of_scope.py
src/agent_framework/guardrails/calibrated/prompts/pinj.py
src/agent_framework/guardrails/calibrated/prompts/ragsec.py
@@ -174,6 +179,8 @@ src/agent_framework/persistence/__init__.py
src/agent_framework/persistence/mongodb_store.py
src/agent_framework/persistence/oracle_store.py
src/agent_framework/persistence/sqlite_store.py
src/agent_framework/presentation/__init__.py
src/agent_framework/presentation/renderers.py
src/agent_framework/rag/__init__.py
src/agent_framework/rag/embedding_provider.py
src/agent_framework/rag/graph_store.py
@@ -200,6 +207,7 @@ src/agent_framework/supervisor/__init__.py
src/agent_framework/supervisor/router_supervisor.py
src/agent_framework/supervisor/supervisor.py
src/agent_framework/workflows/__init__.py
src/agent_framework/workflows/graph.py
src/agent_framework/workflows/models.py
src/agent_framework/workflows/registry.py
src/agent_framework/workflows/repository.py

View File

@@ -129,6 +129,8 @@ def _is_vas_section_name(section_name: str) -> bool:
or "servicos de valor adicionado" in normalized
or "servicos valor adicionado" in normalized
or "sva detalhe total" in normalized
or "servicos contratados de parceiros" in normalized
or "servico contratado de parceiro" in normalized
)
@@ -185,13 +187,23 @@ def _extract_contestation_invoice_items(
"validatedAmount",
)
if candidate_name and candidate_amount is not None and candidate_amount > 0:
payload_type = str(payload.get("type") or payload.get("tipo") or "").strip()
payload_desc = str(payload.get("desc") or "").strip()
classe = str(payload.get("classe", "")).strip().lower()
is_vas = (
_is_vas_section_name(section_name)
or _is_vas_section_name(payload_type)
or classe in {"avulso", "estrategico"}
)
found.append(
{
"name": candidate_name,
"amount": _money(candidate_amount),
"is_vas": _is_vas_section_name(section_name),
"is_vas": is_vas,
"section": section_name,
"classe": str(payload.get("classe", "")).strip().lower(),
"source_type": payload_type,
"source_desc": payload_desc,
"classe": classe,
"estrategico": bool(payload.get("estrategico")),
"verb": str(payload.get("verb", "")).strip().lower(),
}
@@ -397,14 +409,25 @@ def validate_contestation_items(
"vas_estrategico": False,
"status": "em_validacao",
}
matched_candidate = next(
(
candidate
for candidate in candidates
if _is_same_plan_name(candidate.get("name", ""), item_name)
),
None,
matching_candidates = [
candidate
for candidate in candidates
if _is_same_plan_name(candidate.get("name", ""), item_name)
]
# A mesma cobrança pode aparecer em múltiplas visões da fatura.
# Prefira a evidência que traz classificação explícita de VAS em vez
# de aceitar a primeira ocorrência genérica e concluir incorretamente
# que o item está fora da seção VAS.
matching_candidates.sort(
key=lambda candidate: (
0 if (
str(candidate.get("classe", "")).strip().lower() in {"avulso", "estrategico"}
or bool(candidate.get("is_vas"))
) else 1,
0 if _normalize_match_text(candidate.get("name", "")) == _normalize_match_text(item_name) else 1,
)
)
matched_candidate = matching_candidates[0] if matching_candidates else None
if matched_candidate is None:
_record_failure(
item_log,
@@ -414,6 +437,9 @@ def validate_contestation_items(
continue
item_log["item_na_fatura"] = True
item_log["item_confirmado"] = True
item_log["item_fatura_resolvido"] = str(matched_candidate.get("name", "") or "")
item_log["secao_fatura"] = str(matched_candidate.get("section", "") or "")
item_log["tipo_fatura"] = str(matched_candidate.get("source_type", "") or "")
classe = str(matched_candidate.get("classe", "")).strip().lower()
is_strategic = (

View File

@@ -328,47 +328,6 @@ class ProactiveOfferRail(Guardrail):
)
def _sanitize_low_risk_phraseology(text: str, reason: str) -> str | None:
"""Remove apenas fechamentos/redirecionamentos de baixo risco.
FRASEOLOGIA continua fail-closed para conteúdo material. Para B4 e ofertas
genéricas de continuação, porém, bloquear toda uma resposta grounded piora a
UX; nesses casos removemos somente a sentença ofensora.
"""
normalized_reason = (reason or "").casefold()
low_risk = any(token in normalized_reason for token in (
"viola b4", "outro canal", "atendimento especializado",
"realizar alguma ação", "oferta de ação", "orienta o cliente",
))
if not low_risk:
return None
forbidden = (
"entre em contato", "fale com um atendente", "procure uma loja",
"acesse o app", "acesse o site", "atendimento especializado",
"área de planos", "area de planos", "é só me avisar",
"e so me avisar", "realizar alguma ação", "realizar alguma acao",
"gerenciar esses serviços", "gerenciar esses servicos",
)
sentences = re.split(r"(?<=[.!?])\s+", (text or "").strip())
kept: list[str] = []
removed = False
for sentence in sentences:
normalized = sentence.casefold()
proactive = (
("se quiser" in normalized or "caso queira" in normalized or "se desejar" in normalized)
and any(token in normalized for token in ("realizar", "gerenciar", "cancelar", "contratar", "alterar", "ação", "acao"))
)
if proactive or any(token in normalized for token in forbidden):
removed = True
continue
kept.append(sentence.strip())
sanitized = " ".join(x for x in kept if x).strip()
if removed and sanitized:
return sanitized
return None
class PhraseologyRail(Guardrail):
"""FRASEOLOGIA calibrado: bloqueia fraseados proibidos do agente."""
code = "FRASEOLOGIA"
@@ -380,26 +339,9 @@ class PhraseologyRail(Guardrail):
_llm(ctx), "FRASEOLOGIA", {"text": text or "", "context": ctx},
profile_name="grl", component_name="guardrail.fraseologia", generation_name="guardrail.fraseologia",
)
allowed = bool(out.get("allowed", True))
reason = str(out.get("reason") or out.get("label") or "FRASEOLOGIA avaliado")
if not allowed:
sanitized = _sanitize_low_risk_phraseology(text or "", reason)
if sanitized:
return RailDecision(
code=self.code,
allowed=True,
reason=f"FRASEOLOGIA sanitizada: {reason}",
sanitized_text=sanitized,
metadata={
"mechanism": "llm_rail+deterministic_sanitize",
"data": out,
"calibrated": True,
"original_allowed": False,
},
)
return RailDecision(
code=self.code, allowed=allowed,
reason=reason,
code=self.code, allowed=bool(out.get("allowed", True)),
reason=str(out.get("reason") or out.get("label") or "FRASEOLOGIA avaliado"),
sanitized_text=text, metadata={"mechanism": "llm_rail", "data": out, "calibrated": True},
)

Some files were not shown because too many files have changed in this diff Show More