Framework: Melhoria no Transaction
This commit is contained in:
@@ -328,6 +328,47 @@ 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"
|
||||
@@ -339,9 +380,26 @@ 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=bool(out.get("allowed", True)),
|
||||
reason=str(out.get("reason") or out.get("label") or "FRASEOLOGIA avaliado"),
|
||||
code=self.code, allowed=allowed,
|
||||
reason=reason,
|
||||
sanitized_text=text, metadata={"mechanism": "llm_rail", "data": out, "calibrated": True},
|
||||
)
|
||||
|
||||
|
||||
@@ -1167,7 +1167,25 @@ class AgentRuntimeMixin:
|
||||
return None
|
||||
|
||||
def _select_transactional_tool(self, tools: list[str], text: str) -> str | None:
|
||||
return self._transactional_action_match(text, tools)
|
||||
"""Seleciona a ação transacional da intent atual.
|
||||
|
||||
O match por ``selection_keywords`` continua tendo precedência. Porém, depois
|
||||
que o EnterpriseRouter já restringiu ``tools`` às capabilities da intent,
|
||||
uma única tool transacional é uma escolha determinística e segura. Isso
|
||||
evita perder frases naturais como ``quero cancelar TIM Fashion Mensal`` ou
|
||||
``não contratei esse serviço`` só porque elas não repetem literalmente uma
|
||||
keyword de ``tools.yaml``.
|
||||
"""
|
||||
matched = self._transactional_action_match(text, tools)
|
||||
if matched:
|
||||
return matched
|
||||
|
||||
transactional = [
|
||||
tool
|
||||
for tool in tools
|
||||
if self._resolve_tool_execution_policy(tool).get("operation_type") == "transactional"
|
||||
]
|
||||
return transactional[0] if len(transactional) == 1 else None
|
||||
|
||||
@staticmethod
|
||||
def _agent_state_prefix(agent_name: str | None) -> str:
|
||||
@@ -1521,6 +1539,11 @@ class AgentRuntimeMixin:
|
||||
**previous_args,
|
||||
**{k: v for k, v in new_args.items() if v not in (None, "", [], {})},
|
||||
}
|
||||
# Execute parameter extraction before deciding whether the workflow
|
||||
# must enter COLLECTING_PARAMETERS. Otherwise parameters declared
|
||||
# with strategy=llm in mcp_parameter_mapping.yaml are invisible to
|
||||
# the deterministic transaction state machine.
|
||||
arguments = await self._extract_mcp_parameters(tool_name, arguments, state)
|
||||
policy = self._resolve_tool_execution_policy(tool_name, arguments)
|
||||
missing = self._missing_required_arguments(policy, arguments)
|
||||
if missing:
|
||||
@@ -1653,6 +1676,9 @@ class AgentRuntimeMixin:
|
||||
aliases=aliases,
|
||||
extra_args=self._extract_action_arguments(text),
|
||||
)
|
||||
# Extract parameters (including LLM-declared extraction rules) before
|
||||
# validating required fields and before persisting the pending call.
|
||||
action_args = await self._extract_mcp_parameters(selected_action, action_args, state)
|
||||
policy = self._resolve_tool_execution_policy(selected_action, action_args)
|
||||
selected = {"tool_name": selected_action, "arguments": action_args}
|
||||
state["selected_tool_call"] = selected
|
||||
|
||||
Reference in New Issue
Block a user