From f9c66b4792ac9fd63d7397dbab3bcac310e4d780 Mon Sep 17 00:00:00 2001 From: "cristiano.hoshikawa" Date: Wed, 19 Aug 2026 16:08:03 -0300 Subject: [PATCH] Melhorias no guardrails de FRASEOLOGY. Melhorias no Transaction Workflow --- .env | 209 ------------------ .../src/agent_framework/guardrails/rails.py | 62 +++++- .../agent_framework/runtime/agent_runtime.py | 28 ++- 3 files changed, 87 insertions(+), 212 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 116eb7d..0000000 --- a/.env +++ /dev/null @@ -1,209 +0,0 @@ -############################################################################### -# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA -# Este arquivo é lido por Pydantic Settings no framework e no backend template. -############################################################################### - -APP_NAME=ai-agent-template -APP_ENV=local -LOG_LEVEL=INFO -API_HOST=0.0.0.0 -API_PORT=8000 -CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 - -############################################################################### -# LLM - OCI Generative AI como provider principal -############################################################################### -# Opções: mock, oci_openai, oci_sdk, openai_compatible -LLM_PROVIDER=oci_openai -LLM_TEMPERATURE=0.2 -LLM_MAX_TOKENS=2048 -LLM_TIMEOUT_SECONDS=120 - -# OCI OpenAI-compatible endpoint -OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1 -OCI_GENAI_MODEL=openai.gpt-4.1 -OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6 -OCI_GENAI_PROJECT_OCID= - -# OCI_AUTH_MODE=config_file|instance_principal|resource_principal -OCI_AUTH_MODE=config_file -# OCI SDK / signer / profiles -OCI_CONFIG_FILE=~/.oci/config -OCI_PROFILE=DEFAULT -OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -OCI_REGION=us-chicago-1 - -############################################################################### -# Persistência -############################################################################### -# Opções: memory, autonomous, mongodb -SESSION_REPOSITORY_PROVIDER=autonomous -MEMORY_REPOSITORY_PROVIDER=autonomous -CHECKPOINT_REPOSITORY_PROVIDER=autonomous - -# Autonomous Database -ADB_USER=admin -ADB_PASSWORD=fjhsdf04954hf -ADB_DSN=oradb23aidev_high -ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev -ADB_WALLET_PASSWORD=fjhsdf04954hf -ADB_TABLE_PREFIX=AGENTFW - -# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente -MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017 -MONGODB_DATABASE=agent_platform - -# Redis -REDIS_URL=redis://localhost:6379/0 -ENABLE_REDIS_CACHE=false - -############################################################################### -# RAG / Vector / Graph -############################################################################### -VECTOR_STORE_PROVIDER=memory -GRAPH_STORE_PROVIDER=memory -RAG_TOP_K=5 -EMBEDDING_PROVIDER=mock -OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 -RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json - -############################################################################### -# Observabilidade -############################################################################### -ENABLE_LANGFUSE=true -LANGFUSE_TRACE_MODE=verbose # Opcional: verbose, compact -LANGFUSE_ROOT_SPAN_NAME=agent.gateway_message -LANGFUSE_LEGACY_IO_FALLBACK=true -LANGFUSE_PUBLIC_KEY=pk-lf-bd9b0c7e-2b8b-4e5b-a382-284a9b4413b3 -LANGFUSE_SECRET_KEY=sk-lf-5f5cc18d-0bb5-424e-b5d0-cb3664d58c20 -LANGFUSE_HOST=http://localhost:3005 -ENABLE_OTEL=false -OTEL_EXPORTER_OTLP_ENDPOINT= -OTEL_SERVICE_NAME=ai-agent-template -ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true - -############################################################################### -# Analytics / Observer corporativo -############################################################################### -# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo. -ENABLE_ANALYTICS=false -# Providers aceitos: oci_streaming,pubsub,noop -ANALYTICS_PROVIDERS=pubsub -# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente. -AGENT_PUBSUB_TOPIC= -GCP_PUBSUB_TOPIC_PATH= -GCP_PROJECT_ID= -GCP_PUBSUB_TOPIC= -GCP_PUBSUB_TIMEOUT_SECONDS=30 -# Credencial GCP segue padrão Google: -# GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-service-account.json - -############################################################################### -# OCI Streaming -############################################################################### -ENABLE_OCI_STREAMING=false -OCI_STREAM_ENDPOINT= -OCI_STREAM_OCID= -OCI_STREAM_PARTITION_KEY=agent-events - -############################################################################### -# Guardrails, Judges, Supervisor -############################################################################### -ENABLE_INPUT_GUARDRAILS=true -ENABLE_OUTPUT_GUARDRAILS=true -ENABLE_JUDGES=true -ENABLE_SUPERVISOR=true -ENABLE_OUTPUT_SUPERVISOR=true -ENABLE_PARALLEL_GUARDRAILS=true -GUARDRAILS_FAIL_FAST=true -OUTPUT_SUPERVISOR_MAX_RETRIES=3 -GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml -JUDGES_CONFIG_PATH=./config/judges.yaml -PROMPT_POLICY_PATH=./config/prompt_policy.yaml - -############################################################################### -# Gateway de canais -############################################################################### -DEFAULT_CHANNEL=web -# embedded = backend may parse simple/native channel payloads. -# external = backend only accepts GatewayRequest normalized by an external Channel Gateway. -FRAMEWORK_CHANNEL_INPUT_MODE=embedded -ENABLE_VOICE_ADAPTER=true -ENABLE_WHATSAPP_ADAPTER=true -ENABLE_TEXT_ADAPTER=true - -################################################# -# ENTERPRISE ROUTING -################################################# -# Arquivo YAML com intents, keywords, políticas de estado e fallback. -ROUTING_CONFIG_PATH=./config/routing.yaml -# true = usa LLM para classificar quando keywords/estado não resolverem. -# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência. -ENABLE_LLM_ROUTER=true - -# Semantic route stickiness (optional). -# Uses a lightweight LLM profile to decide only CONTINUE vs ROUTE. -# There are no regexes or deterministic language rules. -ENABLE_ROUTE_STICKINESS=false -ROUTE_STICKINESS_LLM_PROFILE=route_continuity -ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90 -ROUTE_STICKINESS_HISTORY_TURNS=2 -ROUTE_STICKINESS_MAX_TOKENS=80 -HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa. -END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato. - -############################################################################### -# MCP / Tools -############################################################################### -ENABLE_MCP_TOOLS=true -MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml -TOOLS_CONFIG_PATH=./config/tools.yaml -TOOL_POLICIES_PATH=./config/tool_policies.yaml -MCP_TOOL_TIMEOUT_SECONDS=30 - -# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes -ROUTING_MODE=router - -# Usage/cost accounting -USAGE_REPOSITORY_PROVIDER=autonomous -IDENTITY_CONFIG_PATH=./config/identity.yaml -MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml - -# ----------------------------------------------------------------------------- -# ConversationSummaryMemory / compressão de contexto conversacional -# ----------------------------------------------------------------------------- -ENABLE_CONVERSATION_SUMMARY_MEMORY=true -MEMORY_CONTEXT_STRATEGY=summary -MEMORY_HISTORY_LIMIT=80 -MEMORY_RECENT_MESSAGES_LIMIT=8 -MEMORY_SUMMARY_TRIGGER_MESSAGES=20 -MEMORY_MAX_SUMMARY_CHARS=6000 -MEMORY_SUMMARY_USE_LLM=true -MEMORY_INJECT_RECENT_MESSAGES=true -MEMORY_INJECT_SUMMARY=true - -############################################################################### -# MCP Gateway -############################################################################### -# true = framework routes tool calls to the dedicated MCP Gateway. -# false = framework calls MCP servers directly from mcp_servers.yaml. -MCP_GATEWAY_ENABLED=true -MCP_GATEWAY_URL=http://localhost:8300 -MCP_GATEWAY_TIMEOUT_SECONDS=60 -# MCP_GATEWAY_TOKEN= -MCP_GATEWAY_AGENT_ID=telecom_contas -MCP_GATEWAY_TENANT_ID=default - -############################################################################### -# LONG-TERM MEMORY -############################################################################### -ENABLE_LONG_TERM_MEMORY=true -LONG_TERM_MEMORY_PROVIDER=sqlite -LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db -LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory -# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY -# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY -LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20 -LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70 -LONG_TERM_MEMORY_AUTO_EXTRACT=true -LONG_TERM_MEMORY_INJECT_CONTEXT=true diff --git a/libs/agent_framework/src/agent_framework/guardrails/rails.py b/libs/agent_framework/src/agent_framework/guardrails/rails.py index 87f4d73..810e694 100644 --- a/libs/agent_framework/src/agent_framework/guardrails/rails.py +++ b/libs/agent_framework/src/agent_framework/guardrails/rails.py @@ -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}, ) diff --git a/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py b/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py index d625df3..2580f43 100644 --- a/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py +++ b/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py @@ -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