From 9af4e10a9295a52f49a6e532402c4ba2051db9cf Mon Sep 17 00:00:00 2001 From: Cristiano Hoshikawa Date: Mon, 15 Jun 2026 07:51:07 -0300 Subject: [PATCH] bugfix --- .env | 240 ----- FRAMEWORK_NATIVE_CORRECTIONS.md | 31 + README.md | 889 ++++++++++++++++++ app/agents/runtime.py | 273 +----- app/workflows/backoffice_workflow_executor.py | 5 +- .../agents/backoffice_anatel/guardrails.yaml | 34 +- config/agents/backoffice_anatel/judges.yaml | 13 + config/guardrails.yaml | 26 + config/judges.yaml | 30 +- config/tools.yaml | 50 + 10 files changed, 1057 insertions(+), 534 deletions(-) delete mode 100644 .env create mode 100644 FRAMEWORK_NATIVE_CORRECTIONS.md create mode 100644 README.md diff --git a/.env b/.env deleted file mode 100644 index cd6feb8..0000000 --- a/.env +++ /dev/null @@ -1,240 +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-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS -OCI_GENAI_PROJECT_OCID= - -# OCI SDK / signer / profiles -OCI_CONFIG_FILE=~/.oci/config -OCI_PROFILE=LATINOAMERICA-Chicago -OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q -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=Moniquinha1972 -ADB_DSN=oradb23ai_high -ADB_WALLET_LOCATION=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai -ADB_WALLET_PASSWORD=Moniquinha1972 -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 - -############################################################################### -# Observabilidade -############################################################################### -ENABLE_LANGFUSE=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 - -############################################################################### -# 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=oci_streaming -# 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 -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 - -############################################################################### -# MCP / Tools -############################################################################### -ENABLE_MCP_TOOLS=true -MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml -TOOLS_CONFIG_PATH=./config/tools.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 - -############################################################################### -# BACKOFFICE CONVERTIDO - CHAVES ADICIONADAS A PARTIR DO .env.example -# Valores existentes no .env original foram preservados. Revise placeholders. -############################################################################### - -# Framework -DEFAULT_AGENT_ID=backoffice_anatel -ENABLE_SSE=true -SQLITE_DB_PATH=.local/backoffice_framework.db -LLM_MODEL=cohere.command-r-08-2024 -# OCI OpenAI-compatible endpoint/credentials. Preencha conforme seu Agent Framework. -OCI_OPENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/chat -OCI_OPENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS -# OPENAI_API_KEY= -OCI_CONFIG_PROFILE=LATINOAMERICA-Chicago -# OCI_REGION=us-chicago-1 -# OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q - -# MCP -MCP_ENABLED=true -DEBUG=true -# IMDB_API_HOST= -# SIEBEL_API_HOST= -# SIEBEL_API_ROUTE= -# SIEBEL_STATUS_API_HOST= -# SIEBEL_PREPAGO_STATUS_API_HOST= -# SPEECH_ANALYTICS_API_HOST= -# TAIS_KB_DB_USER= -# TAIS_KB_DB_PASSWORD= -# TAIS_KB_DSN= -# OCI_REQUEST_STREAM_OCID= -# OCI_RESPONSE_STREAM_OCID= -# OCI_CONSUMER_GROUP_NAME= - -# MCP Server local do backoffice -# O backend principal chama esse endpoint pelo MCPToolRouter do framework. -BACKOFFICE_MCP_BASE_URL=http://localhost:8010 -BACKOFFICE_MCP_USE_MOCK=false -BACKOFFICE_MCP_BACKEND_TYPE=tim_clients -BACKOFFICE_MCP_FAIL_OPEN_ON_BACKEND_ERROR=true - -# TIM/develop original - preencha no .env real, nunca versionar segredos. -PMID_API_HOST=http://localhost:8011/access/v1/info -PMID_API_BASIC_TOKEN=Basic dGlteDpAckp1SkpAcFJPUiM= -PMID_API_TIMEOUT=10 -PMID_API_CLIENT_ID=AIAGENTCR -SIEBEL_API_HOST=http://localhost:8011 -SIEBEL_API_ROUTE=/customers/v1/backOfficeSRopening -SIEBEL_API_TIMEOUT=10 -SIEBEL_API_USERNAME=aiagentcr -SIEBEL_API_PASSWORD=AiAgentCR#FQA# -SIEBEL_API_CLIENT_ID=AIAGENTCR -SIEBEL_STATUS_API_HOST=https://locahost:8011 -SIEBEL_STATUS_API_ROUTE=/interactions/v1/statusServiceRequest -SIEBEL_PREPAGO_STATUS_API_HOST=https://localhost:8011 -SIEBEL_PREPAGO_STATUS_API_ROUTE=/customers/v1/serviceRequest -VERIFY_SSL=false - -SPEECH_PREDICTION_BASE_URL=https://apigatewayfqa1.tim.com.br -SPEECH_PREDICTION_CLIENT_ID=TzlzCbMGNKpGXl7oUDAlE66eL3ZGmWBtuHMjgsClMoDNdmLz -SPEECH_PREDICTION_CLIENT_SECRET=ZIgIIxsn3CG7ra9HiOLSLygRzcgcivIMsoo4kwqLo9MtQBtNnmUadNpUx81ABkEm -SPEECH_HISTORY_BASE_URL=https://run-external-speech-analytics-audio-toxico-49911170294.us-east1.run.app -SPEECH_HISTORY_AUDIENCE=https://run-external-speech-analytics-audio-toxico-49911170294.us-east1.run.app -GOOGLE_APPLICATION_CREDENTIALS=/Users/cristianohoshikawa/Dropbox/ORACLE/TIM/compass/lab_backoffice/config/gcp-credentials-local.json -SPEECH_TIMEOUT=30 -SPEECH_SIMILARITY_THRESHOLD=70 - -TAIS_DB_USER=admin -TAIS_DB_PASSWORD=Moniquinha1972 -TAIS_DB_DSN=" (description= (retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1522)(host=adb.sa-saopaulo-1.oraclecloud.com))(connect_data=(service_name=jy2otyfomimhaoc_oradb23ai_high.adb.oraclecloud.com))(security=(ssl_server_dn_match=yes)))" -TAIS_DB_TIMEOUT=30 -TAIS_GENAI_ENDPOINT=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com -TAIS_GENAI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q -TAIS_GENAI_EMBED_MODEL_ID=cohere.embed-multilingual-v3.0 -TAIS_TABLE_CHUNKS=CHUNKS_CHAR_COHERE_3 -TAIS_TOP_K=3 - -# LLM CLASSIFICATION -CLASSIFICATION_LLM_MODEL=openai.gpt-4.1 -CLASSIFICATION_LLM_TEMPERATURE=0.3 -CLASSIFICATION_LLM_MAX_TOKENS=2000 -CLASSIFICATION_LLM_TOP_P=0.9 -CLASSIFICATION_LLM_TOP_K=250 - -CLASSIFICATION_LARGE_LLM_MODEL=openai.gpt-4.1 -CLASSIFICATION_LARGE_LLM_TEMPERATURE=0.3 -CLASSIFICATION_LARGE_LLM_MAX_TOKENS=4000 -CLASSIFICATION_LARGE_LLM_TOP_P=0.9 -CLASSIFICATION_LARGE_LLM_TOP_K=250 - -USE_FULL_ANATEL_DICT=true diff --git a/FRAMEWORK_NATIVE_CORRECTIONS.md b/FRAMEWORK_NATIVE_CORRECTIONS.md new file mode 100644 index 0000000..362c391 --- /dev/null +++ b/FRAMEWORK_NATIVE_CORRECTIONS.md @@ -0,0 +1,31 @@ +# Correções framework-native aplicadas no Compass Backoffice + +Este pacote foi corrigido para reduzir forks locais e usar o máximo possível do `agent_framework_oci`. + +## Correções principais + +1. `app/agents/runtime.py` deixou de manter um runtime próprio. + - Agora reexporta `AgentRuntimeMixin`, `RuntimeContext` e `MessageBuilder` diretamente de `agent_framework.runtime.agent_runtime`. + - Isso ativa o comportamento nativo do framework para MCP, cache MCP por `args_schema`, deduplicação intra-turno, RAG, memória, cache LLM e eventos IC/NOC/GRL. + +2. `BackofficeWorkflowExecutor` passou a carregar judges pelo profile do agente. + - Antes: `JudgePipeline()` usava o caminho global por padrão. + - Agora: `JudgePipeline(settings=settings, config_path=self.agent_profile.get("judges_config_path"))`. + - Com isso o agente `backoffice_anatel` usa `config/agents/backoffice_anatel/judges.yaml`. + +3. `config/guardrails.yaml` foi alinhado com `config/agents/backoffice_anatel/guardrails.yaml`. + - Evita que estágios do framework que leem o arquivo global executem uma política mais fraca. + - Guardrails ativos: `INPUT_SIZE`, `MSK`, `PINJ`, `TOX`, `DLEX_IN`, `RAGSEC`, `VLOOP`, `REVPREC`, `DLEX_OUT`, `OOS`, `CMP`, `AOFERTA`. + +4. `config/judges.yaml` foi alinhado com `config/agents/backoffice_anatel/judges.yaml`. + - Evita divergência entre execução global e execução por agente. + - Inclui judges de qualidade, groundedness e políticas específicas do domínio backoffice. + +5. `config/tools.yaml` recebeu política declarativa de MCP. + - Tools de consulta: `tool_type: read` e `cache.enabled: true`. + - Tools mutáveis/de ação: `tool_type: action`, `requires` e `cache.enabled: false`. + - A chave de cache passa a ser controlada pelo runtime do framework usando `args_schema`. + +## Regra arquitetural preservada + +As regras genéricas de segurança, qualidade, vazamento, repetição, escopo, groundedness e revisão de resposta ficam no framework. As regras específicas do domínio ANATEL/Backoffice permanecem como workflow de domínio, porque representam etapas de negócio e não guardrails reutilizáveis. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c46b09c --- /dev/null +++ b/README.md @@ -0,0 +1,889 @@ +# compass_backoffice — Manual de execução e testes + +## 1. O que é o `compass_backoffice` + +O `compass_backoffice` é um backend de agente para atendimento operacional de Backoffice/ANATEL, migrado para utilizar o `agent_framework_oci` como motor principal de execução. + +Ele foi construído para receber solicitações de backoffice, reclamações ANATEL, protocolos, casos de cliente e demandas operacionais, acionar ferramentas externas por MCP, consultar contexto via RAG, aplicar guardrails, registrar observabilidade IC/NOC/GRL e produzir uma resposta controlada pelo workflow corporativo do framework. + +Em termos práticos, o projeto permite testar um agente de backoffice com: + +- entrada conversacional via `/gateway/message`; +- entrada REST compatível com o contrato legado via `/agent/process-and-stream` e rotas relacionadas; +- roteamento por intents configuradas em YAML; +- execução de ferramentas pelo `MCPToolRouter` do framework; +- isolamento por `tenant_id`, `agent_id`, `session_id` e `BusinessContext`; +- RAG, memória, checkpoints, cache, telemetria, guardrails, judges e output supervisor do `agent_framework_oci`. + +## 2. Finalidade da aplicação + +O agente foi criado para apoiar fluxos como: + +- triagem de reclamações ANATEL; +- consulta de protocolo, chamado, cliente, contrato, linha, CPF/CNPJ e contexto operacional; +- enriquecimento de caso usando IMDB/PMID, Siebel, Speech Analytics, TAIS KB, ABRT e Portabilidade; +- geração de rascunho ou resposta para emulador ANATEL; +- registro controlado de ação operacional, parecer, conclusão, encaminhamento ou atualização de demanda; +- validação de segurança, conformidade, precisão e aderência ao domínio antes da resposta final. + +O objetivo da migração é evitar que o agente tenha um grafo paralelo ou regras “quebra-galho”. A aplicação deve delegar ao framework tudo que o framework já oferece: workflow, roteamento, identidade, MCP, RAG, guardrails, judges, observabilidade, memória, checkpoint e cache. + +## 3. Arquitetura resumida + +Fluxo principal do agente conversacional: + +```text +HTTP /gateway/message ou /gateway/message/sse + -> ChannelGateway + -> IdentityResolver + BusinessContext + -> SessionRepository / Memory / Checkpoint + -> AgentWorkflow LangGraph + -> input_guardrails + -> routing_decision + -> backoffice_agent + -> output_supervisor + -> output_guardrails + -> judge + -> supervisor_review + -> persist + -> ChannelResponse +``` + +Fluxo REST legado adaptado ao framework: + +```text +HTTP /agent/process-and-stream + -> BackofficeRestChannelAdapter + -> BackofficeWorkflowDispatcher + -> BackofficeWorkflowExecutor + -> etapas framework-native de guardrails, output supervisor, judges, telemetry e persistência +``` + +Fluxo de ferramentas: + +```text +BackofficeAgent + -> MCPToolRouter do agent_framework_oci + -> config/tools.yaml + -> config/mcp_parameter_mapping.yaml + -> config/mcp_servers.yaml + -> Backoffice MCP Server HTTP na porta 8010 + -> backend mock, REST, Oracle ou clientes TIM +``` + +## 4. Componentes principais do projeto + +```text +app/main.py +``` + +API FastAPI principal. Inicializa LLM, memória, summary memory, sessão, checkpoint, cache, ChannelGateway, analytics, observer, MCPToolRouter, IdentityResolver, AgentWorkflow, executor REST e rotas de debug/teste. + +```text +app/workflows/agent_graph.py +``` + +Workflow LangGraph principal. Usa componentes do framework para guardrails, roteamento, supervisor, output supervisor, judges, RAG, telemetria profunda de LangGraph e persistência. + +```text +app/agents/backoffice_agent.py +``` + +Agente de domínio Backoffice/ANATEL. Ele não deve recriar capacidades do framework. Recebe `intent`, `route`, `mcp_tools` e contexto do workflow; consulta RAG; aciona ferramentas MCP; emite IC/NOC/GRL; gera a resposta final. + +```text +app/workflows/backoffice_workflow_executor.py +app/workflows/backoffice_workflow_dispatcher.py +app/channels/backoffice_rest_adapter.py +``` + +Camada de compatibilidade para contratos REST de backoffice. Preserva endpoints antigos, mas executa o fluxo dentro do runtime do framework. + +```text +config/agents.yaml +``` + +Registra o agente `backoffice_anatel`, define o agent default e aponta configs específicas de prompt, guardrails, judges, MCP e tools. + +```text +config/routing.yaml +``` + +Define intents, palavras-chave, exemplos, tools por intent e state policies. O roteamento ativo padrão é `router`, com fallback para `backoffice_agent`. + +```text +config/agents/backoffice_anatel/guardrails.yaml +``` + +Configuração de guardrails específica do agente. Ativa rails de entrada e saída como `INPUT_SIZE`, `MSK`, `PINJ`, `TOX`, `DLEX_IN`, `RAGSEC`, `VLOOP`, `REVPREC`, `DLEX_OUT`, `OOS`, `CMP` e `AOFERTA`. + +```text +config/agents/backoffice_anatel/judges.yaml +``` + +Configuração de judges pós-resposta para avaliação de qualidade, precisão e aderência. + +```text +config/tools.yaml +``` + +Contrato das ferramentas disponíveis para o agente. Exemplos: `consultar_reclamacao`, `consultar_cliente_backoffice`, `consultar_siebel_caso`, `consultar_imdb_cliente`, `consultar_speech_analytics`, `consultar_tais_kb`, `consultar_abrt`, `consultar_portabilidade`, `registrar_acao_backoffice` e ferramentas do emulador. + +```text +config/mcp_parameter_mapping.yaml +``` + +Mapeia `BusinessContext` canônico para argumentos das tools MCP. Exemplo: `interaction_key -> protocol_id`, `customer_key -> customer_key`, `contract_key -> contract_key`, `session_key -> operator_session`. + +```text +mcp_servers/backoffice_mcp_server/ +``` + +Servidor MCP HTTP compatível com o `MCPToolRouter` do framework. Expõe `/health`, `/tools/list` e `/tools/call`. + +## 5. O que o projeto utiliza do `agent_framework_oci` + +O `compass_backoffice` utiliza as seguintes capacidades do framework: + +| Capacidade do framework | Uso no projeto | +|---|---| +| LangGraph / workflow corporativo | `app/workflows/agent_graph.py` | +| ChannelGateway | Normalização/renderização de mensagens por canal | +| IdentityResolver | Resolução de identidade técnica e `BusinessContext` | +| BusinessContext | Chaves canônicas para MCP e sessão | +| AgentProfileRegistry | Carregamento de `config/agents.yaml` | +| EnterpriseRouter | Roteamento por intents em `config/routing.yaml` | +| Supervisor | Modo alternativo quando `ROUTING_MODE=supervisor` | +| GuardrailPipeline | Guardrails de entrada e saída | +| OutputSupervisor | Revisão/retry/sanitize/block de resposta | +| JudgePipeline | Avaliação pós-resposta | +| MCPToolRouter | Chamada de tools via servidor MCP HTTP | +| RagService | Consulta RAG nativa do framework | +| Cache | Cache criado por `create_cache(settings)` | +| Memory | Histórico conversacional | +| ConversationSummaryMemory | Resumo de conversa | +| SessionRepository | Sessões persistentes | +| CheckpointRepository | Checkpoints por sessão | +| Telemetry / Langfuse | Spans, eventos, usage e debug | +| AgentObserver / Analytics | Emissão de IC, NOC e GRL | +| SSEHub | Eventos SSE por sessão | + +## 6. Pré-requisitos + +### 6.1 Python + +O `pyproject.toml` declara: + +```text +requires-python = >=3.11 +``` + +Recomendação prática: + +```text +Python 3.11 ou 3.12 +``` + +O `Dockerfile` usa `python:3.12-slim`. + +### 6.2 Dependências Python + +O projeto possui `requirements.txt` com dependências como: + +- `fastapi` +- `uvicorn[standard]` +- `pydantic` +- `pydantic-settings` +- `python-dotenv` +- `langgraph` +- `langchain-core` +- `openai` +- `oci` +- `oracledb` +- `pymongo` +- `redis` +- `PyYAML` +- `langfuse` +- `httpx` +- `opentelemetry-*` +- `pytest` +- `pytest-asyncio` +- `motor` +- `aiokafka` +- `google-auth` +- `requests` +- `tenacity` +- `python-json-logger` + +### 6.3 `agent_framework_oci` + +O backend depende do pacote `agent_framework_oci`, importado como `agent_framework`. + +Existem duas formas comuns de disponibilizar o framework: + +#### Opção A — instalação editável local + +Estrutura sugerida: + +```text +workspace/ + agent_framework_oci/ + compass_backoffice/ +``` + +Instalação: + +```bash +cd workspace/compass_backoffice +python -m venv .venv +source .venv/bin/activate +pip install --upgrade pip +pip install -e ../agent_framework_oci +pip install -r requirements.txt +``` + +Se o diretório do framework tiver outro nome, ajuste o caminho do `pip install -e`. + +#### Opção B — Docker + +O `Dockerfile` espera copiar o framework para `/agent_framework`. Em builds Docker, garanta que o contexto inclua o diretório do framework conforme esperado pelo arquivo. + +### 6.4 Serviços opcionais + +Para testes locais mínimos, é possível usar SQLite e MCP mock. + +Para execução integrada, podem ser necessários: + +- Redis, se o cache estiver configurado para Redis; +- MongoDB, se algum repositório estiver configurado para Mongo; +- Oracle Autonomous DB, se memória/checkpoint/RAG/TAIS real forem usados; +- Langfuse, se telemetria estiver habilitada; +- credenciais OCI Generative AI; +- credenciais TIM/Siebel/PMID/Speech/TAIS, se o MCP estiver em modo real. + +## 7. Configuração local + +Crie o `.env` principal: + +```bash +cp .env.example .env +``` + +Principais variáveis para teste local: + +```env +APP_ENV=local +LOG_LEVEL=INFO +CORS_ORIGINS=* + +DEFAULT_AGENT_ID=backoffice_anatel +ROUTING_MODE=router +ENABLE_SSE=true +ENABLE_PARALLEL_GUARDRAILS=true +GUARDRAILS_FAIL_FAST=true + +SESSION_REPOSITORY_PROVIDER=sqlite +MEMORY_REPOSITORY_PROVIDER=sqlite +CHECKPOINT_REPOSITORY_PROVIDER=sqlite +USAGE_REPOSITORY_PROVIDER=sqlite +SQLITE_DB_PATH=.local/backoffice_framework.db + +LLM_PROVIDER=oci_openai +LLM_MODEL=cohere.command-r-08-2024 + +MCP_ENABLED=true +MCP_SERVERS_CONFIG_PATH=config/mcp_servers.yaml +MCP_PARAMETER_MAPPING_PATH=config/mcp_parameter_mapping.yaml +BACKOFFICE_MCP_BASE_URL=http://localhost:8010 +``` + +Para rodar com LLM real, preencha também as variáveis do provider OCI/OpenAI-compatible exigidas pelo seu `agent_framework_oci`, por exemplo endpoint, chave, profile, região e compartment. + +Para testes sem integrações reais de backoffice, configure o MCP em modo mock. + +## 8. Como subir o Legacy Backoffice MCP Server + +Crie o arquivo de ambiente do MCP: + +```bash +cp .env.backoffice_mcp.example .env.backoffice_mcp +``` + +Para teste local com dados simulados: + +```env +BACKOFFICE_MCP_HOST=0.0.0.0 +BACKOFFICE_MCP_PORT=8010 +BACKOFFICE_MCP_USE_MOCK=true +BACKOFFICE_MCP_BACKEND_TYPE=mock +``` + +Suba o servidor MCP: + +```bash +source .venv/bin/activate +./scripts/run_backoffice_mcp.sh +``` + +Teste o health: + +```bash +curl http://localhost:8010/health +``` + +Liste as tools: + +```bash +curl http://localhost:8010/tools/list +``` + +Chamada direta de tool: + +```bash +curl --location 'http://localhost:8010/tools/call' \ + --header 'Content-Type: application/json' \ + --data '{ + "name": "consultar_reclamacao", + "arguments": { + "protocol_id": "202603279001551", + "customer_key": "06252533106" + } + }' +``` + +### Modos de backend do MCP + +O MCP server suporta os modos abaixo: + +| Modo | Uso | +|---|---| +| `mock` | Teste local com dados simulados em memória | +| `rest` | Chamada para backend REST configurado por `BACKOFFICE_MCP_REST_BASE_URL` | +| `oracle` | Placeholder para integração Oracle real | +| `tim_clients` | Uso de clientes TIM/develop configurados no `.env` | + +Para usar integrações reais, desative mock e preencha as credenciais correspondentes: + +```env +BACKOFFICE_MCP_USE_MOCK=false +BACKOFFICE_MCP_BACKEND_TYPE=tim_clients +BACKOFFICE_MCP_FAIL_OPEN_ON_BACKEND_ERROR=true +``` + +## 9. Como subir a aplicação principal + +Em outro terminal: + +```bash +cd compass_backoffice +source .venv/bin/activate +mkdir -p .local +uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload +``` + +Teste o health: + +```bash +curl http://localhost:8000/health +``` + +Endpoints úteis de diagnóstico: + +```bash +curl http://localhost:8000/agents +curl http://localhost:8000/debug/env +curl http://localhost:8000/debug/mcp/tools +curl http://localhost:8000/test-llm +``` + +Teste de identidade: + +```bash +curl --location 'http://localhost:8000/debug/identity' \ + --header 'Content-Type: application/json' \ + --data '{ + "channel": "web", + "agent_id": "backoffice_anatel", + "tenant_id": "default", + "payload": { + "message": "Consultar protocolo 202603279001551 do CPF 06252533106", + "session_id": "teste-readme-001", + "user_id": "operador" + } + }' +``` + +Teste de roteamento: + +```bash +curl --location 'http://localhost:8000/debug/route' \ + --header 'Content-Type: application/json' \ + --data '{ + "channel": "web", + "agent_id": "backoffice_anatel", + "tenant_id": "default", + "payload": { + "message": "Preciso analisar uma reclamação da ANATEL do protocolo 202603279001551", + "session_id": "teste-readme-001", + "user_id": "operador" + } + }' +``` + +## 10. Teste conversacional pelo gateway do framework + +```bash +curl --location 'http://localhost:8000/gateway/message' \ + --header 'Content-Type: application/json' \ + --data '{ + "channel": "web", + "agent_id": "backoffice_anatel", + "tenant_id": "default", + "payload": { + "message": "Preciso analisar a reclamação ANATEL protocolo 202603279001551 do CPF 06252533106. A TIM está ligando cobrando planos que o cliente diz não ter assinado.", + "session_id": "teste-backoffice-001", + "user_id": "operador-bko", + "business_context": { + "customer_key": "06252533106", + "interaction_key": "202603279001551", + "session_key": "teste-backoffice-001" + } + } + }' +``` + +Resposta esperada em alto nível: + +- `text` com resposta do agente; +- `metadata.agent_id = backoffice_anatel`; +- `metadata.route` e/ou `metadata.intent`; +- `metadata.business_context` resolvido; +- `metadata.mcp_results`, quando tools forem acionadas; +- `metadata.guardrails` e `metadata.judges`, quando habilitados/retornados pelo pipeline. + +## 11. Teste com SSE + +Primeiro envie uma mensagem com SSE habilitado: + +```bash +curl --location 'http://localhost:8000/gateway/message/sse' \ + --header 'Content-Type: application/json' \ + --data '{ + "channel": "web", + "agent_id": "backoffice_anatel", + "tenant_id": "default", + "payload": { + "message": "Consultar protocolo ANATEL 202603279001551", + "session_id": "teste-sse-001", + "user_id": "operador-bko", + "business_context": { + "interaction_key": "202603279001551", + "customer_key": "06252533106" + } + } + }' +``` + +Depois acompanhe os eventos usando o `conversation_key` retornado na resposta. Em geral, o formato é: + +```text +tenant_id:agent_id:session_id +``` + +Exemplo: + +```bash +curl -N 'http://localhost:8000/gateway/events/default:backoffice_anatel:teste-sse-001' +``` + +Eventos esperados: + +- `flow.start` +- `session.upserted` +- `message.received` +- `workflow.started` +- `workflow.completed` +- `message.responded` +- `flow.end` + +## 12. Teste com o curl anexado + +O arquivo anexado `curl_test.txt` usa a rota: + +```text +POST /agent/process-and-stream +``` + +Esse endpoint preserva o contrato REST do backoffice e passa pelo adapter framework-native. + +Com a aplicação na porta 8000, execute o conteúdo do arquivo: + +```bash +bash curl_test.txt +``` + +Ou copie o curl abaixo: + +```bash +curl --location 'http://localhost:8000/agent/process-and-stream' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "caseType": "anatel", + "origin": { + "sourceSystem": "turbina_odc", + "submittedBy": { + "userId": "f8052701", + "name": "Nicolas Silva", + "email": null + } + }, + "crmProtocol": "DS-987654321", + "ticketId": "ert", + "customer": { + "cpfCnpj": "06252533106", + "phones": ["62981152324"], + "msisdn": "62981152324", + "name": "Nicolas Ferreira da Silva", + "email": "nicolas.silva@exemplo.com.br", + "govBrSeal": null, + "odcCustomer": false, + "contumazCustomer": false, + "address": { + "cep": "01001-000", + "street": "Praca da Se", + "neighborhood": "Se", + "city": "Sao Paulo", + "state": "SP" + }, + "subscriber": { + "cpfCnpj": "06252533106", + "subscriberName": "Assinante Exemplo", + "contactPhone": "62981152324", + "contactName": "Nicolas Ferreira da Silva" + } + }, + "complaint": { + "complaintProtocol": "202603279001551", + "actionType": "nova", + "providerProtocol": null, + "inputChannel": "Anatel", + "service": "Celular Pós-pago", + "firstService": "Celular Pós-pago", + "modality": "Cobrança", + "motive": "Operadora liga ou envia mensagens indevidas de cobrança", + "description": "A TIM está me ligando cobrando planos que eu não assinei. Quero que parem", + "openedAt": "2026-03-27T11:45:00-03:00", + "dueAt": null + } +}' +``` + +## 13. Teste de MCP via backend principal + +O backend principal também permite chamar tools usando o router do framework: + +```bash +curl --location 'http://localhost:8000/debug/mcp/call/consultar_reclamacao' \ + --header 'Content-Type: application/json' \ + --data '{ + "business_context": { + "interaction_key": "202603279001551", + "customer_key": "06252533106", + "session_key": "teste-mcp-001" + } + }' +``` + +Esse teste é importante porque valida o caminho correto: + +```text +FastAPI principal -> MCPToolRouter -> mcp_parameter_mapping.yaml -> Backoffice MCP Server +``` + +Não é recomendado chamar clientes TIM/Siebel diretamente dentro do agente. O caminho correto é via MCP. + +## 14. Como subir com Docker Compose + +O projeto inclui `docker-compose.yml` com dois serviços: + +- `backoffice-api`, porta `8000`; +- `backoffice-mcp`, porta `8010`. + +Execute: + +```bash +cp .env.example .env +cp .env.backoffice_mcp.example .env.backoffice_mcp + +docker compose up --build +``` + +Teste: + +```bash +curl http://localhost:8000/health +curl http://localhost:8010/health +``` + +Observação: confira o `Dockerfile` e o contexto de build. Ele espera que o framework esteja disponível no contexto como `agent_framework` e a aplicação como `agent_template_backend`. Se a pasta local tiver outro nome, ajuste o Dockerfile ou o contexto antes do build. + +## 15. Guardrails, judges e output supervisor + +O agente usa duas camadas de configuração: + +```text +config/guardrails.yaml +config/agents/backoffice_anatel/guardrails.yaml +``` + +A configuração específica do agente é a mais importante para o `backoffice_anatel`. + +Guardrails de entrada configurados: + +- `INPUT_SIZE` +- `MSK` +- `PINJ` +- `TOX` +- `DLEX_IN` +- `RAGSEC` +- `VLOOP` + +Guardrails de saída configurados: + +- `REVPREC` +- `DLEX_OUT` +- `OOS` +- `CMP` +- `AOFERTA` + +O workflow também usa: + +- `OutputSupervisor`, para revisão, retry, sanitização ou bloqueio; +- `JudgePipeline`, para avaliação pós-resposta; +- `supervisor_review`, para revisão final do fluxo. + +## 16. IC, NOC e GRL + +A emissão de observabilidade passa por: + +```text +AgentObserver +create_analytics_publisher(settings) +src.compat.framework_observer.configure(...) +``` + +No agente de backoffice, os eventos principais aparecem em `app/agents/backoffice_agent.py`. + +Exemplos de ICs usados no agente: + +- `AGA.001`: entrada recebida pelo agente nativo; +- `AGA.018`: contexto canônico validado; +- `AGA.012`: RAG/TAIS KB consultado; +- `AGA.014`: ferramentas MCP selecionadas; +- `AGA.008`: registro operacional ignorado por ausência de `action_text`; +- `AGA.010`: Speech Analytics consultado; +- `AGA.011`: cliente/IMDB consultado; +- `AGA.020`: templates/TAIS consultados; +- `AGA.006`: ação operacional solicitada; +- `AGA.043`: resposta produzida. + +Exemplo de NOC: + +- `NOC.001`: conclusão do agente nativo de backoffice. + +Os GRLs são esperados principalmente nas etapas de guardrails/output supervisor/judges do framework e nos pontos de observabilidade plugados pelo `AgentObserver`. + +## 17. RAG + +O workflow inicializa: + +```text +create_embedding_provider(settings) +RagService(settings, telemetry=telemetry) +``` + +O agente usa RAG para enriquecer a resposta quando configurado. O namespace de referência do agente aparece em `config/agents.yaml`: + +```yaml +metadata: + rag_namespace: backoffice_anatel +``` + +Para uso real do RAG, configure no `.env` os providers e stores exigidos pelo `agent_framework_oci`, como embeddings OCI, vector store e credenciais de banco quando aplicável. + +## 18. Rotas disponíveis + +### Framework/gateway + +```text +GET /health +GET /agents +GET /debug/env +GET /test-llm +POST /debug/route +POST /debug/identity +GET /debug/usage +GET /debug/mcp/tools +POST /debug/mcp/call/{tool_name} +POST /gateway/message +POST /gateway/message/sse +GET /gateway/events/{session_id} +GET /sessions/{session_id}/messages +GET /sessions/{session_id}/checkpoint +``` + +### Contratos REST de backoffice preservados + +```text +POST /agent/process-ticket +POST /agent/execute +POST /agent/process-and-stream +POST /agent/search-tais-kb +POST /case/{transaction_id}/response-emulator/generate +POST /case/{transaction_id}/response-emulator/finalize +GET /case/{transaction_id}/response-emulator +POST /emulator-rag/search +GET /health/live +GET /health/ready +GET /debug/backoffice/parity +``` + +### MCP server + +```text +GET /health +GET /tools/list +POST /tools/call +``` + +## 19. Validação estrutural + +Antes de executar testes funcionais, valide sintaxe e imports básicos: + +```bash +python -m compileall -q app src tools mcp_servers +python tools/validate_parity.py +``` + +## 20. Troubleshooting + +### `ModuleNotFoundError: agent_framework` + +Instale o framework em modo editável: + +```bash +pip install -e ../agent_framework_oci +``` + +Ou ajuste o `PYTHONPATH` temporariamente: + +```bash +export PYTHONPATH="../agent_framework_oci:$PYTHONPATH" +``` + +### `/debug/mcp/tools` sem tools + +Verifique: + +```env +MCP_ENABLED=true +MCP_SERVERS_CONFIG_PATH=config/mcp_servers.yaml +MCP_PARAMETER_MAPPING_PATH=config/mcp_parameter_mapping.yaml +``` + +E confirme se o MCP está ativo: + +```bash +curl http://localhost:8010/health +``` + +### MCP retorna erro de conexão + +Confirme se `config/mcp_servers.yaml` aponta para: + +```yaml +endpoint: http://localhost:8010 +``` + +No Docker, o endpoint deve ser o nome do serviço: + +```yaml +endpoint: http://backoffice-mcp:8010 +``` + +### LLM não responde + +Teste: + +```bash +curl http://localhost:8000/test-llm +``` + +Se falhar, revise as variáveis do provider OCI/OpenAI-compatible. + +### SQLite não cria banco + +Crie a pasta local: + +```bash +mkdir -p .local +``` + +### Endpoint legado funciona, mas gateway não aciona tools + +Teste o roteamento: + +```bash +curl http://localhost:8000/debug/mcp/tools +``` + +Depois teste `/debug/route` e confirme se a intent retorna `mcp_tools`. + +## 21. Sequência recomendada de teste local + +```bash +# Terminal 1 +cd compass_backoffice +source .venv/bin/activate +cp .env.backoffice_mcp.example .env.backoffice_mcp +./scripts/run_backoffice_mcp.sh +``` + +```bash +# Terminal 2 +cd compass_backoffice +source .venv/bin/activate +cp .env.example .env +mkdir -p .local +uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload +``` +```bash +# Terminal 3 +cd compass_backoffice +source .venv/bin/activate +cd legacy_backend +uvicorn mock_imdb_server:app --port 8011 +``` + + +```bash +# Terminal 4 +curl http://localhost:8010/health +curl http://localhost:8000/health +curl http://localhost:8000/debug/mcp/tools +bash curl_test.txt +``` + +## 22. Diretriz de evolução + +Ao evoluir o `compass_backoffice`, mantenha a regra arquitetural: + +- identidade e chaves de negócio: `IdentityResolver` e `BusinessContext`; +- roteamento: `config/routing.yaml` e `EnterpriseRouter`; +- ferramentas externas: `MCPToolRouter` + MCP server; +- conhecimento: `RagService` do framework; +- segurança e conformidade: guardrails, output supervisor e judges do framework; +- observabilidade: `AgentObserver`, IC, NOC, GRL, Langfuse e telemetry; +- persistência: memória, sessão e checkpoint do framework. + +Só implemente código de domínio fora do framework quando a capacidade realmente não existir no `agent_framework_oci`. diff --git a/app/agents/runtime.py b/app/agents/runtime.py index 9e6ca25..83b94e6 100644 --- a/app/agents/runtime.py +++ b/app/agents/runtime.py @@ -1,267 +1,12 @@ -from __future__ import annotations +"""Framework-native agent runtime facade. -import hashlib -from typing import Any +Compass Backoffice must not maintain a forked runtime for MCP, RAG, cache, +telemetry, memory or IC/NOC/GRL behavior. The application imports the runtime +mixin directly from agent_framework_oci so fixes in the framework (for example +MCP cache using args_schema, BusinessContext mapping and tool deduplication) are +picked up by this backend without duplicating code here. +""" +from agent_framework.runtime.agent_runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext -class AgentRuntimeMixin: - """Mixin operacional para agentes. - - Integra RAG, cache, telemetria e chamadas MCP usando BusinessContext. - Os agentes não precisam conhecer nomes reais de parâmetros do domínio - (msisdn, invoice_id, order_id etc.); eles repassam as chaves canônicas e - o MCPParameterMapper converte para cada tool configurada. - """ - - - async def _emit_ic(self, code: str, state: dict[str, Any], payload: dict[str, Any] | None = None, component: str | None = None) -> None: - """Emite Item de Controle (IC) sem impactar a execução do agente. - - Este helper é intencionalmente fail-open: erro de observabilidade não - pode quebrar a jornada de negócio do agente. O desenvolvedor pode usar - o mesmo padrão para ICs específicos da sua squad. - """ - observer = getattr(self, "observer", None) - if not observer: - return - ctx = state.get("context") or {} - base = { - "session_id": state.get("conversation_key") or state.get("session_id"), - "tenant_id": state.get("tenant_id"), - "agent_id": state.get("agent_id"), - "route": state.get("route"), - "intent": state.get("intent"), - "message_id": ctx.get("message_id"), - "channel_id": ctx.get("channel"), - } - base.update(payload or {}) - try: - await observer.emit_ic(code, base, component=component or f"agent.{getattr(self, 'name', 'unknown')}") - except Exception: - return - - async def _emit_noc(self, code: str, state: dict[str, Any], payload: dict[str, Any] | None = None, component: str | None = None) -> None: - """Emite evento NOC sem acoplar a lógica de negócio à observabilidade.""" - observer = getattr(self, "observer", None) - if not observer: - return - ctx = state.get("context") or {} - base = { - "session_id": state.get("conversation_key") or state.get("session_id"), - "tenant_id": state.get("tenant_id"), - "agent_id": state.get("agent_id"), - "route": state.get("route"), - "intent": state.get("intent"), - "message_id": ctx.get("message_id"), - "channel_id": ctx.get("channel"), - } - base.update(payload or {}) - try: - await observer.emit_noc(code, base, component=component or f"agent.{getattr(self, 'name', 'unknown')}") - except Exception: - return - - async def _emit_grl(self, code: str, state: dict[str, Any], payload: dict[str, Any] | None = None, component: str | None = None) -> None: - """Emite evento GRL opcional para custom rails implementados no backend.""" - observer = getattr(self, "observer", None) - if not observer: - return - ctx = state.get("context") or {} - base = { - "session_id": state.get("conversation_key") or state.get("session_id"), - "tenant_id": state.get("tenant_id"), - "agent_id": state.get("agent_id"), - "route": state.get("route"), - "intent": state.get("intent"), - "message_id": ctx.get("message_id"), - "channel_id": ctx.get("channel"), - } - base.update(payload or {}) - try: - await observer.emit_grl(code, base, component=component or f"agent.{getattr(self, 'name', 'unknown')}") - except Exception: - return - - async def _retrieve_rag_context(self, state: dict[str, Any]) -> tuple[str, dict[str, Any]]: - rag_service = getattr(self, "rag_service", None) - if not rag_service: - return "", {"enabled": False} - text = state.get("sanitized_input") or state.get("user_text") or "" - namespace = ( - (state.get("agent_profile") or {}).get("rag_namespace") - or state.get("agent_id") - or state.get("route") - or "default" - ) - ctx = state.get("context") or {} - business_context = ctx.get("business_context") or {} - graph_node = ( - ctx.get("graph_node") - or business_context.get("customer_key") - or business_context.get("contract_key") - or ctx.get("customer_id") - ) - result = await rag_service.retrieve(text, namespace=namespace, graph_node=graph_node) - context = result.as_prompt_context() - return context, { - "enabled": True, - "namespace": namespace, - "latency_ms": result.latency_ms, - "document_count": len(result.documents), - "graph_neighbors": len(result.graph_neighbors), - "top_document_ids": [d.id for d in result.documents[:5]], - "top_scores": [d.score for d in result.documents[:5]], - } - - async def _call_mcp_tool(self, tool: str, arguments: dict[str, Any] | None, state: dict[str, Any]) -> dict[str, Any]: - """Chama uma ferramenta via MCPToolRouter usando o contrato canônico do framework. - - Use este helper quando o agente precisa passar argumentos específicos - além do BusinessContext mapeado em mcp_parameter_mapping.yaml. - Observabilidade IC.MCP_TOOL_CALLED/IC.TOOL_CALLED permanece uniforme. - """ - if not getattr(self, "tool_router", None): - return {"ok": False, "tool_name": tool, "error": "tool_router não configurado"} - ctx = state.get("context") or {} - business_context = ctx.get("business_context") or state.get("business_context") or {} - original_context = { - **ctx, - "tenant_id": state.get("tenant_id"), - "agent_id": state.get("agent_id"), - "session_id": state.get("conversation_key") or state.get("session_id"), - "conversation_key": state.get("conversation_key") or state.get("session_id"), - } - observer = getattr(self, "observer", None) - if observer: - await observer.emit_ic( - "IC.MCP_TOOL_CALLED", - { - "session_id": original_context.get("conversation_key") or original_context.get("session_id"), - "tenant_id": original_context.get("tenant_id"), - "agent_id": original_context.get("agent_id"), - "tool_name": tool, - "framework_native": True, - }, - component="agent_runtime.native_mcp", - ) - try: - res = await self.tool_router.call( - tool, - arguments or {}, - business_context=business_context, - original_context=original_context, - ) - result_payload = res.model_dump(mode="json") if hasattr(res, "model_dump") else dict(res) - except Exception as exc: - result_payload = {"ok": False, "tool_name": tool, "error": str(exc), "error_type": type(exc).__name__} - result_payload.setdefault("tool_name", tool) - if observer: - await observer.emit_ic( - "IC.TOOL_CALLED", - { - "session_id": original_context.get("conversation_key") or original_context.get("session_id"), - "tenant_id": original_context.get("tenant_id"), - "agent_id": original_context.get("agent_id"), - "tool_name": tool, - "ok": result_payload.get("ok"), - "server_name": result_payload.get("server_name"), - "error": result_payload.get("error"), - "framework_native": True, - }, - component="agent_runtime.native_mcp", - ) - return result_payload - - async def _collect_mcp_context(self, state: dict[str, Any]) -> list[dict[str, Any]]: - results: list[dict[str, Any]] = [] - if not getattr(self, "tool_router", None): - return results - tools = state.get("mcp_tools") or [] - ctx = state.get("context") or {} - business_context = ctx.get("business_context") or state.get("business_context") or {} - original_context = { - **ctx, - "tenant_id": state.get("tenant_id"), - "agent_id": state.get("agent_id"), - "session_id": state.get("conversation_key") or state.get("session_id"), - "conversation_key": state.get("conversation_key") or state.get("session_id"), - } - for tool in tools: - observer = getattr(self, "observer", None) - if observer: - await observer.emit_ic( - "IC.MCP_TOOL_CALLED", - { - "session_id": original_context.get("conversation_key") or original_context.get("session_id"), - "tenant_id": original_context.get("tenant_id"), - "agent_id": original_context.get("agent_id"), - "tool_name": tool, - }, - component="agent_runtime", - ) - res = await self.tool_router.call( - tool, - {}, - business_context=business_context, - original_context=original_context, - ) - result_payload = res.model_dump(mode="json") - if observer: - await observer.emit_ic( - "IC.TOOL_CALLED", - { - "session_id": original_context.get("conversation_key") or original_context.get("session_id"), - "tenant_id": original_context.get("tenant_id"), - "agent_id": original_context.get("agent_id"), - "tool_name": tool, - "ok": result_payload.get("ok"), - "server_name": result_payload.get("server_name"), - "error": result_payload.get("error"), - }, - component="agent_runtime", - ) - results.append(result_payload) - return results - - async def _cache_get(self, key: str): - cache = getattr(self, "cache", None) - if not cache: - return None - return await cache.get(key) - - async def _cache_set(self, key: str, value: Any, ttl_seconds: int | None = None): - cache = getattr(self, "cache", None) - if not cache: - return - await cache.set(key, value, ttl_seconds) - - def _llm_cache_key(self, state: dict[str, Any], agent_name: str, prompt_parts: list[Any]) -> str: - business_context = (state.get("context") or {}).get("business_context") or {} - raw = "|".join([ - agent_name, - state.get("tenant_id") or "", - state.get("agent_id") or "", - state.get("intent") or "", - business_context.get("customer_key") or "", - business_context.get("contract_key") or "", - business_context.get("interaction_key") or "", - state.get("sanitized_input") or state.get("user_text") or "", - repr(prompt_parts), - ]) - return "llm:" + hashlib.sha256(raw.encode("utf-8")).hexdigest() - - async def _invoke_llm_cached(self, state: dict[str, Any], agent_name: str, messages: list[dict[str, str]]): - ttl = int(getattr(getattr(self, "settings", None), "CACHE_TTL_SECONDS", 300) or 300) - key = self._llm_cache_key(state, agent_name, messages) - cached = await self._cache_get(key) - if cached is not None: - telemetry = getattr(self, "telemetry", None) - if telemetry: - await telemetry.event("cache.llm.hit", {"agent": agent_name, "key": key}, kind="cache") - return cached - telemetry = getattr(self, "telemetry", None) - if telemetry: - await telemetry.event("cache.llm.miss", {"agent": agent_name, "key": key}, kind="cache") - answer = await self.llm.ainvoke(messages) - await self._cache_set(key, answer, ttl) - return answer +__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"] diff --git a/app/workflows/backoffice_workflow_executor.py b/app/workflows/backoffice_workflow_executor.py index f5ccb0d..4faa0bc 100644 --- a/app/workflows/backoffice_workflow_executor.py +++ b/app/workflows/backoffice_workflow_executor.py @@ -220,7 +220,10 @@ class BackofficeWorkflowExecutor: enable_parallel=bool(getattr(settings, "ENABLE_PARALLEL_GUARDRAILS", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), ) - self.judges = JudgePipeline() + self.judges = JudgePipeline( + settings=settings, + config_path=self.agent_profile.get("judges_config_path"), + ) self.supervisor = Supervisor() self.workflow_telemetry = WorkflowTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry) diff --git a/config/agents/backoffice_anatel/guardrails.yaml b/config/agents/backoffice_anatel/guardrails.yaml index 904c81f..9d30d15 100644 --- a/config/agents/backoffice_anatel/guardrails.yaml +++ b/config/agents/backoffice_anatel/guardrails.yaml @@ -1,54 +1,34 @@ +# Source of truth for global/default guardrails in Compass Backoffice. +# This file is intentionally aligned with config/agents/backoffice_anatel/guardrails.yaml +# so framework stages that read the global path and stages that read the agent profile +# apply the same policy set. agent_id: backoffice_anatel -profile: backoffice_anatel_enterprise +profile: backoffice_anatel + input: - code: INPUT_SIZE enabled: true - action: block - max_chars: 12000 - code: MSK enabled: true - action: sanitize - code: PINJ enabled: true - action: block - code: TOX enabled: true - action: block - code: DLEX_IN enabled: true - action: block - code: RAGSEC enabled: true - action: block - code: VLOOP enabled: true - action: block + output: - code: REVPREC enabled: true - action: sanitize - code: DLEX_OUT enabled: true - action: sanitize - code: OOS enabled: true - action: review - code: CMP enabled: true - action: review - code: AOFERTA enabled: true - action: block -business_rules: - require_evidence_for: - - protocolo - - cliente - - contrato - - status_siebel - - acao_operacional - forbid_without_tool_ok: - - registrar_acao_backoffice - - registrar_acao_siebel - domain_workflows: - checklist: src.agent.graphs.main_graph.create_main_agent_graph - response_emulator: src.agent.graphs.emulator_graph.create_emulator_graph diff --git a/config/agents/backoffice_anatel/judges.yaml b/config/agents/backoffice_anatel/judges.yaml index 239c81f..d1ac493 100644 --- a/config/agents/backoffice_anatel/judges.yaml +++ b/config/agents/backoffice_anatel/judges.yaml @@ -1,4 +1,9 @@ +# Source of truth for global/default judges in Compass Backoffice. +# Aligned with config/agents/backoffice_anatel/judges.yaml to avoid running a +# weaker judge set when the framework is configured with the global path. agent_id: backoffice_anatel +profile: judge +fail_closed: true judges: - name: response_quality enabled: true @@ -8,13 +13,21 @@ judges: threshold: 0.80 - name: backoffice_no_fabricated_protocol enabled: true + type: llm + threshold: 0.80 description: Verifica se a resposta não inventa protocolo, cliente, contrato, SLA ou status operacional. - name: siebel_action_requires_tool_ok enabled: true + type: llm + threshold: 0.85 description: Bloqueia confirmação de registro/fechamento Siebel sem evidência ok/registered. - name: anatel_domain_traceability enabled: true + type: llm + threshold: 0.80 description: Exige rastreabilidade para decisão de cancelamento, reclassificação, tratamento ou encaminhamento. - name: response_emulator_policy enabled: true + type: llm + threshold: 0.80 description: Valida resposta formal ANATEL gerada pelo emulador antes de persistir/aprovar/fechar. diff --git a/config/guardrails.yaml b/config/guardrails.yaml index 9fe094a..9d30d15 100644 --- a/config/guardrails.yaml +++ b/config/guardrails.yaml @@ -1,8 +1,34 @@ +# Source of truth for global/default guardrails in Compass Backoffice. +# This file is intentionally aligned with config/agents/backoffice_anatel/guardrails.yaml +# so framework stages that read the global path and stages that read the agent profile +# apply the same policy set. +agent_id: backoffice_anatel +profile: backoffice_anatel + input: + - code: INPUT_SIZE + enabled: true - code: MSK enabled: true + - code: PINJ + enabled: true + - code: TOX + enabled: true + - code: DLEX_IN + enabled: true + - code: RAGSEC + enabled: true - code: VLOOP enabled: true + output: - code: REVPREC enabled: true + - code: DLEX_OUT + enabled: true + - code: OOS + enabled: true + - code: CMP + enabled: true + - code: AOFERTA + enabled: true diff --git a/config/judges.yaml b/config/judges.yaml index 62fc7c7..d1ac493 100644 --- a/config/judges.yaml +++ b/config/judges.yaml @@ -1,7 +1,33 @@ +# Source of truth for global/default judges in Compass Backoffice. +# Aligned with config/agents/backoffice_anatel/judges.yaml to avoid running a +# weaker judge set when the framework is configured with the global path. +agent_id: backoffice_anatel +profile: judge +fail_closed: true judges: - name: response_quality enabled: true - threshold: 0.7 + threshold: 0.75 - name: groundedness enabled: true - threshold: 0.6 + threshold: 0.80 + - name: backoffice_no_fabricated_protocol + enabled: true + type: llm + threshold: 0.80 + description: Verifica se a resposta não inventa protocolo, cliente, contrato, SLA ou status operacional. + - name: siebel_action_requires_tool_ok + enabled: true + type: llm + threshold: 0.85 + description: Bloqueia confirmação de registro/fechamento Siebel sem evidência ok/registered. + - name: anatel_domain_traceability + enabled: true + type: llm + threshold: 0.80 + description: Exige rastreabilidade para decisão de cancelamento, reclassificação, tratamento ou encaminhamento. + - name: response_emulator_policy + enabled: true + type: llm + threshold: 0.80 + description: Valida resposta formal ANATEL gerada pelo emulador antes de persistir/aprovar/fechar. diff --git a/config/tools.yaml b/config/tools.yaml index 07293d8..10dcfff 100644 --- a/config/tools.yaml +++ b/config/tools.yaml @@ -3,60 +3,110 @@ tools: description: Consulta reclamação/protocolo de backoffice/ANATEL. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 300 args_schema: { protocol_id: string, customer_key: string, interaction_key: string } consultar_cliente_backoffice: description: Consulta contexto operacional do cliente para backoffice. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 600 args_schema: { customer_key: string, contract_key: string, session_key: string } registrar_acao_backoffice: description: Registra ação operacional, parecer ou encaminhamento no sistema de backoffice. mcp_server: backoffice enabled: true + tool_type: action + requires: [protocol_id, action_text, operator_session] + confirmation_required: false + cache: + enabled: false args_schema: { protocol_id: string, action_text: string, operator_session: string } consultar_siebel_caso: description: Consulta caso/SR no Siebel. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 300 args_schema: { protocol_id: string, interaction_key: string, customer_key: string } registrar_acao_siebel: description: Registra ação, reclassificação ou fechamento no Siebel. mcp_server: backoffice enabled: true + tool_type: action + requires: [protocol_id, action_text, operator_session] + confirmation_required: false + cache: + enabled: false args_schema: { protocol_id: string, action_text: string, operator_session: string } consultar_imdb_cliente: description: Consulta enriquecimento IMDB/PMID do cliente. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 600 args_schema: { customer_key: string, contract_key: string, session_key: string } consultar_speech_analytics: description: Consulta histórico/resumo do Speech Analytics. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 600 args_schema: { protocol_id: string, customer_key: string, interaction_key: string } consultar_tais_kb: description: Consulta TAIS Knowledge Base/RAG e templates. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 900 args_schema: { query: string, protocol_id: string, customer_key: string } consultar_abrt: description: Consulta ABRT associado ao cliente/caso. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 600 args_schema: { customer_key: string, protocol_id: string } consultar_portabilidade: description: Consulta status de portabilidade. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 600 args_schema: { customer_key: string, contract_key: string } buscar_templates_emulador: description: Busca templates/documentos para Response Emulator. mcp_server: backoffice enabled: true + tool_type: read + cache: + enabled: true + ttl_seconds: 900 args_schema: { protocol_id: string, query: string } gerar_rascunho_emulador: description: Gera rascunho de resposta do Response Emulator. mcp_server: backoffice enabled: true + tool_type: action + requires: [protocol_id] + cache: + enabled: false args_schema: { protocol_id: string, selected_actions: array, operator_instructions: string }