Projeto do Agent Contas ORACLE
This commit is contained in:
363
.env
Normal file
363
.env
Normal file
@@ -0,0 +1,363 @@
|
||||
###############################################################################
|
||||
# MIGRATED TIM CONTAS - agent_framework_oci
|
||||
# Source of truth for shared platform settings: official agent_framework_oci .env
|
||||
# Contas-specific variables follow and are adapted to the same local model/DB/region.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# 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_sdk
|
||||
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
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS
|
||||
OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
#OCI_GENAI_BASE_URL=https://pegruagntaiatenddev.pe.inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
|
||||
#OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
#OCI_GENAI_API_KEY=
|
||||
#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=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=Moniquinha19721972
|
||||
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://ADMIN:Moniquinha1972@srlrz1eg.adb.sa-saopaulo-1.oraclecloudapps.com:37017/ADMIN?authMechanism=PLAIN&authSource=$external&ssl=true&retryWrites=false&loadBalanced=true
|
||||
MONGODB_DATABASE=agent_platform
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://default:devredis@localhost:6379/0
|
||||
ENABLE_REDIS_CACHE=false
|
||||
|
||||
###############################################################################
|
||||
# RAG / Vector / Graph
|
||||
###############################################################################
|
||||
VECTOR_STORE_PROVIDER=autonomous
|
||||
GRAPH_STORE_PROVIDER=autonomous
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=oci
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
|
||||
###############################################################################
|
||||
# Observabilidade
|
||||
###############################################################################
|
||||
ENABLE_LANGFUSE=true
|
||||
# Opcional: verbose, compact
|
||||
LANGFUSE_TRACE_MODE=compact
|
||||
# Nome customizado do trace pai, ex.: backoffice.checklist.workflow ou backoffice.emulador.workflow
|
||||
LANGFUSE_COMPACT_VISIBLE_EVENT_PREFIXES=AGA.,NOC., IC.
|
||||
LANGFUSE_COMPACT_SUPPRESSED_PREFIXES=llm.chat_completion
|
||||
LANGFUSE_IGNORE_HEALTHCHECKS=true
|
||||
LANGFUSE_IGNORED_PATHS=/health,/ready,/metrics
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-4a1e3921-5158-4fd3-a16d-7a77549fb312
|
||||
LANGFUSE_SECRET_KEY=sk-lf-efc6fd59-c5ec-4858-b6ec-4aa129734915
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
|
||||
|
||||
###############################################################################
|
||||
# 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
|
||||
# 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=true
|
||||
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
|
||||
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
|
||||
|
||||
###############################################################################
|
||||
# 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
|
||||
|
||||
###############################################################################
|
||||
# CONTAS DOMAIN / LEGACY COMPATIBILITY (local values aligned to framework above)
|
||||
###############################################################################
|
||||
AGENT_NAME=ai-agent-contas
|
||||
CHECKPOINT_BACKEND=mongodb
|
||||
ENABLE_CONTROL_EVENTS_OTEL_LOGS=true
|
||||
ENABLE_NOC_OTEL_LOGS=false
|
||||
ENV_TESTE=123
|
||||
GUARDRAIL_LLM="20b"
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
MONGODB_COLLECTION=sales_memory
|
||||
MONGODB_CONNECTION_STRING=mongodb://ADMIN:Moniquinha1972@srlrz1eg.adb.sa-saopaulo-1.oraclecloudapps.com:37017/ADMIN?authMechanism=PLAIN&authSource=$external&ssl=true&retryWrites=false&loadBalanced=true
|
||||
OTEL_EXPORTER_OTLP_HOST_HEADER=tim-ai-atend-agnt-opentelemetry
|
||||
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://10.153.35.23/v1/logs
|
||||
PUBSUB_EXCLUDE_NOC=true
|
||||
PUBSUB_PAYLOAD_MODE=flat
|
||||
PUBSUB_SEQUENCE_ENABLED=true
|
||||
PUBSUB_SEQUENCE_PROVIDER=mongodb
|
||||
PUBSUB_SEQUENCE_MEMORY_FALLBACK=false
|
||||
PUBSUB_SEQUENCE_MONGODB_COLLECTION=observer_sequences
|
||||
PUBSUB_SEQUENCE_MONGODB_DATABASE=USR_ADB_AGNTATEND_W_DEV
|
||||
PUBSUB_SEQUENCE_MONGODB_URI=mongodb://ADMIN:Moniquinha1972@srlrz1eg.adb.sa-saopaulo-1.oraclecloudapps.com:37017/ADMIN?authMechanism=PLAIN&authSource=$external&ssl=true&retryWrites=false&loadBalanced=true
|
||||
PUBSUB_SEQUENCE_PROVIDER=mongodb
|
||||
PUBSUB_SEQUENCE_REDIS_URL=redis://default:devredis@127.0.0.1:6379/0
|
||||
TIM_AGENT_FRAMEWORK_EVENT_PAYLOAD_LOG_ENABLED=true
|
||||
TIM_AGENT_FRAMEWORK_LOG_EXPORT_ENABLED=true
|
||||
TIM_AGENT_ID=contas
|
||||
TIM_APP_HOST=0.0.0.0
|
||||
TIM_APP_LOG_LEVEL=INFO
|
||||
TIM_APP_LOG_MASK_SENSITIVE_DATA=true
|
||||
TIM_APP_PORT=8000
|
||||
TIM_APP_RELOAD=false
|
||||
TIM_APP_VERSION=1.0.0
|
||||
TIM_APP_WS_PING_INTERVAL=10
|
||||
TIM_APP_WS_PING_TIMEOUT=30
|
||||
TIM_BLOQUEIO_ACCEPT_ENCODING=gzip,deflate
|
||||
TIM_BLOQUEIO_CLIENT_ID=AIAGENTCR
|
||||
TIM_BLOQUEIO_OPERATION_TYPE=block
|
||||
TIM_BLOQUEIO_TIMEOUT=30
|
||||
TIM_CANCELAMENTO_TIMEOUT=30
|
||||
TIM_CANCELAMENTO_URL=http://pmidfqa.internal.timbrasil.com.br:8000/access/v1/singleVas
|
||||
TIM_CHANNEL_ADAPTER_DEFAULT_CHANNEL=voice
|
||||
TIM_CHANNEL_ADAPTER_ENABLED=false
|
||||
TIM_CHANNEL_ADAPTER_SHADOW=false
|
||||
TIM_COMPLETE_INVOICES_TIMEOUT=30
|
||||
TIM_COMPLETE_INVOICES_URL=http://pmidfqa.internal.timbrasil.com.br:8000/customers/v1/completeInvoices
|
||||
TIM_CONSULTA_TIMEOUT=30
|
||||
TIM_CONTRACT_VERSION=1.0.0
|
||||
TIM_CONTRATO_TIMEOUT=30
|
||||
TIM_CONTRATO_URL=http://pmidfqa.internal.timbrasil.com.br:8000/access/v1/contractInformation
|
||||
TIM_CUSTOMER_CONTESTATION_CLIENT_ID=AIAGENTCR
|
||||
TIM_CUSTOMER_CONTESTATION_TIMEOUT=30
|
||||
TIM_CUSTOMER_CONTESTATION_URL=http://pmidfqa.internal.timbrasil.com.br:8000/interactions/v1/customerContestation
|
||||
TIM_CUSTOMER_CONTESTATION_USER_ID=AIAGENTCR
|
||||
TIM_DEFAULT_CHANNEL=APP
|
||||
TIM_DEFAULT_CLIENT_ID=CHAT
|
||||
TIM_DEFAULT_CSP_ID=740
|
||||
TIM_DIVERGENCIA_TIMEOUT=120
|
||||
TIM_DIVERGENCIA_URL=http://pmidfqa.internal.timbrasil.com.br:8000/customers/v1/billingAnalysis
|
||||
TIM_DIVERGENCIA_USER=agentecontestacao
|
||||
TIM_GATEWAY_DEFAULT_TIMEOUT=30
|
||||
TIM_GATEWAY_MODE=mock
|
||||
TIM_GATEWAY_RETRY_BACKOFF_FACTOR=0.5
|
||||
TIM_GATEWAY_RETRY_MAX_RETRIES=3
|
||||
TIM_INVOICE_RECOVER_CLIENT_ID=AIAGENTCR
|
||||
TIM_SECURE_PDF_AUTH=Basic test
|
||||
TIM_INVOICE_RECOVER_TIMEOUT=30
|
||||
TIM_JUDGES_BASELINE_SCORES=
|
||||
TIM_JUDGES_MIN_SCORES=CSI=0,ALUC=7,RQLT=6,VCTN=8
|
||||
#TIM_LLM_EXTRA={"auth_type":"RESOURCE_PRINCIPAL","provider":"meta","oci_use_oke_workload_signer":true}
|
||||
TIM_LLM_OCI_AUTH_FILE_LOCATION=/home/hoshi/.oci/config
|
||||
TIM_LLM_EXTRA={"auth_type":"API_KEY","auth_profile":"LATINOAMERICA-Chicago","provider":"meta","oci_use_oke_workload_signer":false}
|
||||
TIM_LLM_GATEWAY_FALLBACK_PROMPT_CONTENT=Você é um agente para operações VAS TIM. Use tools quando precisar executar ações no backend.
|
||||
TIM_LLM_GATEWAY_FALLBACK_PROMPT_ID=default
|
||||
TIM_LLM_GATEWAY_LANGFUSE_CACHE_TTL_SECONDS=60
|
||||
TIM_LLM_GATEWAY_LANGFUSE_DEFAULT_LABEL=production
|
||||
TIM_LLM_GATEWAY_LANGFUSE_FETCH_TIMEOUT_SECONDS=5
|
||||
TIM_LLM_GATEWAY_LANGFUSE_HOST=http://localhost:3005
|
||||
TIM_LLM_GATEWAY_LANGFUSE_MASK_SENSITIVE_DATA=true
|
||||
TIM_LLM_GATEWAY_LANGFUSE_MAX_RETRIES=3
|
||||
TIM_LLM_GATEWAY_LANGFUSE_PUBLIC_KEY=pk-lf-4a1e3921-5158-4fd3-a16d-7a77549fb312
|
||||
TIM_LLM_GATEWAY_LANGFUSE_SECRET_KEY=sk-lf-efc6fd59-c5ec-4858-b6ec-4aa129734915
|
||||
TIM_LLM_GATEWAY_LANGGRAPH_TIMEOUT=5
|
||||
TIM_LLM_GATEWAY_LOCAL_DIR=prompts
|
||||
TIM_LLM_GATEWAY_SOURCES=txt
|
||||
TIM_LLM_MODEL=openai.gpt-4.1
|
||||
TIM_LLM_OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q
|
||||
TIM_LLM_OCI_MODEL_ID=openai.gpt-4.1
|
||||
TIM_LLM_OCI_MODEL_ID_120B=ocid1.generativeaiendpoint.oc1.sa-saopaulo-1.amaaaaaaaehl73aa2amkvyvpsv3ts6rdbx6tzya43zgwx7vqljtjade2vjya
|
||||
TIM_LLM_OCI_MODEL_ID_20B=ocid1.generativeaiendpoint.oc1.sa-saopaulo-1.amaaaaaaaehl73aarckk4norgg263as5zleyp36tujnynrssoohshf3nwlhq
|
||||
TIM_LLM_OCI_MODEL_KWARGS={"temperature":0.0,"top_p":0.1,"reasoning_effort":"LOW","max_tokens":16384}
|
||||
TIM_LLM_OCI_MODEL_KWARGS_120B={"temperature":0.0,"top_p":0.1,"reasoning_effort":"LOW","max_tokens":16384}
|
||||
TIM_LLM_OCI_MODEL_KWARGS_20B={"temperature":0.0,"top_p":0.1,"reasoning_effort":"LOW","max_tokens":5000}
|
||||
TIM_LLM_OCI_SERVICE_ENDPOINT=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
|
||||
TIM_LLM_OCI_VARIANT=120b
|
||||
TIM_LLM_PROVIDER=oci
|
||||
TIM_MOCK_FIXTURES_DIR=/mnt/data/tim-ai-contas-agnt-agent-framework-oci-migrado/agente_contas_tim/gateway/mocks/fixtures
|
||||
TIM_OCI_FINGERPRINT=a6:e6:82:ca:d0:8e:8a:7f:58:14:ce:89:b4:b7:55:18
|
||||
TIM_OCI_KEY_FILE=/etc/oci/key.pem
|
||||
TIM_OCI_REGION=us-chicago-1
|
||||
TIM_OCI_TENANCY=ocid1.tenancy.oc1..aaaaaaaayzepbi32gafno3hj23o3d6tuquqetuw3yelxb5y7t2ft2wtm2i7q
|
||||
TIM_OCI_USER=ocid1.user.oc1..aaaaaaaanzctq4cfq6u7atitnpgb46rzcnewlnbljlipihlbcdnqscy7qmkq
|
||||
TIM_PREFETCH_INVOICE_CONTEXT_USE_CACHE=true
|
||||
TIM_PROFILE_FULL_URL=http://pmidfqa.internal.timbrasil.com.br:8000/access/v1/info/{msisdn}
|
||||
TIM_PROTOCOL_URL=http://pmidfqa.internal.timbrasil.com.br:8000/customers/v1/backOfficeSRopening
|
||||
TIM_RAG_DB_DSN=oradb23ai_high
|
||||
TIM_RAG_DB_USER=ADMIN
|
||||
TIM_RAG_DB_PASSWORD=Moniquinha19721972
|
||||
TIM_RAG_DB_CONFIG_DIR=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai
|
||||
TIM_RAG_ENABLED=true
|
||||
TIM_RAG_OCI_AUTH_FILE_LOCATION=/etc/oci/config
|
||||
TIM_RAG_OCI_SERVICE_ENDPOINT=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
|
||||
TIM_RAG_OCI_USE_OKE_WORKLOAD_IDENTITY=true
|
||||
TIM_SERVICE_REQUEST_STATUS_TIMEOUT=30
|
||||
TIM_SERVICE_REQUEST_STATUS_URL=http://pmidfqa.internal.timbrasil.com.br:8000/interactions/v1/statusServiceRequest
|
||||
TIM_SMS_CLIENT_ID=AIAGENTCR
|
||||
TIM_SMS_SENDER_ADDRESS=324
|
||||
TIM_SMS_URL=http://pmidfqa.internal.timbrasil.com.br:8000/invoices/v1/smsBarcode
|
||||
TIM_STATE_BACKEND=oracle
|
||||
#TIM_STATE_ORACLE_CONFIG_DIR=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai
|
||||
#TIM_STATE_ORACLE_DSN=oradb23ai_high
|
||||
#TIM_STATE_ORACLE_PASSWORD=Moniquinha19721972
|
||||
TIM_STATE_ORACLE_POOL_ENABLED=true
|
||||
TIM_STATE_ORACLE_POOL_INCREMENT=1
|
||||
TIM_STATE_ORACLE_POOL_MAX=8
|
||||
TIM_STATE_ORACLE_POOL_MIN=1
|
||||
TIM_STATE_ORACLE_POOL_WAIT_TIMEOUT_MS=5000
|
||||
#TIM_STATE_ORACLE_USER=admin
|
||||
TIM_TENANT_ID=tim-brasil
|
||||
TIM_TRACKING_ACTIVITIES_CHANNEL=AIAGENTCR
|
||||
TIM_TRACKING_ACTIVITIES_CLIENT_ID=AIAGENTCR
|
||||
TIM_TRACKING_ACTIVITIES_TIMEOUT=30
|
||||
TIM_TRACKING_ACTIVITIES_URL=http://pmidfqa.internal.timbrasil.com.br:8000/customers/v1/trackingActivities
|
||||
TIM_TRACKING_ACTIVITIES_USER_LOGIN=SIEBELPOS_INBOUND
|
||||
TIM_URL_BLOQUEIO_VAS=http://pmidfqa.internal.timbrasil.com.br:8000/customers/v1/partialServiceBlocking
|
||||
TIM_URL_CONSULTA_VAS=http://pmidfqa.internal.timbrasil.com.br:8000/access/v1/{msisdn}/partnerServices
|
||||
TIM_URL_INVOICE_RECOVER=http://pmidfqa.internal.timbrasil.com.br:8000/customers/v1/securePDF
|
||||
TIM_URL_PERFIL_FATURA=http://pmidfqa.internal.timbrasil.com.br:8000/customers/v1/completeInvoices
|
||||
TIM_USE_MOCK_GATEWAY=true
|
||||
TIM_VAS_HISTORY_TIMEOUT=30
|
||||
TIM_VAS_HISTORY_URL=http://pmidfqa.internal.timbrasil.com.br:8000/customers/v1/servicesHistory
|
||||
TIM_WORKFLOWS_DIR=workflows
|
||||
TIM_WORKFLOW_POSTGRES_DSN=
|
||||
USE_MOCK_LLM=false
|
||||
|
||||
TOOL_POLICIES_PATH=./config/tool_policies.yaml
|
||||
SKIP_RAG_WHEN_MCP_SUFFICIENT=true
|
||||
MCP_TOOL_CACHE_ENABLED=true
|
||||
MCP_TOOL_CACHE_TTL_SECONDS=60
|
||||
66
Dockerfile
Normal file
66
Dockerfile
Normal file
@@ -0,0 +1,66 @@
|
||||
# Estágio 1: Build
|
||||
FROM python:3.13.11-slim AS builder
|
||||
|
||||
ARG UV_INDEX_TIMBRASIL_PASSWORD
|
||||
ARG UV_INDEX_TIMBRASIL_USERNAME
|
||||
|
||||
# Dependências de compilação para pacotes com extensão nativa (ex.: annoy via nemoguardrails)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Instala o uv para gerenciar dependências de forma rápida e eficiente
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copia apenas os arquivos de dependências para aproveitar o cache do Docker
|
||||
COPY pyproject.toml uv.lock README.md ./
|
||||
COPY agent_framework_oci/libs/agent_framework ./agent_framework_oci/libs/agent_framework
|
||||
|
||||
# Sincroniza as dependências (sem instalar as de desenvolvimento e sem instalar o projeto local agora)
|
||||
# Analisar como ter lock independente da maquina do desenvolvedor
|
||||
RUN uv lock && uv sync --no-dev --no-install-project
|
||||
|
||||
# Estágio 2: Runtime
|
||||
FROM python:3.13.11-slim
|
||||
COPY tim-custom-ca.crt /usr/local/share/ca-certificates/tim-custom-ca.crt
|
||||
# Instala curl para o healthcheck e cria usuário não-root
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& groupadd -g 1000 appuser \
|
||||
&& update-ca-certificates \
|
||||
&& useradd -u 1000 -g 1000 -m appuser
|
||||
|
||||
ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
|
||||
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copia o ambiente virtual criado pelo uv
|
||||
COPY --from=builder /app/.venv /app/.venv
|
||||
|
||||
# Garante que o python use o venv por padrão
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# Copia o código da aplicação
|
||||
# Mudamos a ordem para copiar apenas o necessário e definir permissões
|
||||
COPY . .
|
||||
RUN chown -R appuser:appuser /app
|
||||
|
||||
# Diretório padrão para configs OCI (montado via volume em runtime)
|
||||
RUN mkdir -p /config && chown appuser:appuser /config
|
||||
|
||||
# Expõe a porta que o FastAPI utilizará
|
||||
EXPOSE 8000
|
||||
|
||||
# Variáveis de ambiente padrão
|
||||
ENV TIM_APP_HOST=0.0.0.0
|
||||
ENV TIM_APP_PORT=8000
|
||||
ENV PYTHONPATH=/app
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Troca para o usuário não-root
|
||||
USER appuser
|
||||
|
||||
# Comando para iniciar a aplicação
|
||||
CMD ["python", "-m", "agente_contas_tim.main"]
|
||||
118
Makefile
Normal file
118
Makefile
Normal file
@@ -0,0 +1,118 @@
|
||||
# Variables
|
||||
PROJECT_NAME=agente-de-contas-backend
|
||||
# Detecta o comando de compose disponível, priorizando Podman
|
||||
DOCKER_COMPOSE := $(shell command -v podman >/dev/null 2>&1 && (podman compose version >/dev/null 2>&1 && echo "podman compose" || echo "podman-compose") || (command -v docker-compose >/dev/null 2>&1 && echo "docker-compose" || echo "docker compose"))
|
||||
|
||||
.PHONY: help deploy down logs shell check-tools health-check local-observability-up local-observability-down prepare-local-pubsub-env validate-local-pubsub pull-local-pubsub local-api-pubsub-up local-api-pubsub-down local-api-pubsub-logs validate-oci-migration-setup validate-oci-migration llm-test add-case
|
||||
|
||||
help: ## Show this help menu
|
||||
@echo "Available targets for Project: $(PROJECT_NAME)"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-15s %s\n", $$1, $$2}'
|
||||
|
||||
deploy: down ## Full clean deployment flow (Down -> Build No-Cache -> Up)
|
||||
@echo "Building Docker image (no cache)..."
|
||||
$(DOCKER_COMPOSE) build --no-cache
|
||||
@echo "Starting containers..."
|
||||
$(DOCKER_COMPOSE) up -d
|
||||
@echo "Deployment completed!"
|
||||
|
||||
down: ## Stop and remove containers
|
||||
@echo "Stopping containers..."
|
||||
$(DOCKER_COMPOSE) down
|
||||
|
||||
logs: ## Tail the containers logs
|
||||
@echo "Showing logs..."
|
||||
$(DOCKER_COMPOSE) logs -f backend
|
||||
|
||||
shell: ## Open a shell inside the backend container
|
||||
@echo "Opening shell..."
|
||||
$(DOCKER_COMPOSE) exec backend /bin/bash
|
||||
|
||||
check-tools: ## Check if required tools are installed
|
||||
@echo "Checking tools..."
|
||||
@command -v make >/dev/null 2>&1 && echo " Make: [OK]" || echo " Make: [MISSING]"
|
||||
@command -v podman >/dev/null 2>&1 && echo " Podman: [OK]" || echo " Podman: [NOT FOUND]"
|
||||
@podman compose version >/dev/null 2>&1 && echo " Podman Compose Plugin: [OK]" || echo " Podman Compose Plugin: [MISSING]"
|
||||
@echo "Selected compose command: $(DOCKER_COMPOSE)"
|
||||
|
||||
health-check: ## Test the application health check
|
||||
@echo "Testing health check..."
|
||||
@curl -f http://localhost:8000/health || echo "Health check failed!"
|
||||
|
||||
local-oracle-state-test: ## Test Oracle state using local .env (RAG disabled)
|
||||
@test -f .env || (echo ".env local nao encontrado"; exit 1)
|
||||
@set -a; . ./.env; set +a; TIM_RUN_ORACLE_STATE_INTEGRATION=1 uv run pytest -q tests/integration/test_oracle_state_pool_integration.py
|
||||
|
||||
local-api-oracle-state: ## Start API with Oracle state from local .env (RAG disabled)
|
||||
@test -f .env || (echo ".env local nao encontrado"; exit 1)
|
||||
@set -a; . ./.env; set +a; uv run uvicorn agente_contas_tim.api.app:app --reload
|
||||
|
||||
local-observability-up: ## Start local Pub/Sub emulator and Mongo sequence
|
||||
COMPOSE_DISABLE_ENV_FILE=1 $(DOCKER_COMPOSE) -f docker-compose.local-observability.yml up -d
|
||||
|
||||
local-observability-down: ## Stop local observability fakes
|
||||
COMPOSE_DISABLE_ENV_FILE=1 $(DOCKER_COMPOSE) -f docker-compose.local-observability.yml down
|
||||
|
||||
prepare-local-pubsub-env: ## Create local Pub/Sub fake credentials, topic and subscription
|
||||
uv run --locked --no-sync python scripts/validate_local_pubsub_emulator.py --prepare-only
|
||||
|
||||
validate-local-pubsub: ## Emit IC/RCT/NOC through the OCI bridge into local Pub/Sub/OTLP fakes
|
||||
uv run --locked --no-sync python scripts/validate_local_pubsub_emulator.py --require-otel
|
||||
|
||||
pull-local-pubsub: ## Pull messages already published by the app to local Pub/Sub emulator
|
||||
uv run --locked --no-sync python scripts/validate_local_pubsub_emulator.py --pull-only --pull-timeout 60
|
||||
|
||||
local-api-pubsub-up: local-observability-up prepare-local-pubsub-env ## Start backend API pointing to local Pub/Sub emulator
|
||||
COMPOSE_DISABLE_ENV_FILE=1 $(DOCKER_COMPOSE) -f docker-compose.yml -f docker-compose.local-api-pubsub.yml up -d --build backend
|
||||
|
||||
local-api-pubsub-down: ## Stop backend API started with local Pub/Sub emulator override
|
||||
COMPOSE_DISABLE_ENV_FILE=1 $(DOCKER_COMPOSE) -f docker-compose.yml -f docker-compose.local-api-pubsub.yml down
|
||||
|
||||
local-api-pubsub-logs: ## Tail backend API logs for local Pub/Sub emulator validation
|
||||
COMPOSE_DISABLE_ENV_FILE=1 $(DOCKER_COMPOSE) -f docker-compose.yml -f docker-compose.local-api-pubsub.yml logs -f backend
|
||||
|
||||
validate-oci-migration-setup: ## Sync dependencies for local OCI observer migration validation
|
||||
uv sync --locked
|
||||
|
||||
validate-oci-migration: ## Validate locally phases 1-4 of the OCI observer migration
|
||||
@echo "Checking uv.lock consistency..."
|
||||
uv lock --check
|
||||
@echo "Checking legacy framework dependency/import removal..."
|
||||
@if if command -v rg >/dev/null 2>&1; then \
|
||||
rg -n 'agent-framework==2\.6\.4|agent_framework\.guardrails_old|guardrails_old|Compass|compass' pyproject.toml uv.lock agente_contas_tim workflows tests agent_framework_oci/libs/agent_framework/src; \
|
||||
else \
|
||||
grep -RInE 'agent-framework==2\.6\.4|agent_framework\.guardrails_old|guardrails_old|Compass|compass' pyproject.toml uv.lock agente_contas_tim workflows tests agent_framework_oci/libs/agent_framework/src; \
|
||||
fi; then \
|
||||
echo "Legacy framework dependency/import references found"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Legacy framework dependency/import references: OK"; \
|
||||
fi
|
||||
@echo "Checking Python compilation..."
|
||||
uv run --locked --no-sync python -m compileall agente_contas_tim agent_framework_oci/libs/agent_framework/src/agent_framework -q
|
||||
@echo "Checking diff whitespace..."
|
||||
git diff --check develop...HEAD
|
||||
@echo "Running focused migration tests..."
|
||||
uv run --locked --no-sync python -m pytest \
|
||||
tests/observability/test_oracle_migration_phase1_contract.py \
|
||||
tests/observability/test_agent_framework_bridge.py \
|
||||
tests/observability/test_agent_framework_oci_tim_contract.py \
|
||||
tests/observability/test_observability.py \
|
||||
tests/observability/test_langfuse_pii_masking.py \
|
||||
tests/api/test_health_routes.py \
|
||||
tests/api/test_app_metadata.py \
|
||||
tests/integrations/test_business_context.py \
|
||||
tests/integrations/test_grl_events.py \
|
||||
tests/judges/test_evaluator.py \
|
||||
tests/api/test_agent_sse_context_aliases.py \
|
||||
tests/agent/test_invoice_context_provider.py \
|
||||
tests/guardrails/test_compliance_anatel.py \
|
||||
tests/guardrails/test_ausencia_oferta_proativa.py \
|
||||
tests/guardrails/test_verbalizacao_prematura.py \
|
||||
-q
|
||||
|
||||
llm-test: ## Roda uma suíte LLM por SUITE: intent|intent-transcricao|orch|matcher|processing-interruption|workflow-answer|reescrita|guardrails|input|output|det|pinj|oos|aoferta|tox|revprec|... [ARGS="--repeat 3 --only X"]
|
||||
RUN_LLM_TESTS=1 uv run --locked --no-sync python -m tests.llm_tests $(SUITE) $(ARGS)
|
||||
|
||||
add-case: ## Consome o paste de tests/llm_tests/paste_inbox.json e cria o caso (intent/orch auto); esvazia o inbox
|
||||
uv run --locked --no-sync python -m tests.llm_tests.add_case_from_paste $(ARGS)
|
||||
37
README.md
Normal file
37
README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# TIM Contas — agent_framework_oci
|
||||
|
||||
Reconstrução framework-native do Agent Contas. O código executável em `app/`, `mcp/` e `config/` não depende do pacote anterior do Contas.
|
||||
|
||||
## Comece aqui
|
||||
|
||||
Leia **`docs/MANUAL_AGENT_CONTAS_MIGRADO.md`**.
|
||||
|
||||
## Arquitetura
|
||||
|
||||
- `app/`: backend e agentes de domínio sobre o LangGraph/framework.
|
||||
- `app/domain/contas/`: regras e integrações específicas do Contas, sem runtime de agente próprio.
|
||||
- `contas_mcp/servers/contas_mcp_server/`: exposição MCP das operações de domínio.
|
||||
- `config/`: intents, tools, policies, identity, guardrails, judges e prompts.
|
||||
- `agent_framework_oci/`: infraestrutura compartilhada reutilizada.
|
||||
|
||||
## Smoke test
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run uvicorn contas_mcp.servers.contas_mcp_server.main:app --port 8400
|
||||
uv run uvicorn app.main:app --port 8000
|
||||
```
|
||||
|
||||
Em outro terminal:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=".:agent_framework_oci/libs/agent_framework/src" python scripts/smoke_mcp.py
|
||||
```
|
||||
|
||||
## Regra de arquitetura
|
||||
|
||||
```bash
|
||||
grep -R "agente_contas_tim" app mcp config
|
||||
```
|
||||
|
||||
Deve retornar zero ocorrências.
|
||||
209
agent_framework_oci/.env
Normal file
209
agent_framework_oci/.env
Normal file
@@ -0,0 +1,209 @@
|
||||
###############################################################################
|
||||
# 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
|
||||
209
agent_framework_oci/.env.example
Normal file
209
agent_framework_oci/.env.example
Normal file
@@ -0,0 +1,209 @@
|
||||
###############################################################################
|
||||
# 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
|
||||
24
agent_framework_oci/.oca/custom_code_review_guidelines.txt
Normal file
24
agent_framework_oci/.oca/custom_code_review_guidelines.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
# Sample guideline, please follow similar structure for guideline with code samples
|
||||
# 1. Suggest using streams instead of simple loops for better readability.
|
||||
# <example>
|
||||
# *Comment:
|
||||
# Category: Minor
|
||||
# Issue: Use streams instead of a loop for better readability.
|
||||
# Code Block:
|
||||
#
|
||||
# ```java
|
||||
# // Calculate squares of numbers
|
||||
# List<Integer> squares = new ArrayList<>();
|
||||
# for (int number : numbers) {
|
||||
# squares.add(number * number);
|
||||
# }
|
||||
# ```
|
||||
# Recommendation:
|
||||
#
|
||||
# ```java
|
||||
# // Calculate squares of numbers
|
||||
# List<Integer> squares = Arrays.stream(numbers)
|
||||
# .map(n -> n * n) // Map each number to its square
|
||||
# .toList();
|
||||
# ```
|
||||
# </example>
|
||||
@@ -0,0 +1,24 @@
|
||||
# Sample guideline, please follow similar structure for guideline with code samples
|
||||
# 1. Suggest using streams instead of simple loops for better readability.
|
||||
# <example>
|
||||
# *Comment:
|
||||
# Category: Minor
|
||||
# Issue: Use streams instead of a loop for better readability.
|
||||
# Code Block:
|
||||
#
|
||||
# ```java
|
||||
# // Calculate squares of numbers
|
||||
# List<Integer> squares = new ArrayList<>();
|
||||
# for (int number : numbers) {
|
||||
# squares.add(number * number);
|
||||
# }
|
||||
# ```
|
||||
# Recommendation:
|
||||
#
|
||||
# ```java
|
||||
# // Calculate squares of numbers
|
||||
# List<Integer> squares = Arrays.stream(numbers)
|
||||
# .map(n -> n * n) // Map each number to its square
|
||||
# .toList();
|
||||
# ```
|
||||
# </example>
|
||||
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
# Correção: deadlock/espera cross-loop na geração de sequence
|
||||
|
||||
## Problema
|
||||
|
||||
A API síncrona `agent_framework.observer.event()` podia ser chamada em uma worker thread sem event loop ativo. Nesse caso, a implementação anterior executava `asyncio.run(aevent(...))`, criando um novo event loop temporário. Ao mesmo tempo, `analytics/tim_sequence.py` compartilhava instâncias globais de `asyncio.Lock` (`_mongo_index_lock` e `_memory_lock`) entre chamadas que podiam vir de event loops diferentes.
|
||||
|
||||
Na primeira operação Mongo, `_ensure_mongo_ttl_index_once()` mantinha `_mongo_index_lock` durante a criação do índice TTL. A contenção por outro loop podia deixar a segunda chamada aguardando indefinidamente.
|
||||
|
||||
## Alterações aplicadas
|
||||
|
||||
1. `observer.py`
|
||||
- removido `asyncio.run()` do caminho síncrono de `event()`;
|
||||
- adicionado um event loop dedicado e reutilizável para chamadas síncronas;
|
||||
- submissão cross-thread feita com `asyncio.run_coroutine_threadsafe()`;
|
||||
- encerramento best-effort do loop no shutdown do processo.
|
||||
|
||||
2. `analytics/tim_sequence.py`
|
||||
- `_mongo_index_lock`: `asyncio.Lock` -> `threading.Lock`;
|
||||
- `_memory_lock`: `asyncio.Lock` -> `threading.Lock`;
|
||||
- inicialização do índice TTL movida para uma função síncrona protegida por lock de thread e chamada via `asyncio.to_thread()`;
|
||||
- o contador de fallback em memória usa uma seção crítica curta e thread-safe.
|
||||
|
||||
3. Testes
|
||||
- `tests/test_observer_cross_loop_deadlock_fix.py` valida:
|
||||
- múltiplas worker threads usando `event()` compartilham o mesmo loop síncrono do observer;
|
||||
- sequence em memória permanece monotônica entre event loops independentes;
|
||||
- criação do índice TTL ocorre apenas uma vez sob contenção cross-loop.
|
||||
|
||||
## Validação executada
|
||||
|
||||
```bash
|
||||
PYTHONPATH=libs/agent_framework/src pytest -q tests/test_observer_cross_loop_deadlock_fix.py
|
||||
```
|
||||
|
||||
Resultado: `3 passed`.
|
||||
|
||||
A suíte completa do repositório possui falhas preexistentes/independentes desta alteração, incluindo conflitos de coleta de arquivos `test_long_term_memory.py`, caminhos estáticos de template e testes de checkpoint/workflow. Esses itens não foram alterados por esta correção.
|
||||
@@ -0,0 +1,133 @@
|
||||
# Inventário — Agent Gateway + MCP Gateway Overlay
|
||||
|
||||
Este inventário lista os arquivos incluídos no overlay `agent_platform_agent_gateway_mcp_gateway_overlay.zip`, indicando a área, o tipo de alteração e a finalidade de cada arquivo.
|
||||
|
||||
## Resumo
|
||||
|
||||
| Área | Quantidade |
|
||||
|---|---:|
|
||||
| Documentação | 1 |
|
||||
| Agent Gateway | 10 |
|
||||
| MCP Gateway | 5 |
|
||||
| Agent Framework | 4 |
|
||||
| Template Backend | 2 |
|
||||
| MCP Server Mock | 2 |
|
||||
| Deploy | 2 |
|
||||
|
||||
## Arquivos por área
|
||||
|
||||
### Documentação
|
||||
|
||||
| Arquivo | Tipo | Finalidade |
|
||||
|---|---|---|
|
||||
| `README_AGENT_GATEWAY_AND_MCP_GATEWAY_EVOLUTION.md` | Novo / overlay | Documento principal do overlay. Explica a nova arquitetura sem AI Gateway separado, com Agent Gateway governando políticas/modelos e MCP Gateway separado para tools. |
|
||||
|
||||
### Agent Gateway
|
||||
|
||||
| Arquivo | Tipo | Finalidade |
|
||||
|---|---|---|
|
||||
| `apps/agent_gateway/app/config/governance_loader.py` | Novo / overlay | Carrega o arquivo YAML de governança do Agent Gateway a partir de AGENT_GATEWAY_GOVERNANCE_CONFIG. |
|
||||
| `apps/agent_gateway/app/governance/__init__.py` | Novo / overlay | Inicializa o pacote Python de governança do Agent Gateway. |
|
||||
| `apps/agent_gateway/app/governance/audit.py` | Novo / overlay | Centraliza logging/auditoria das decisões de governança do Agent Gateway, com proteção simples para não logar mensagem completa. |
|
||||
| `apps/agent_gateway/app/governance/evaluation_hooks.py` | Novo / overlay | Hooks antes e depois da chamada ao backend/runtime. Serve para amostragem, evaluator, scoring ou integração futura com Langfuse. |
|
||||
| `apps/agent_gateway/app/governance/model_policies.py` | Novo / overlay | Resolve políticas de modelo/profile no Agent Gateway. Define qual provider/model/profile deve ser usado por operação, tenant e agente. |
|
||||
| `apps/agent_gateway/app/governance/rate_limit.py` | Novo / overlay | Implementa rate limit em memória por tenant, agente e canal antes de encaminhar a requisição ao backend/runtime. |
|
||||
| `apps/agent_gateway/app/governance/usage.py` | Novo / overlay | Hook para registrar uso de gateway, políticas aplicadas e respostas do backend. Pronto para plugar métricas, banco, Langfuse ou OTEL. |
|
||||
| `apps/agent_gateway/app/governance_middleware.py` | Novo / overlay | Componente principal de governança do Agent Gateway. Aplica rate limit, resolve model_policy, gera headers/metadados e executa hooks antes/depois do backend. |
|
||||
| `apps/agent_gateway/app/routes/governed_proxy_example.py` | Novo / overlay | Exemplo de rota governada para demonstrar como aplicar governança antes de encaminhar para o Agent Backend/Runtime. |
|
||||
| `apps/agent_gateway/config/gateway_governance.yaml` | Novo / overlay | Configuração de governança do Agent Gateway: profiles, operation_profiles, providers permitidos, rate limits, headers propagados e evaluation hooks. |
|
||||
|
||||
### MCP Gateway
|
||||
|
||||
| Arquivo | Tipo | Finalidade |
|
||||
|---|---|---|
|
||||
| `apps/mcp_gateway/Dockerfile` | Novo / overlay | Imagem Docker do MCP Gateway. |
|
||||
| `apps/mcp_gateway/app/__init__.py` | Novo / overlay | Inicializa o pacote Python da aplicação MCP Gateway. |
|
||||
| `apps/mcp_gateway/app/main.py` | Novo / overlay | Aplicação FastAPI do MCP Gateway. Expõe health, ready, catálogo de tools e endpoint de invoke com auth, autorização, mapping, cache, timeout e retry. |
|
||||
| `apps/mcp_gateway/config/mcp_gateway.yaml` | Novo / overlay | Configuração central do MCP Gateway: MCP servers, tools, versões, cache, timeout, retry, autorização por agente/canal e mapping BusinessContext → parâmetros. |
|
||||
| `apps/mcp_gateway/requirements.txt` | Novo / overlay | Dependências Python do MCP Gateway. |
|
||||
|
||||
### Agent Framework
|
||||
|
||||
| Arquivo | Tipo | Finalidade |
|
||||
|---|---|---|
|
||||
| `libs/agent_framework/src/agent_framework/gateway_policy_context.py` | Novo / overlay | Helper no framework para o Runtime ler a política de modelo enviada pelo Agent Gateway em state['metadata']['model_policy']. |
|
||||
| `libs/agent_framework/src/agent_framework/gateways/__init__.py` | Novo / overlay | Inicializa o pacote de clients de gateways no framework, exportando MCPGatewayClient. |
|
||||
| `libs/agent_framework/src/agent_framework/gateways/mcp_gateway_client.py` | Novo / overlay | Client assíncrono do framework para chamar o MCP Gateway: listar tools e executar tools. |
|
||||
| `libs/agent_framework/src/agent_framework/runtime_mcp_gateway_adapter.py` | Novo / overlay | Mixin opcional para agentes/runtime chamarem tools via MCP Gateway e anexarem resultados em state['mcp_results']. |
|
||||
|
||||
### Template Backend
|
||||
|
||||
| Arquivo | Tipo | Finalidade |
|
||||
|---|---|---|
|
||||
| `templates/agent_template_backend/app/mcp_gateway_client_factory.py` | Novo / overlay | Factory no template backend para construir MCPGatewayClient a partir de variáveis de ambiente. |
|
||||
|
||||
### MCP Server Mock
|
||||
|
||||
| Arquivo | Tipo | Finalidade |
|
||||
|---|---|---|
|
||||
| `mcp/servers/mock_telecom_mcp/app.py` | Novo / overlay | Mock MCP Server com tools consultar_fatura e consultar_pagamentos para validar o MCP Gateway localmente. |
|
||||
| `mcp/servers/mock_telecom_mcp/requirements.txt` | Novo / overlay | Dependências do mock MCP Server de telecom usado para testes locais. |
|
||||
|
||||
### Deploy
|
||||
|
||||
| Arquivo | Tipo | Finalidade |
|
||||
|---|---|---|
|
||||
| `deploy/docker/docker-compose.mcp-gateway.yml` | Novo / overlay | Docker Compose para subir MCP Gateway e mock_telecom_mcp localmente. |
|
||||
| `deploy/k8s/mcp-gateway.yaml` | Novo / overlay | Manifest Kubernetes de Deployment e Service do MCP Gateway. |
|
||||
|
||||
## Observações de integração
|
||||
|
||||
### Agent Gateway
|
||||
|
||||
Os arquivos em `apps/agent_gateway` não criam um novo serviço. Eles evoluem o Agent Gateway existente para atuar como gateway dedicado da plataforma, centralizando:
|
||||
|
||||
- políticas de modelo/profile;
|
||||
- rate limit;
|
||||
- auditoria;
|
||||
- hooks de avaliação;
|
||||
- propagação de metadados de governança para o Runtime.
|
||||
|
||||
A rota `governed_proxy_example.py` é um exemplo de integração. O handler real do `POST /gateway/message` deve aplicar:
|
||||
|
||||
```python
|
||||
governed_body, headers = governance.prepare_backend_request(body)
|
||||
```
|
||||
|
||||
antes de chamar o backend/runtime, e:
|
||||
|
||||
```python
|
||||
return governance.process_backend_response(data)
|
||||
```
|
||||
|
||||
após receber a resposta.
|
||||
|
||||
### MCP Gateway
|
||||
|
||||
O MCP Gateway é um serviço separado. Ele centraliza:
|
||||
|
||||
- catálogo de tools;
|
||||
- autorização por agente/canal;
|
||||
- versionamento de tools;
|
||||
- mapping de BusinessContext para parâmetros;
|
||||
- cache;
|
||||
- timeout;
|
||||
- retry;
|
||||
- auditoria simples.
|
||||
|
||||
### Runtime / Backend
|
||||
|
||||
O Runtime continua responsável por:
|
||||
|
||||
- LangGraph;
|
||||
- estado;
|
||||
- memória;
|
||||
- checkpoints;
|
||||
- fluxo;
|
||||
- providers LLM existentes.
|
||||
|
||||
O Runtime passa a chamar tools via MCP Gateway usando `MCPGatewayClient` e/ou `MCPGatewayRuntimeMixin`.
|
||||
|
||||
### AI Gateway
|
||||
|
||||
Este overlay não cria `apps/ai_gateway`. A governança de modelo fica no Agent Gateway, e a execução LLM continua no Runtime/backend usando os providers já existentes.
|
||||
@@ -0,0 +1,520 @@
|
||||
### Long-Term Memory Implementation Guide
|
||||
|
||||
### Concept
|
||||
|
||||
Long-Term Memory (LTM) is the `agent_framework` capability that stores and retrieves durable facts beyond the lifetime of a conversation session.
|
||||
|
||||
Unlike message history, which is normally associated with a `session_id`, Long-Term Memory is associated with the business identity of the user or customer. In the current implementation, this identity consists of:
|
||||
|
||||
```text
|
||||
tenant_id
|
||||
agent_id
|
||||
customer_key
|
||||
```
|
||||
|
||||
This allows an agent to retrieve preferences, identity information, projects and constraints even when a new session is created.
|
||||
|
||||
### Purpose
|
||||
|
||||
Long-Term Memory is used to:
|
||||
|
||||
- maintain continuity across sessions;
|
||||
- personalize responses;
|
||||
- prevent users from repeating previously supplied information;
|
||||
- reduce the need to send the full conversation history to the model;
|
||||
- store preferences, current projects, preferred names and constraints;
|
||||
- isolate memory across tenants, agents and customers.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Session A:
|
||||
"Call me Cris. My preferred language is Python."
|
||||
|
||||
Session B, with another session_id and the same customer_key:
|
||||
"What do you remember about me?"
|
||||
|
||||
Expected response:
|
||||
"Your preferred name is Cris and your preferred language is Python."
|
||||
```
|
||||
|
||||
### Memory type differences
|
||||
|
||||
#### Conversation Memory
|
||||
|
||||
Stores messages from the current conversation and is normally associated with the `session_id`.
|
||||
|
||||
#### Summary Memory
|
||||
|
||||
Stores a summary of the conversation to reduce the context size sent to the model.
|
||||
|
||||
#### Long-Term Memory
|
||||
|
||||
Stores durable facts across sessions and is associated with the business identity, primarily the `customer_key`.
|
||||
|
||||
### Components
|
||||
|
||||
#### LongTermMemoryManager
|
||||
|
||||
Coordinates:
|
||||
|
||||
- memory loading;
|
||||
- identity-based retrieval;
|
||||
- context rendering;
|
||||
- durable fact extraction;
|
||||
- fact persistence;
|
||||
- deduplication and updates.
|
||||
|
||||
#### LongTermMemoryStore
|
||||
|
||||
Persistence interface used by the manager.
|
||||
|
||||
#### SQLiteLongTermMemoryStore
|
||||
|
||||
Reference implementation based on SQLite.
|
||||
|
||||
It is suitable for:
|
||||
|
||||
- local development;
|
||||
- testing;
|
||||
- demonstrations;
|
||||
- low-scale environments.
|
||||
|
||||
#### InMemoryLongTermMemoryStore
|
||||
|
||||
In-memory implementation used for quick tests.
|
||||
|
||||
Its content is lost when the backend process stops.
|
||||
|
||||
#### LongTermMemoryExtractor
|
||||
|
||||
Identifies durable facts in messages.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
preferred_name = Cris
|
||||
preferred_language = Python
|
||||
current_project = Atlas
|
||||
```
|
||||
|
||||
#### LongTermMemoryItem
|
||||
|
||||
Data model representing a persisted item, including identity, key, value, category, confidence and metadata.
|
||||
|
||||
#### AgentRuntime
|
||||
|
||||
Loads memory before agent execution and injects the rendered context into the prompt.
|
||||
|
||||
#### persist_long_term_memory node
|
||||
|
||||
LangGraph node responsible for persisting facts after the final response has been generated and validated.
|
||||
|
||||
### File structure
|
||||
|
||||
```text
|
||||
libs/
|
||||
└── agent_framework/
|
||||
└── src/
|
||||
└── agent_framework/
|
||||
└── memory/
|
||||
├── __init__.py
|
||||
├── long_term_extractor.py
|
||||
├── long_term_memory.py
|
||||
├── long_term_models.py
|
||||
└── long_term_store.py
|
||||
```
|
||||
|
||||
### Execution flow
|
||||
|
||||
```text
|
||||
User message
|
||||
│
|
||||
▼
|
||||
AgentRuntime.prepare_memory_context()
|
||||
│
|
||||
├── Conversation Memory
|
||||
├── Summary Memory
|
||||
└── Long-Term Memory
|
||||
│
|
||||
▼
|
||||
long_term_memory_context
|
||||
│
|
||||
▼
|
||||
Agent prompt
|
||||
│
|
||||
▼
|
||||
Agent
|
||||
│
|
||||
▼
|
||||
Guardrails / Judges / Supervisor
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
LongTermMemoryExtractor
|
||||
│
|
||||
▼
|
||||
LongTermMemoryStore
|
||||
```
|
||||
|
||||
### Framework configuration
|
||||
|
||||
### New modules
|
||||
|
||||
Copy:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_extractor.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_memory.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_models.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_store.py
|
||||
```
|
||||
|
||||
### Update memory/__init__.py
|
||||
|
||||
Export the Long-Term Memory components:
|
||||
|
||||
```python
|
||||
from agent_framework.memory.long_term_memory import (
|
||||
LongTermMemoryManager,
|
||||
create_long_term_memory_manager,
|
||||
)
|
||||
from agent_framework.memory.long_term_models import LongTermMemoryItem
|
||||
from agent_framework.memory.long_term_store import (
|
||||
InMemoryLongTermMemoryStore,
|
||||
LongTermMemoryStore,
|
||||
SQLiteLongTermMemoryStore,
|
||||
create_long_term_memory_store,
|
||||
)
|
||||
```
|
||||
|
||||
### Update settings.py
|
||||
|
||||
Add:
|
||||
|
||||
```python
|
||||
ENABLE_LONG_TERM_MEMORY: bool = False
|
||||
LONG_TERM_MEMORY_PROVIDER: str = "sqlite"
|
||||
LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db"
|
||||
LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory"
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True
|
||||
```
|
||||
|
||||
### AgentRuntime integration
|
||||
|
||||
The runtime must:
|
||||
|
||||
1. verify that the feature is enabled;
|
||||
2. create the manager when needed;
|
||||
3. retrieve facts using the identity;
|
||||
4. populate the workflow state;
|
||||
5. inject the rendered context into the prompt.
|
||||
|
||||
State fields:
|
||||
|
||||
```python
|
||||
long_term_memories: list[dict]
|
||||
long_term_memory_context: str
|
||||
long_term_memory_write_result: dict
|
||||
```
|
||||
|
||||
### AgentWorkflow initialization
|
||||
|
||||
Create the manager in `AgentWorkflow`:
|
||||
|
||||
```python
|
||||
self.long_term_memory_manager = create_long_term_memory_manager(
|
||||
settings,
|
||||
telemetry=telemetry,
|
||||
)
|
||||
```
|
||||
|
||||
### Correct agent initialization
|
||||
|
||||
Do not pass `long_term_memory_manager` through `agent_kwargs` when the constructors of `BillingAgent`, `ProductAgent`, `OrdersAgent` and `SupportAgent` do not declare that parameter.
|
||||
|
||||
This initialization causes an error:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"settings": settings,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
"long_term_memory_manager": self.long_term_memory_manager,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
```
|
||||
|
||||
Resulting error:
|
||||
|
||||
```text
|
||||
TypeError: BillingAgent.__init__() got an unexpected keyword argument
|
||||
'long_term_memory_manager'
|
||||
```
|
||||
|
||||
The recommended approach is to create agents using their existing signatures and inject the manager as an attribute after initialization:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"tool_router": getattr(self, "tool_router", None),
|
||||
"rag_service": self.rag_service,
|
||||
"cache": self.cache,
|
||||
"settings": settings,
|
||||
"observer": self.observer,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
self.product = ProductAgent(llm, **agent_kwargs)
|
||||
self.orders = OrdersAgent(llm, **agent_kwargs)
|
||||
self.support = SupportAgent(llm, **agent_kwargs)
|
||||
|
||||
for agent in (
|
||||
self.billing,
|
||||
self.product,
|
||||
self.orders,
|
||||
self.support,
|
||||
):
|
||||
agent.long_term_memory_manager = self.long_term_memory_manager
|
||||
```
|
||||
|
||||
This approach avoids changing every agent constructor and keeps the feature encapsulated in the framework.
|
||||
|
||||
### LangGraph configuration
|
||||
|
||||
Register the node:
|
||||
|
||||
```python
|
||||
builder.add_node(
|
||||
"persist_long_term_memory",
|
||||
self._node(
|
||||
"persist_long_term_memory",
|
||||
self.persist_long_term_memory,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Update the edges:
|
||||
|
||||
```python
|
||||
builder.add_edge(
|
||||
"supervisor_review",
|
||||
"persist_long_term_memory",
|
||||
)
|
||||
builder.add_edge(
|
||||
"persist_long_term_memory",
|
||||
"persist",
|
||||
)
|
||||
```
|
||||
|
||||
Implement:
|
||||
|
||||
```python
|
||||
async def persist_long_term_memory(
|
||||
self,
|
||||
state: AgentState,
|
||||
) -> dict[str, object]:
|
||||
result = await self.long_term_memory_manager.persist_turn(state)
|
||||
|
||||
return {
|
||||
"long_term_memory_write_result": result,
|
||||
}
|
||||
```
|
||||
|
||||
Final flow:
|
||||
|
||||
```text
|
||||
supervisor_review
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
persist
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
```env
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### SQLite database path
|
||||
|
||||
A relative path is resolved from the directory in which the backend is started.
|
||||
|
||||
To prevent different databases from being created accidentally, prefer an absolute path in development environments:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db
|
||||
```
|
||||
|
||||
Create the directory before starting:
|
||||
|
||||
```bash
|
||||
mkdir -p data
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
### Test 1 — Persistence
|
||||
|
||||
Send:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-a",
|
||||
"customer_key": "11999999999",
|
||||
"message": "Call me Cris. My preferred language is Python and my current project is Atlas."
|
||||
}
|
||||
```
|
||||
|
||||
### Test 2 — Retrieval in another session
|
||||
|
||||
Use another `session_id` while keeping the same `customer_key`:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-b",
|
||||
"customer_key": "11999999999",
|
||||
"message": "What do you remember about me, my preferences and my project?"
|
||||
}
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
```text
|
||||
Your preferred name is Cris.
|
||||
Your preferred language is Python.
|
||||
Your current project is Atlas.
|
||||
```
|
||||
|
||||
### Test 3 — Isolation
|
||||
|
||||
Use another customer:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-c",
|
||||
"customer_key": "another-customer",
|
||||
"message": "What is my preferred name and current project?"
|
||||
}
|
||||
```
|
||||
|
||||
The data associated with `11999999999` must not be returned.
|
||||
|
||||
### Test 4 — Frontend reset
|
||||
|
||||
Restart or reset the frontend and verify that it still sends the same `customer_key`.
|
||||
|
||||
Memory must survive a `session_id` change. Resetting the frontend does not delete the SQLite database.
|
||||
|
||||
### Test 5 — Backend restart
|
||||
|
||||
Restart Uvicorn and repeat the query.
|
||||
|
||||
With:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
memory must remain available.
|
||||
|
||||
With:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
memory is lost when the process stops.
|
||||
|
||||
### Direct SQLite verification
|
||||
|
||||
Find the database:
|
||||
|
||||
```bash
|
||||
find . -name "agent_framework.db" -type f
|
||||
```
|
||||
|
||||
Open it:
|
||||
|
||||
```bash
|
||||
sqlite3 ./data/agent_framework.db
|
||||
```
|
||||
|
||||
Query:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
tenant_id,
|
||||
agent_id,
|
||||
customer_key,
|
||||
memory_type,
|
||||
memory_key,
|
||||
memory_value,
|
||||
confidence,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM agentfw_long_term_memory
|
||||
ORDER BY updated_at DESC;
|
||||
```
|
||||
|
||||
### Success criteria
|
||||
|
||||
The implementation is working when:
|
||||
|
||||
- memory is retrieved with another `session_id`;
|
||||
- the same `customer_key` retrieves previous facts;
|
||||
- another `customer_key` cannot access those facts;
|
||||
- restarting the frontend does not erase memory;
|
||||
- restarting the backend does not erase memory when using SQLite;
|
||||
- the `persist_long_term_memory` node runs;
|
||||
- the prompt receives `long_term_memory_context`.
|
||||
|
||||
### Best practices
|
||||
|
||||
- Persist only durable facts.
|
||||
- Do not store the complete conversation as Long-Term Memory.
|
||||
- Isolate data by `tenant_id`, `agent_id` and `customer_key`.
|
||||
- Do not use `session_id` as the permanent user identity.
|
||||
- Persist only after final validations.
|
||||
- Avoid persisting temporary tool results.
|
||||
- Record telemetry for reads, writes, updates and failures.
|
||||
- Define retention and deletion policies.
|
||||
- Use an absolute SQLite path in environments with multiple working directories.
|
||||
- Move to an enterprise database for production and high-availability environments.
|
||||
|
||||
### Reference implementation limitations
|
||||
|
||||
The current implementation uses rule-based extraction and SQLite as the reference provider.
|
||||
|
||||
Recommended future enhancements:
|
||||
|
||||
- LLM-based fact extraction;
|
||||
- vector-based semantic memory;
|
||||
- episodic memory;
|
||||
- expiration and versioning;
|
||||
- semantic deduplication;
|
||||
- consent policies;
|
||||
- query and deletion APIs;
|
||||
- Oracle Autonomous Database provider;
|
||||
- encryption and sensitive-data classification.
|
||||
@@ -0,0 +1,272 @@
|
||||
# Agent Platform OCI — Manual Oficial de Agent Gateway e MCP Gateway
|
||||
|
||||
## Objetivo
|
||||
|
||||
Este documento consolida:
|
||||
- Arquitetura oficial
|
||||
- Inventário dos componentes
|
||||
- Procedimento completo de execução local
|
||||
- MCP Gateway
|
||||
- Agent Gateway
|
||||
- Backend Runtime
|
||||
- Frontend
|
||||
- Testes E2E
|
||||
- Troubleshooting
|
||||
- Decisões arquiteturais
|
||||
|
||||
---
|
||||
|
||||
# Arquitetura Oficial
|
||||
|
||||
Frontend (5173)
|
||||
↓
|
||||
Agent Gateway (9000)
|
||||
↓
|
||||
Agent Template Backend / Runtime (8000)
|
||||
↓
|
||||
MCP Gateway (8300)
|
||||
↓
|
||||
Telecom MCP Server (8100)
|
||||
Retail MCP Server (8200)
|
||||
|
||||
---
|
||||
|
||||
# Portas Oficiais
|
||||
|
||||
| Componente | Porta |
|
||||
|------------|--------|
|
||||
| Frontend | 5173 |
|
||||
| Agent Gateway | 9000 |
|
||||
| Backend Runtime | 8000 |
|
||||
| MCP Gateway | 8300 |
|
||||
| Telecom MCP Server | 8100 |
|
||||
| Retail MCP Server | 8200 |
|
||||
|
||||
---
|
||||
|
||||
# Variáveis Oficiais
|
||||
|
||||
## Agent Template Backend
|
||||
|
||||
ENABLE_MCP_TOOLS=true
|
||||
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
|
||||
## Agent Gateway
|
||||
|
||||
DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
|
||||
AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
|
||||
|
||||
## MCP Gateway
|
||||
|
||||
MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
|
||||
---
|
||||
|
||||
# Ordem de Inicialização
|
||||
|
||||
1. Telecom MCP Server
|
||||
2. Retail MCP Server
|
||||
3. MCP Gateway
|
||||
4. Agent Template Backend
|
||||
5. Agent Gateway
|
||||
6. Frontend
|
||||
|
||||
---
|
||||
|
||||
# Terminal 1 — Telecom MCP Server
|
||||
|
||||
cd mcp/servers/telecom_mcp_server
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
python -m uvicorn main:app --host 0.0.0.0 --port 8100 --reload
|
||||
|
||||
Validação:
|
||||
|
||||
curl http://localhost:8100/health
|
||||
|
||||
---
|
||||
|
||||
# Terminal 2 — Retail MCP Server
|
||||
|
||||
cd mcp/servers/retail_mcp_server
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
python -m uvicorn main:app --host 0.0.0.0 --port 8200 --reload
|
||||
|
||||
Validação:
|
||||
|
||||
curl http://localhost:8200/health
|
||||
|
||||
---
|
||||
|
||||
# Terminal 3 — MCP Gateway
|
||||
|
||||
cd apps/mcp_gateway
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
export MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload
|
||||
|
||||
Validações:
|
||||
|
||||
curl http://localhost:8300/health
|
||||
curl http://localhost:8300/ready
|
||||
curl http://localhost:8300/v1/tools
|
||||
|
||||
Teste:
|
||||
|
||||
curl -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke
|
||||
|
||||
---
|
||||
|
||||
# Terminal 4 — Agent Template Backend
|
||||
|
||||
cd templates/agent_template_backend
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
|
||||
Validações:
|
||||
|
||||
curl http://localhost:8000/health
|
||||
curl http://localhost:8000/agents
|
||||
|
||||
---
|
||||
|
||||
# Terminal 5 — Agent Gateway
|
||||
|
||||
cd apps/agent_gateway
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
export DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
|
||||
export AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
|
||||
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 9000 --reload
|
||||
|
||||
Validações:
|
||||
|
||||
curl http://localhost:9000/health
|
||||
|
||||
Teste:
|
||||
|
||||
curl -X POST http://localhost:9000/gateway/message
|
||||
|
||||
---
|
||||
|
||||
# Terminal 6 — Frontend
|
||||
|
||||
cd agent_frontend
|
||||
|
||||
npm install
|
||||
|
||||
npm run dev -- --host 0.0.0.0 --port 5173
|
||||
|
||||
Abrir:
|
||||
|
||||
http://localhost:5173
|
||||
|
||||
Backend URL:
|
||||
|
||||
http://localhost:9000
|
||||
|
||||
---
|
||||
|
||||
# Fluxo de Tools
|
||||
|
||||
Agent
|
||||
↓
|
||||
MCPToolRouter
|
||||
↓
|
||||
MCPGatewayClient
|
||||
↓
|
||||
MCP Gateway
|
||||
↓
|
||||
MCP Server
|
||||
|
||||
---
|
||||
|
||||
# Teste Integrado E2E
|
||||
|
||||
Frontend
|
||||
↓
|
||||
Agent Gateway
|
||||
↓
|
||||
Backend Runtime
|
||||
↓
|
||||
MCP Gateway
|
||||
↓
|
||||
Telecom MCP Server
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
- Agent Gateway recebe requisição
|
||||
- Runtime executa LangGraph
|
||||
- MCP Gateway resolve tool
|
||||
- MCP Server responde
|
||||
- Usuário recebe resposta
|
||||
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Backend chamando MCP Server direto
|
||||
|
||||
Confirmar:
|
||||
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
|
||||
## Porta incorreta
|
||||
|
||||
A porta oficial do MCP Gateway é:
|
||||
|
||||
8300
|
||||
|
||||
## Agent Gateway não encontra Backend
|
||||
|
||||
Validar:
|
||||
|
||||
curl http://localhost:8000/health
|
||||
|
||||
## MCP Gateway não encontra MCP Server
|
||||
|
||||
Validar:
|
||||
|
||||
curl http://localhost:8100/health
|
||||
curl http://localhost:8200/health
|
||||
|
||||
---
|
||||
|
||||
# Decisões Arquiteturais Oficiais
|
||||
|
||||
- Agent Gateway centraliza governança
|
||||
- Runtime executa LangGraph
|
||||
- Runtime executa LLM
|
||||
- MCP Gateway centraliza tools
|
||||
- MCP Servers executam tools
|
||||
- Backend usa MCP Gateway
|
||||
- gateway_runtime.env.example foi removido
|
||||
- MCP_GATEWAY_* fica no .env do backend
|
||||
- Porta oficial MCP Gateway = 8300
|
||||
@@ -0,0 +1,627 @@
|
||||
# Manual de Execução Local
|
||||
## Agent Gateway + MCP Gateway + Agent Template Backend + Frontend
|
||||
|
||||
## 1. Arquitetura de execução
|
||||
|
||||
A arquitetura local fica assim:
|
||||
|
||||
```text
|
||||
Frontend
|
||||
porta 5173
|
||||
│
|
||||
▼
|
||||
Agent Gateway
|
||||
porta 9000
|
||||
│
|
||||
▼
|
||||
Agent Template Backend / Agent Runtime
|
||||
porta 8000
|
||||
│
|
||||
▼
|
||||
MCP Gateway
|
||||
porta 8300
|
||||
│
|
||||
▼
|
||||
MCP Server / Mock Telecom MCP
|
||||
porta 8001
|
||||
```
|
||||
|
||||
A governança de modelo, rate limit, auditoria e políticas ficam no **Agent Gateway**.
|
||||
|
||||
O **Agent Runtime / Agent Template Backend** continua responsável por:
|
||||
|
||||
- LangGraph;
|
||||
- estado;
|
||||
- memória;
|
||||
- checkpoints;
|
||||
- supervisor/router;
|
||||
- guardrails;
|
||||
- judges;
|
||||
- chamada LLM via providers existentes;
|
||||
- chamada de tools via MCP Gateway.
|
||||
|
||||
---
|
||||
|
||||
## 2. Portas
|
||||
|
||||
| Componente | Porta | URL |
|
||||
|---|---:|---|
|
||||
| Frontend | 5173 | `http://localhost:5173` |
|
||||
| Agent Gateway | 9000 | `http://localhost:9000` |
|
||||
| Agent Template Backend | 8000 | `http://localhost:8000` |
|
||||
| MCP Gateway | 8300 | `http://localhost:8300` |
|
||||
| MCP Server / Mock Telecom MCP | 8001 | `http://localhost:8001` |
|
||||
|
||||
---
|
||||
|
||||
## 3. Ordem recomendada para subir
|
||||
|
||||
Subir nesta ordem:
|
||||
|
||||
1. MCP Server / Mock Telecom MCP
|
||||
2. MCP Gateway
|
||||
3. Agent Template Backend
|
||||
4. Agent Gateway
|
||||
5. Frontend
|
||||
|
||||
---
|
||||
|
||||
# 4. Terminal 1 — MCP Server / Mock Telecom MCP
|
||||
|
||||
Se estiver usando o mock incluído no overlay:
|
||||
|
||||
```bash
|
||||
cd agent_platform_oci/mcp/servers/mock_telecom_mcp
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
uvicorn app:app --host 0.0.0.0 --port 8001 --reload
|
||||
```
|
||||
|
||||
Validar:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8001/health
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"service": "mock_telecom_mcp"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 5. Terminal 2 — MCP Gateway
|
||||
|
||||
```bash
|
||||
cd agent_platform_oci/apps/mcp_gateway
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
export MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload
|
||||
```
|
||||
|
||||
Validar health:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8300/health
|
||||
```
|
||||
|
||||
Validar readiness:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8300/ready
|
||||
```
|
||||
|
||||
Listar tools:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8300/v1/tools | jq
|
||||
```
|
||||
|
||||
Executar tool:
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"channel": "web",
|
||||
"tool_name": "consultar_fatura",
|
||||
"business_context": {
|
||||
"customer_key": "11999999999",
|
||||
"contract_key": "INV-001",
|
||||
"session_key": "session-001"
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "consultar_fatura",
|
||||
"version": "1.0.0",
|
||||
"ok": true,
|
||||
"data": {
|
||||
"invoice_id": "INV-001",
|
||||
"msisdn": "11999999999",
|
||||
"valor_total": 249.9,
|
||||
"vencimento": "2026-06-10",
|
||||
"status": "ABERTA"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 6. Terminal 3 — Agent Template Backend / Agent Runtime
|
||||
|
||||
```bash
|
||||
cd agent_platform_oci/templates/agent_template_backend
|
||||
```
|
||||
|
||||
ou, se o seu backend estiver em outra pasta:
|
||||
|
||||
```bash
|
||||
cd agent_platform_oci/templates/agent_template_backend
|
||||
```
|
||||
|
||||
Ativar ambiente:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Se ainda não existir `.venv`:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Configurar variáveis:
|
||||
|
||||
```bash
|
||||
export MCP_GATEWAY_ENABLED=true
|
||||
export MCP_GATEWAY_URL=http://localhost:8300
|
||||
export MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
|
||||
export AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
|
||||
```
|
||||
|
||||
Se estiver usando OCI/OpenAI-compatible, manter também as variáveis já existentes do backend:
|
||||
|
||||
```bash
|
||||
export LLM_PROVIDER=oci_openai
|
||||
export OCI_GENAI_API_KEY=<sua-chave>
|
||||
```
|
||||
|
||||
ou, para mock:
|
||||
|
||||
```bash
|
||||
export LLM_PROVIDER=mock
|
||||
```
|
||||
|
||||
Subir backend:
|
||||
|
||||
```bash
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
```
|
||||
|
||||
Validar:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
```
|
||||
|
||||
Validar agentes:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/agents | jq
|
||||
```
|
||||
|
||||
Testar backend direto:
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:8000/gateway/message \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"payload": {
|
||||
"message": "Quero consultar minha fatura",
|
||||
"session_id": "session-001",
|
||||
"user_id": "user-001",
|
||||
"message_id": "msg-001",
|
||||
"business_context": {
|
||||
"customer_key": "11999999999",
|
||||
"contract_key": "INV-001",
|
||||
"session_key": "session-001"
|
||||
}
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 7. Terminal 4 — Agent Gateway
|
||||
|
||||
```bash
|
||||
cd agent_platform_oci/apps/agent_gateway
|
||||
```
|
||||
|
||||
Ativar ambiente:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Configurar variáveis:
|
||||
|
||||
```bash
|
||||
export DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
|
||||
export AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
|
||||
```
|
||||
|
||||
Subir Agent Gateway:
|
||||
|
||||
```bash
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 9000 --reload
|
||||
```
|
||||
|
||||
Validar:
|
||||
|
||||
```bash
|
||||
curl http://localhost:9000/health
|
||||
```
|
||||
|
||||
Se a rota governada de exemplo estiver registrada no `app.main`, testar:
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:9000/gateway/message/governed \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"payload": {
|
||||
"message": "Quero consultar minha fatura",
|
||||
"session_id": "session-001",
|
||||
"user_id": "user-001",
|
||||
"message_id": "msg-001",
|
||||
"metadata": {
|
||||
"operation": "agent.final_answer"
|
||||
},
|
||||
"business_context": {
|
||||
"customer_key": "11999999999",
|
||||
"contract_key": "INV-001",
|
||||
"session_key": "session-001"
|
||||
}
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
Se a rota real for `/gateway/message`, testar:
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:9000/gateway/message \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"payload": {
|
||||
"message": "Quero consultar minha fatura",
|
||||
"session_id": "session-001",
|
||||
"user_id": "user-001",
|
||||
"message_id": "msg-001",
|
||||
"metadata": {
|
||||
"operation": "agent.final_answer"
|
||||
},
|
||||
"business_context": {
|
||||
"customer_key": "11999999999",
|
||||
"contract_key": "INV-001",
|
||||
"session_key": "session-001"
|
||||
}
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 8. Terminal 5 — Frontend
|
||||
|
||||
```bash
|
||||
cd agent_platform_oci/agent_frontend
|
||||
```
|
||||
|
||||
ou a pasta onde estiver o frontend.
|
||||
|
||||
Instalar dependências:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Subir:
|
||||
|
||||
```bash
|
||||
npm run dev -- --host 0.0.0.0 --port 5173
|
||||
```
|
||||
|
||||
Abrir:
|
||||
|
||||
```text
|
||||
http://localhost:5173
|
||||
```
|
||||
|
||||
Configurar no frontend:
|
||||
|
||||
```text
|
||||
Backend URL: http://localhost:9000
|
||||
Agent: telecom_contas
|
||||
Session ID: session-001
|
||||
Customer Key: 11999999999
|
||||
Contract Key: INV-001
|
||||
```
|
||||
|
||||
O frontend deve chamar o **Agent Gateway** na porta 9000, não o MCP Gateway.
|
||||
|
||||
---
|
||||
|
||||
# 9. Fluxo final esperado
|
||||
|
||||
```text
|
||||
Frontend 5173
|
||||
↓
|
||||
Agent Gateway 9000
|
||||
↓
|
||||
Agent Template Backend 8000
|
||||
↓
|
||||
MCP Gateway 8300
|
||||
↓
|
||||
Mock Telecom MCP 8001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 10. Docker Compose para MCP Gateway + Mock MCP
|
||||
|
||||
Também é possível subir MCP Gateway + Mock MCP com Docker Compose:
|
||||
|
||||
```bash
|
||||
cd agent_platform_oci
|
||||
|
||||
docker compose -f deploy/docker/docker-compose.mcp-gateway.yml up --build
|
||||
```
|
||||
|
||||
Isso sobe:
|
||||
|
||||
```text
|
||||
MCP Gateway http://localhost:8300
|
||||
Mock Telecom MCP http://localhost:8001
|
||||
```
|
||||
|
||||
Depois subir manualmente:
|
||||
|
||||
- Agent Template Backend na porta 8000;
|
||||
- Agent Gateway na porta 9000;
|
||||
- Frontend na porta 5173.
|
||||
|
||||
---
|
||||
|
||||
# 11. Checklist de validação
|
||||
|
||||
## MCP Server
|
||||
|
||||
```bash
|
||||
curl http://localhost:8001/health
|
||||
```
|
||||
|
||||
## MCP Gateway
|
||||
|
||||
```bash
|
||||
curl http://localhost:8300/health
|
||||
curl http://localhost:8300/v1/tools
|
||||
```
|
||||
|
||||
## Backend Runtime
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
curl http://localhost:8000/agents
|
||||
```
|
||||
|
||||
## Agent Gateway
|
||||
|
||||
```bash
|
||||
curl http://localhost:9000/health
|
||||
```
|
||||
|
||||
## Frontend
|
||||
|
||||
```text
|
||||
http://localhost:5173
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 12. Erros comuns
|
||||
|
||||
## 12.1. Frontend chamando porta errada
|
||||
|
||||
Errado:
|
||||
|
||||
```text
|
||||
Frontend → http://localhost:8000
|
||||
```
|
||||
|
||||
Correto:
|
||||
|
||||
```text
|
||||
Frontend → http://localhost:9000
|
||||
```
|
||||
|
||||
Se você quiser testar sem Agent Gateway, pode apontar temporariamente para 8000. Mas no modelo final, o frontend deve usar o Agent Gateway.
|
||||
|
||||
---
|
||||
|
||||
## 12.2. MCP Gateway sem MCP Server
|
||||
|
||||
Sintoma:
|
||||
|
||||
```text
|
||||
MCP server unavailable
|
||||
```
|
||||
|
||||
Correção:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8001/health
|
||||
```
|
||||
|
||||
Se falhar, subir o mock MCP server.
|
||||
|
||||
---
|
||||
|
||||
## 12.3. Tool sem BusinessContext
|
||||
|
||||
Sintoma:
|
||||
|
||||
```json
|
||||
{
|
||||
"missing_business_keys": ["customer_key", "contract_key"]
|
||||
}
|
||||
```
|
||||
|
||||
Correção:
|
||||
|
||||
enviar:
|
||||
|
||||
```json
|
||||
"business_context": {
|
||||
"customer_key": "11999999999",
|
||||
"contract_key": "INV-001",
|
||||
"session_key": "session-001"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12.4. Agent Gateway não encontra backend
|
||||
|
||||
Sintoma:
|
||||
|
||||
```text
|
||||
Connection refused http://localhost:8000
|
||||
```
|
||||
|
||||
Correção:
|
||||
|
||||
validar:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
```
|
||||
|
||||
e configurar:
|
||||
|
||||
```bash
|
||||
export DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12.5. Rota governada não registrada
|
||||
|
||||
Se `/gateway/message/governed` retornar 404, significa que o arquivo de exemplo ainda não foi incluído no `app.main`.
|
||||
|
||||
Nesse caso, use a rota real `/gateway/message` ou registre no `main.py`:
|
||||
|
||||
```python
|
||||
from app.routes.governed_proxy_example import router as governed_router
|
||||
|
||||
app.include_router(governed_router)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 13. Variáveis consolidadas
|
||||
|
||||
## Agent Gateway
|
||||
|
||||
```env
|
||||
DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
|
||||
AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
|
||||
```
|
||||
|
||||
## Agent Template Backend
|
||||
|
||||
```env
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
LLM_PROVIDER=mock
|
||||
```
|
||||
|
||||
## MCP Gateway
|
||||
|
||||
```env
|
||||
MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 14. Resumo rápido
|
||||
|
||||
Em cinco terminais:
|
||||
|
||||
```bash
|
||||
# Terminal 1
|
||||
cd mcp/servers/mock_telecom_mcp
|
||||
source .venv/bin/activate
|
||||
uvicorn app:app --host 0.0.0.0 --port 8001 --reload
|
||||
|
||||
# Terminal 2
|
||||
cd apps/mcp_gateway
|
||||
source .venv/bin/activate
|
||||
export MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload
|
||||
|
||||
# Terminal 3
|
||||
cd templates/agent_template_backend
|
||||
source .venv/bin/activate
|
||||
export MCP_GATEWAY_ENABLED=true
|
||||
export MCP_GATEWAY_URL=http://localhost:8300
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
|
||||
# Terminal 4
|
||||
cd apps/agent_gateway
|
||||
source .venv/bin/activate
|
||||
export DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
|
||||
export AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 9000 --reload
|
||||
|
||||
# Terminal 5
|
||||
cd agent_frontend
|
||||
npm install
|
||||
npm run dev -- --host 0.0.0.0 --port 5173
|
||||
```
|
||||
93
agent_framework_oci/Documentacao/MCP_GATEWAY_RUNBOOK.md
Normal file
93
agent_framework_oci/Documentacao/MCP_GATEWAY_RUNBOOK.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# MCP Gateway Runbook
|
||||
|
||||
## Arquitetura corrigida
|
||||
|
||||
O backend/agente não deve chamar diretamente os MCP servers finais. O fluxo correto é:
|
||||
|
||||
```text
|
||||
agent_template_backend / agent_framework
|
||||
-> MCP Gateway Client
|
||||
-> apps/mcp_gateway
|
||||
-> mcp/servers/telecom_mcp_server ou mcp/servers/retail_mcp_server
|
||||
```
|
||||
|
||||
## Subir localmente
|
||||
|
||||
A partir da raiz do projeto:
|
||||
|
||||
### Terminal 1 - Telecom MCP Server
|
||||
|
||||
```bash
|
||||
cd mcp/servers/telecom_mcp_server
|
||||
python -m uvicorn main:app --host 0.0.0.0 --port 8100 --reload
|
||||
```
|
||||
|
||||
### Terminal 2 - Retail MCP Server
|
||||
|
||||
```bash
|
||||
cd mcp/servers/retail_mcp_server
|
||||
python -m uvicorn main:app --host 0.0.0.0 --port 8200 --reload
|
||||
```
|
||||
|
||||
### Terminal 3 - MCP Gateway
|
||||
|
||||
```bash
|
||||
cd apps/mcp_gateway
|
||||
export MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload
|
||||
```
|
||||
|
||||
### Terminal 4 - Backend/agente
|
||||
|
||||
No `.env` do backend/agente ou do runtime que usa o `agent_framework`, habilite:
|
||||
|
||||
```env
|
||||
ENABLE_MCP_TOOLS=true
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
```
|
||||
|
||||
## Testes rápidos
|
||||
|
||||
### Health do gateway
|
||||
|
||||
```bash
|
||||
curl http://localhost:8300/health
|
||||
```
|
||||
|
||||
### Lista de tools expostas pelo gateway
|
||||
|
||||
```bash
|
||||
curl http://localhost:8300/v1/tools
|
||||
```
|
||||
|
||||
### Chamada de tool via gateway
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"channel": "web",
|
||||
"tool_name": "consultar_fatura",
|
||||
"arguments": {
|
||||
"msisdn": "11999999999",
|
||||
"invoice_id": "INV-123"
|
||||
},
|
||||
"business_context": {},
|
||||
"metadata": {"session_id": "local-test"}
|
||||
}'
|
||||
```
|
||||
|
||||
Resposta esperada: `ok: true`, `data.invoice_id`, `data.msisdn`, `metadata.server: telecom`.
|
||||
|
||||
## O que foi corrigido
|
||||
|
||||
- `apps/mcp_gateway/config/mcp_gateway.yaml` agora aponta para os MCP servers reais nas portas `8100` e `8200`.
|
||||
- O MCP Gateway agora suporta o contrato legado dos MCP servers: `POST /mcp/tools/call` com `{tool_name, arguments}`.
|
||||
- O `agent_framework` ganhou flags `MCP_GATEWAY_ENABLED`, `MCP_GATEWAY_URL`, `MCP_GATEWAY_TOKEN`, `MCP_GATEWAY_AGENT_ID` e `MCP_GATEWAY_TENANT_ID`.
|
||||
- O `MCPToolRouter` passa a chamar o MCP Gateway quando `MCP_GATEWAY_ENABLED=true`.
|
||||
- `libs/agent_framework/config/mcp_servers.yaml` foi mantido como registry lógico/fallback, não como caminho principal quando o gateway está ativo.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
520
agent_framework_oci/Documentacao/Manual_Long_Term_Memory_PT.md
Normal file
520
agent_framework_oci/Documentacao/Manual_Long_Term_Memory_PT.md
Normal file
@@ -0,0 +1,520 @@
|
||||
### Manual de Implementação — Long-Term Memory
|
||||
|
||||
### Conceito
|
||||
|
||||
A Long-Term Memory (LTM) é a capacidade do `agent_framework` de armazenar e recuperar fatos duradouros além da duração de uma sessão de conversa.
|
||||
|
||||
Diferentemente do histórico de mensagens, que normalmente está associado a um `session_id`, a memória de longo prazo é associada à identidade de negócio do usuário ou cliente. Na implementação atual, essa identidade é composta por:
|
||||
|
||||
```text
|
||||
tenant_id
|
||||
agent_id
|
||||
customer_key
|
||||
```
|
||||
|
||||
Isso permite que um agente recupere preferências, informações de identidade, projetos e restrições mesmo quando uma nova sessão é criada.
|
||||
|
||||
### Para que serve
|
||||
|
||||
A Long-Term Memory serve para:
|
||||
|
||||
- manter continuidade entre sessões;
|
||||
- personalizar respostas;
|
||||
- evitar que o usuário repita informações já fornecidas;
|
||||
- reduzir a necessidade de enviar todo o histórico ao modelo;
|
||||
- armazenar preferências, projetos atuais, nomes preferidos e restrições;
|
||||
- isolar a memória entre tenants, agentes e clientes.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```text
|
||||
Sessão A:
|
||||
"Me chame de Cris. Minha linguagem preferida é Python."
|
||||
|
||||
Sessão B, com outro session_id e o mesmo customer_key:
|
||||
"O que você lembra sobre mim?"
|
||||
|
||||
Resposta esperada:
|
||||
"Seu nome preferido é Cris e sua linguagem preferida é Python."
|
||||
```
|
||||
|
||||
### Diferença entre os tipos de memória
|
||||
|
||||
#### Conversation Memory
|
||||
|
||||
Mantém as mensagens da conversa atual e normalmente está associada ao `session_id`.
|
||||
|
||||
#### Summary Memory
|
||||
|
||||
Mantém um resumo da conversa para reduzir o tamanho do contexto enviado ao modelo.
|
||||
|
||||
#### Long-Term Memory
|
||||
|
||||
Mantém fatos duradouros entre sessões e é associada à identidade de negócio, principalmente ao `customer_key`.
|
||||
|
||||
### Componentes da funcionalidade
|
||||
|
||||
#### LongTermMemoryManager
|
||||
|
||||
Responsável por coordenar:
|
||||
|
||||
- carregamento das memórias;
|
||||
- recuperação por identidade;
|
||||
- renderização do contexto;
|
||||
- extração de novos fatos;
|
||||
- persistência dos fatos;
|
||||
- deduplicação e atualização.
|
||||
|
||||
#### LongTermMemoryStore
|
||||
|
||||
Interface de persistência utilizada pelo manager.
|
||||
|
||||
#### SQLiteLongTermMemoryStore
|
||||
|
||||
Implementação de referência baseada em SQLite.
|
||||
|
||||
É apropriada para:
|
||||
|
||||
- desenvolvimento local;
|
||||
- testes;
|
||||
- demonstrações;
|
||||
- ambientes de baixa escala.
|
||||
|
||||
#### InMemoryLongTermMemoryStore
|
||||
|
||||
Implementação em memória utilizada para testes rápidos.
|
||||
|
||||
O conteúdo é perdido quando o processo do backend é encerrado.
|
||||
|
||||
#### LongTermMemoryExtractor
|
||||
|
||||
Responsável por identificar fatos duradouros nas mensagens.
|
||||
|
||||
Exemplos de fatos:
|
||||
|
||||
```text
|
||||
preferred_name = Cris
|
||||
preferred_language = Python
|
||||
current_project = Atlas
|
||||
```
|
||||
|
||||
#### LongTermMemoryItem
|
||||
|
||||
Modelo que representa um item persistido, incluindo identidade, chave, valor, categoria, confiança e metadados.
|
||||
|
||||
#### AgentRuntime
|
||||
|
||||
Carrega a memória antes da execução do agente e injeta o contexto no prompt.
|
||||
|
||||
#### Nó persist_long_term_memory
|
||||
|
||||
Nó do LangGraph responsável por persistir os fatos após a geração e validação da resposta final.
|
||||
|
||||
### Estrutura dos arquivos
|
||||
|
||||
```text
|
||||
libs/
|
||||
└── agent_framework/
|
||||
└── src/
|
||||
└── agent_framework/
|
||||
└── memory/
|
||||
├── __init__.py
|
||||
├── long_term_extractor.py
|
||||
├── long_term_memory.py
|
||||
├── long_term_models.py
|
||||
└── long_term_store.py
|
||||
```
|
||||
|
||||
### Fluxo de execução
|
||||
|
||||
```text
|
||||
Mensagem do usuário
|
||||
│
|
||||
▼
|
||||
AgentRuntime.prepare_memory_context()
|
||||
│
|
||||
├── Conversation Memory
|
||||
├── Summary Memory
|
||||
└── Long-Term Memory
|
||||
│
|
||||
▼
|
||||
long_term_memory_context
|
||||
│
|
||||
▼
|
||||
Prompt do agente
|
||||
│
|
||||
▼
|
||||
Agente
|
||||
│
|
||||
▼
|
||||
Guardrails / Judges / Supervisor
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
LongTermMemoryExtractor
|
||||
│
|
||||
▼
|
||||
LongTermMemoryStore
|
||||
```
|
||||
|
||||
### Configuração do framework
|
||||
|
||||
### Novos módulos
|
||||
|
||||
Copie os arquivos:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_extractor.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_memory.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_models.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_store.py
|
||||
```
|
||||
|
||||
### Atualização de memory/__init__.py
|
||||
|
||||
Exporte os componentes da Long-Term Memory:
|
||||
|
||||
```python
|
||||
from agent_framework.memory.long_term_memory import (
|
||||
LongTermMemoryManager,
|
||||
create_long_term_memory_manager,
|
||||
)
|
||||
from agent_framework.memory.long_term_models import LongTermMemoryItem
|
||||
from agent_framework.memory.long_term_store import (
|
||||
InMemoryLongTermMemoryStore,
|
||||
LongTermMemoryStore,
|
||||
SQLiteLongTermMemoryStore,
|
||||
create_long_term_memory_store,
|
||||
)
|
||||
```
|
||||
|
||||
### Atualização de settings.py
|
||||
|
||||
Adicione as configurações:
|
||||
|
||||
```python
|
||||
ENABLE_LONG_TERM_MEMORY: bool = False
|
||||
LONG_TERM_MEMORY_PROVIDER: str = "sqlite"
|
||||
LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db"
|
||||
LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory"
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True
|
||||
```
|
||||
|
||||
### Integração com AgentRuntime
|
||||
|
||||
O runtime deve:
|
||||
|
||||
1. verificar se a funcionalidade está habilitada;
|
||||
2. criar o manager quando necessário;
|
||||
3. recuperar os fatos pela identidade;
|
||||
4. preencher o estado;
|
||||
5. injetar o contexto no prompt.
|
||||
|
||||
Campos adicionados ao estado:
|
||||
|
||||
```python
|
||||
long_term_memories: list[dict]
|
||||
long_term_memory_context: str
|
||||
long_term_memory_write_result: dict
|
||||
```
|
||||
|
||||
### Inicialização no AgentWorkflow
|
||||
|
||||
O manager deve ser criado no `AgentWorkflow`:
|
||||
|
||||
```python
|
||||
self.long_term_memory_manager = create_long_term_memory_manager(
|
||||
settings,
|
||||
telemetry=telemetry,
|
||||
)
|
||||
```
|
||||
|
||||
### Inicialização correta dos agentes
|
||||
|
||||
O `long_term_memory_manager` não deve ser passado pelo `agent_kwargs` caso os construtores de `BillingAgent`, `ProductAgent`, `OrdersAgent` e `SupportAgent` não declarem esse parâmetro.
|
||||
|
||||
Esta inicialização causa erro:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"settings": settings,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
"long_term_memory_manager": self.long_term_memory_manager,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
```
|
||||
|
||||
Erro resultante:
|
||||
|
||||
```text
|
||||
TypeError: BillingAgent.__init__() got an unexpected keyword argument
|
||||
'long_term_memory_manager'
|
||||
```
|
||||
|
||||
A forma recomendada é criar os agentes com a assinatura já existente e injetar o manager como atributo após a inicialização:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"tool_router": getattr(self, "tool_router", None),
|
||||
"rag_service": self.rag_service,
|
||||
"cache": self.cache,
|
||||
"settings": settings,
|
||||
"observer": self.observer,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
self.product = ProductAgent(llm, **agent_kwargs)
|
||||
self.orders = OrdersAgent(llm, **agent_kwargs)
|
||||
self.support = SupportAgent(llm, **agent_kwargs)
|
||||
|
||||
for agent in (
|
||||
self.billing,
|
||||
self.product,
|
||||
self.orders,
|
||||
self.support,
|
||||
):
|
||||
agent.long_term_memory_manager = self.long_term_memory_manager
|
||||
```
|
||||
|
||||
Essa abordagem evita alterar os construtores de todos os agentes e mantém a funcionalidade encapsulada no framework.
|
||||
|
||||
### Configuração do LangGraph
|
||||
|
||||
Registre o nó:
|
||||
|
||||
```python
|
||||
builder.add_node(
|
||||
"persist_long_term_memory",
|
||||
self._node(
|
||||
"persist_long_term_memory",
|
||||
self.persist_long_term_memory,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Altere o fluxo:
|
||||
|
||||
```python
|
||||
builder.add_edge(
|
||||
"supervisor_review",
|
||||
"persist_long_term_memory",
|
||||
)
|
||||
builder.add_edge(
|
||||
"persist_long_term_memory",
|
||||
"persist",
|
||||
)
|
||||
```
|
||||
|
||||
Implemente o método:
|
||||
|
||||
```python
|
||||
async def persist_long_term_memory(
|
||||
self,
|
||||
state: AgentState,
|
||||
) -> dict[str, object]:
|
||||
result = await self.long_term_memory_manager.persist_turn(state)
|
||||
|
||||
return {
|
||||
"long_term_memory_write_result": result,
|
||||
}
|
||||
```
|
||||
|
||||
Fluxo final:
|
||||
|
||||
```text
|
||||
supervisor_review
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
persist
|
||||
```
|
||||
|
||||
### Variáveis de ambiente
|
||||
|
||||
```env
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Caminho do banco SQLite
|
||||
|
||||
O caminho relativo é resolvido a partir do diretório em que o backend é iniciado.
|
||||
|
||||
Para evitar que bancos diferentes sejam criados acidentalmente, prefira um caminho absoluto em ambientes de desenvolvimento:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db
|
||||
```
|
||||
|
||||
Crie a pasta antes de iniciar:
|
||||
|
||||
```bash
|
||||
mkdir -p data
|
||||
```
|
||||
|
||||
### Como testar
|
||||
|
||||
### Teste 1 — Gravação
|
||||
|
||||
Envie:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-a",
|
||||
"customer_key": "11999999999",
|
||||
"message": "Me chame de Cris. Minha linguagem preferida é Python e meu projeto atual se chama Atlas."
|
||||
}
|
||||
```
|
||||
|
||||
### Teste 2 — Recuperação em outra sessão
|
||||
|
||||
Utilize outro `session_id`, mantendo o mesmo `customer_key`:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-b",
|
||||
"customer_key": "11999999999",
|
||||
"message": "O que você lembra sobre mim, minhas preferências e meu projeto?"
|
||||
}
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```text
|
||||
Seu nome preferido é Cris.
|
||||
Sua linguagem preferida é Python.
|
||||
Seu projeto atual se chama Atlas.
|
||||
```
|
||||
|
||||
### Teste 3 — Isolamento
|
||||
|
||||
Utilize outro cliente:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-c",
|
||||
"customer_key": "outro-cliente",
|
||||
"message": "Qual é meu nome preferido e qual é meu projeto atual?"
|
||||
}
|
||||
```
|
||||
|
||||
Os dados de `11999999999` não devem aparecer.
|
||||
|
||||
### Teste 4 — Reinicialização do frontend
|
||||
|
||||
Reinicie ou resete o frontend e confirme que ele continua enviando o mesmo `customer_key`.
|
||||
|
||||
A memória deve sobreviver à troca do `session_id`. O reset do frontend não apaga o SQLite.
|
||||
|
||||
### Teste 5 — Reinicialização do backend
|
||||
|
||||
Reinicie o Uvicorn e repita a consulta.
|
||||
|
||||
Com:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
a memória deve continuar disponível.
|
||||
|
||||
Com:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
a memória será perdida quando o processo for encerrado.
|
||||
|
||||
### Verificação direta no SQLite
|
||||
|
||||
Localize o banco:
|
||||
|
||||
```bash
|
||||
find . -name "agent_framework.db" -type f
|
||||
```
|
||||
|
||||
Abra:
|
||||
|
||||
```bash
|
||||
sqlite3 ./data/agent_framework.db
|
||||
```
|
||||
|
||||
Consulte:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
tenant_id,
|
||||
agent_id,
|
||||
customer_key,
|
||||
memory_type,
|
||||
memory_key,
|
||||
memory_value,
|
||||
confidence,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM agentfw_long_term_memory
|
||||
ORDER BY updated_at DESC;
|
||||
```
|
||||
|
||||
### Critérios de sucesso
|
||||
|
||||
A implementação está funcionando quando:
|
||||
|
||||
- a memória é recuperada com outro `session_id`;
|
||||
- o mesmo `customer_key` recupera os fatos anteriores;
|
||||
- outro `customer_key` não acessa esses fatos;
|
||||
- reiniciar o frontend não apaga a memória;
|
||||
- reiniciar o backend não apaga a memória quando o provider é SQLite;
|
||||
- o nó `persist_long_term_memory` é executado;
|
||||
- o prompt recebe `long_term_memory_context`.
|
||||
|
||||
### Boas práticas
|
||||
|
||||
- Persistir somente fatos duradouros.
|
||||
- Não armazenar a conversa completa como Long-Term Memory.
|
||||
- Isolar dados por `tenant_id`, `agent_id` e `customer_key`.
|
||||
- Não utilizar `session_id` como identidade permanente do usuário.
|
||||
- Persistir somente depois das validações finais.
|
||||
- Evitar armazenar resultados temporários de ferramentas.
|
||||
- Registrar telemetria de leitura, escrita, atualização e falha.
|
||||
- Definir políticas de retenção e exclusão.
|
||||
- Usar caminho absoluto para SQLite em ambientes com múltiplos diretórios de execução.
|
||||
- Migrar para um banco corporativo em ambientes de produção e alta disponibilidade.
|
||||
|
||||
### Limitações da implementação de referência
|
||||
|
||||
A implementação atual utiliza extração baseada em regras e SQLite como provider de referência.
|
||||
|
||||
Evoluções recomendadas:
|
||||
|
||||
- extração de fatos com LLM;
|
||||
- memória semântica com vetores;
|
||||
- memória episódica;
|
||||
- expiração e versionamento;
|
||||
- deduplicação semântica;
|
||||
- política de consentimento;
|
||||
- API de consulta e exclusão;
|
||||
- provider Oracle Autonomous Database;
|
||||
- criptografia e classificação de dados sensíveis.
|
||||
@@ -0,0 +1,129 @@
|
||||
# Agent Platform OCI — Agent Gateway + MCP Gateway Evolution
|
||||
|
||||
Este overlay remove o conceito de `AI Gateway` separado.
|
||||
|
||||
## Arquitetura
|
||||
|
||||
```text
|
||||
Frontend
|
||||
↓
|
||||
Agent Gateway
|
||||
├── governance
|
||||
├── model policies
|
||||
├── rate limit
|
||||
├── audit
|
||||
└── evaluation hooks
|
||||
↓
|
||||
Agent Backend / Runtime
|
||||
├── LangGraph
|
||||
├── state
|
||||
├── memory
|
||||
├── checkpoints
|
||||
└── LLM providers via profiles existentes
|
||||
↓
|
||||
MCP Gateway
|
||||
↓
|
||||
MCP Servers
|
||||
```
|
||||
|
||||
## O que entra no Agent Gateway
|
||||
|
||||
```text
|
||||
apps/agent_gateway/app/governance/
|
||||
apps/agent_gateway/app/governance_middleware.py
|
||||
apps/agent_gateway/app/routes/governed_proxy_example.py
|
||||
apps/agent_gateway/config/gateway_governance.yaml
|
||||
```
|
||||
|
||||
## O que entra no MCP Gateway
|
||||
|
||||
```text
|
||||
apps/mcp_gateway/
|
||||
libs/agent_framework/src/agent_framework/gateways/mcp_gateway_client.py
|
||||
libs/agent_framework/src/agent_framework/runtime_mcp_gateway_adapter.py
|
||||
```
|
||||
|
||||
## Aplicar overlay
|
||||
|
||||
```bash
|
||||
unzip agent_platform_agent_gateway_mcp_gateway_overlay.zip -d /tmp/overlay
|
||||
rsync -av /tmp/overlay/ ./
|
||||
```
|
||||
|
||||
## Subir MCP Gateway local
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/docker/docker-compose.mcp-gateway.yml up --build
|
||||
```
|
||||
|
||||
Serviços:
|
||||
|
||||
```text
|
||||
MCP Gateway http://localhost:8300
|
||||
Mock Telecom MCP http://localhost:8001
|
||||
```
|
||||
|
||||
## Testar MCP Gateway
|
||||
|
||||
```bash
|
||||
curl http://localhost:8300/health
|
||||
curl http://localhost:8300/v1/tools
|
||||
```
|
||||
|
||||
Executar tool:
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"channel": "web",
|
||||
"tool_name": "consultar_fatura",
|
||||
"business_context": {
|
||||
"customer_key": "11999999999",
|
||||
"contract_key": "INV-001",
|
||||
"session_key": "session-001"
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
## Como plugar no Agent Gateway
|
||||
|
||||
No handler real do `POST /gateway/message`, antes de encaminhar ao backend/runtime:
|
||||
|
||||
```python
|
||||
governed_body, headers = governance.prepare_backend_request(body)
|
||||
```
|
||||
|
||||
Ao receber resposta do backend:
|
||||
|
||||
```python
|
||||
return governance.process_backend_response(data)
|
||||
```
|
||||
|
||||
O arquivo abaixo mostra um exemplo completo:
|
||||
|
||||
```text
|
||||
apps/agent_gateway/app/routes/governed_proxy_example.py
|
||||
```
|
||||
|
||||
## Variáveis do Runtime
|
||||
|
||||
```env
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
```
|
||||
|
||||
## Importante
|
||||
|
||||
Não existe `apps/ai_gateway`.
|
||||
|
||||
A governança de modelo fica no Agent Gateway como policy/metadados.
|
||||
|
||||
O Runtime continua usando os LLM providers existentes, podendo ler a política enviada pelo Gateway em:
|
||||
|
||||
```python
|
||||
state["metadata"]["model_policy"]
|
||||
```
|
||||
@@ -0,0 +1,68 @@
|
||||
# Checkpoint Enterprise no Agent Framework OCI
|
||||
|
||||
Esta versão adiciona quatro capacidades ao checkpointer do LangGraph usado pelo framework:
|
||||
|
||||
1. **Checkpoint Integrity**: cada checkpoint é salvo dentro de um envelope com `schema_version`, `checkpoint_id`, `payload_hash` SHA-256 e `created_at`. Na leitura, o hash é recalculado. Se o payload foi truncado, alterado ou corrompido, o checkpoint é ignorado no recovery.
|
||||
2. **Checkpoint Compaction**: checkpoints antigos são removidos automaticamente conforme a configuração `CHECKPOINT_COMPACT_EVERY` e `CHECKPOINT_KEEP_LAST`. Isso evita crescimento infinito da tabela `workflow_checkpoints`.
|
||||
3. **Resilient Checkpointer**: gravações e leituras usam retry com backoff e jitter. A camada resiliente funciona sobre memory, SQLite e Oracle/Autonomous Database.
|
||||
4. **Checkpoint Recovery**: ao recuperar o estado, o framework varre os últimos checkpoints e retorna o mais recente válido, pulando checkpoints corrompidos.
|
||||
|
||||
## Configuração
|
||||
|
||||
No `.env`:
|
||||
|
||||
```env
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
ENABLE_RESILIENT_CHECKPOINTER=true
|
||||
ENABLE_CHECKPOINT_INTEGRITY=true
|
||||
ENABLE_CHECKPOINT_COMPACTION=true
|
||||
CHECKPOINT_COMPACT_EVERY=50
|
||||
CHECKPOINT_KEEP_LAST=20
|
||||
CHECKPOINT_RECOVERY_SCAN_LIMIT=25
|
||||
CHECKPOINT_RETRY_MAX_ATTEMPTS=3
|
||||
CHECKPOINT_RETRY_BASE_DELAY_SECONDS=0.05
|
||||
CHECKPOINT_RETRY_MAX_DELAY_SECONDS=1.0
|
||||
CHECKPOINT_RETRY_JITTER_SECONDS=0.05
|
||||
```
|
||||
|
||||
Para produção com múltiplos pods, prefira:
|
||||
|
||||
```env
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
|
||||
ADB_USER=...
|
||||
ADB_PASSWORD=...
|
||||
ADB_DSN=...
|
||||
ADB_WALLET_LOCATION=...
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
```
|
||||
|
||||
## Uso no LangGraph
|
||||
|
||||
```python
|
||||
from agent_framework.checkpoints import create_langgraph_checkpointer
|
||||
|
||||
checkpointer = create_langgraph_checkpointer(settings)
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
config = {"configurable": {"thread_id": session_id}}
|
||||
result = graph.invoke(input_state, config=config)
|
||||
```
|
||||
|
||||
O `thread_id` continua sendo a chave de recuperação da conversa. Em ambiente com Load Balancer, qualquer pod consegue retomar a execução se usar o mesmo repositório persistente.
|
||||
|
||||
## Arquivos alterados
|
||||
|
||||
- `agent_framework/src/agent_framework/checkpoints/checkpoint_repository.py`
|
||||
- `agent_framework/src/agent_framework/checkpoints/langgraph_saver.py`
|
||||
- `agent_framework/src/agent_framework/checkpoints/__init__.py`
|
||||
- `agent_framework/src/agent_framework/config/settings.py`
|
||||
- `tests/unit/test_resilient_checkpointer.py`
|
||||
|
||||
## Observação importante
|
||||
|
||||
O provider `memory` agora também usa o `RepositoryCheckpointSaver` quando `ENABLE_RESILIENT_CHECKPOINTER=true`. Para voltar ao `MemorySaver` puro do LangGraph em testes locais, configure:
|
||||
|
||||
```env
|
||||
ENABLE_RESILIENT_CHECKPOINTER=false
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=memory
|
||||
```
|
||||
114
agent_framework_oci/Documentacao/README_ENTERPRISE_ROUTING.md
Normal file
114
agent_framework_oci/Documentacao/README_ENTERPRISE_ROUTING.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# AI Agent Platform — Enterprise Routing Edition
|
||||
|
||||
Esta versão inclui o projeto completo com:
|
||||
|
||||
- `agent_framework`: framework reutilizável.
|
||||
- `agent_template_backend`: backend FastAPI com LangGraph, OCI Generative AI, Langfuse, guardrails, judges, supervisor e roteamento enterprise.
|
||||
- `agent_frontend`: frontend web independente.
|
||||
- `templates/template_telecom_billing_product`: template de exemplo para telecom com agentes de Fatura e Produto.
|
||||
- `templates/template_retail_orders_support`: template de exemplo para e-commerce com agentes de Pedido e Suporte.
|
||||
|
||||
## Roteamento enterprise
|
||||
|
||||
O roteamento fica em:
|
||||
|
||||
```text
|
||||
agent_framework/src/agent_framework/routing/
|
||||
```
|
||||
|
||||
Componentes principais:
|
||||
|
||||
- `models.py`: modelos `IntentDefinition`, `RouterStatePolicy`, `RouteDecision`.
|
||||
- `config_loader.py`: carrega o YAML de intents e políticas.
|
||||
- `enterprise_router.py`: decide o agente de destino por estado, keyword, LLM ou fallback.
|
||||
|
||||
O template usa:
|
||||
|
||||
```text
|
||||
agent_template_backend/config/routing.yaml
|
||||
```
|
||||
|
||||
## Ordem de decisão
|
||||
|
||||
1. Estado conversacional (`state_policies`).
|
||||
2. Keywords/intents configuráveis.
|
||||
3. LLM Router opcional (`ENABLE_LLM_ROUTER=true`).
|
||||
4. Fallback (`router.fallback_agent`).
|
||||
|
||||
## Como testar roteamento sem chamar o agente final
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/debug/route \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"payload": {
|
||||
"text": "Minha fatura veio alta",
|
||||
"user_id": "u1",
|
||||
"channel_id": "browser-1",
|
||||
"context": {"msisdn": "5511999999999"}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Resposta esperada:
|
||||
|
||||
```json
|
||||
{
|
||||
"route": "billing_agent",
|
||||
"agent": "billing_agent",
|
||||
"intent": "billing_invoice_explanation",
|
||||
"method": "keyword"
|
||||
}
|
||||
```
|
||||
|
||||
## Como habilitar roteamento por LLM
|
||||
|
||||
No `.env` do backend:
|
||||
|
||||
```env
|
||||
LLM_PROVIDER=oci_openai
|
||||
OCI_GENAI_API_KEY=...
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com/openai/v1
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
ENABLE_LLM_ROUTER=true
|
||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
```
|
||||
|
||||
## Como adicionar novo agente
|
||||
|
||||
1. Criar classe do agente em `agent_template_backend/app/agents/`.
|
||||
2. Instanciar o agente em `AgentWorkflow.__init__`.
|
||||
3. Adicionar node no LangGraph.
|
||||
4. Adicionar a rota no `add_conditional_edges`.
|
||||
5. Criar intent no `config/routing.yaml` apontando `agent: nome_do_agente`.
|
||||
|
||||
## Templates incluídos
|
||||
|
||||
### Template 1 — Telecom
|
||||
|
||||
Diretório:
|
||||
|
||||
```text
|
||||
templates/template_telecom_billing_product
|
||||
```
|
||||
|
||||
Agentes:
|
||||
|
||||
- BillingAgent
|
||||
- ProductAgent
|
||||
|
||||
### Template 2 — Retail/E-commerce
|
||||
|
||||
Diretório:
|
||||
|
||||
```text
|
||||
templates/template_retail_orders_support
|
||||
```
|
||||
|
||||
Agentes:
|
||||
|
||||
- OrdersAgent
|
||||
- SupportAgent
|
||||
|
||||
Este segundo template mostra como reutilizar a mesma arquitetura para outro domínio de negócio.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Delta Implementado para Padrão FIRST
|
||||
|
||||
Esta versão corrige as prioridades levantadas na comparação com o FIRST:
|
||||
|
||||
1. Oracle Session Repository real
|
||||
2. Oracle Message History real
|
||||
3. Oracle LangGraph Checkpoint Repository real
|
||||
4. LangGraph Deep Telemetry
|
||||
5. Token Accounting
|
||||
6. Cost Accounting
|
||||
7. Session Lock SSE
|
||||
8. Replay Buffer SSE
|
||||
9. KeepAlive SSE
|
||||
10. Recovery por Last-Event-ID
|
||||
11. Redis Provider e Distributed Cache
|
||||
12. Oracle Vector Provider
|
||||
13. Oracle Graph Provider
|
||||
14. RAG Telemetry
|
||||
15. Langfuse Generation Tracking
|
||||
16. OpenTelemetry/Event Bus compatível
|
||||
17. OCI Streaming Exporter preservado
|
||||
|
||||
A lógica de domínio continua genérica; o framework não copia regras específicas de cobrança do FIRST.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Agent Framework FIRST Enterprise Plus
|
||||
|
||||
Esta versão evolui o framework nos quatro blocos solicitados:
|
||||
|
||||
1. **Langfuse Enterprise completo**
|
||||
- `Telemetry.span()` com trace/session/user/metadata/tags.
|
||||
- `Telemetry.generation()` com `usage`, token/cost metadata e compatibilidade Langfuse v2/v3.
|
||||
- `Telemetry.score()` para judges/avaliações.
|
||||
- Eventos arbitrários são registrados como spans seguros para evitar `Unknown observation type` no Langfuse.
|
||||
|
||||
2. **Token/Cost Accounting completo**
|
||||
- `TokenUsageCollector` suporta `prompt_tokens`, `completion_tokens`, `cached_tokens`, `reasoning_tokens` e `total_tokens`.
|
||||
- Tabela de preços por modelo via `MODEL_PRICES_JSON`.
|
||||
- Conversão USD→BRL via `USD_BRL_RATE`.
|
||||
- Persistência em `UsageRepository` e endpoint `/debug/usage`.
|
||||
|
||||
3. **Redis distribuído**
|
||||
- `DistributedCache`: L1 memória + L2 Redis/SQLite/Oracle.
|
||||
- `RedisCache` com `redis.asyncio` quando disponível e fallback sync.
|
||||
- Namespace por `CACHE_KEY_PREFIX`.
|
||||
- Telemetria de cache hit/miss/set/delete.
|
||||
|
||||
4. **Oracle Vector + PGQL reais**
|
||||
- `OracleVectorStore` usa `VECTOR_DISTANCE(..., COSINE)` e `TO_VECTOR()` no Oracle 23ai.
|
||||
- Tentativa automática de criar vector index quando suportado.
|
||||
- `OracleGraphStore` usa tabelas `GRAPH_NODE` e `GRAPH_EDGE`.
|
||||
- Suporte a criação de Property Graph e consulta por `GRAPH_TABLE`/PGQL, com fallback SQL.
|
||||
|
||||
Também foi corrigido o problema de duplicação SSE por replay + fila live usando controle de `max_replayed_id` no `SSEHub.subscribe()`.
|
||||
|
||||
## Testes
|
||||
|
||||
```bash
|
||||
PYTHONPATH=agent_framework/src pytest -q tests/unit
|
||||
```
|
||||
|
||||
Resultado validado nesta geração:
|
||||
|
||||
```text
|
||||
17 passed
|
||||
```
|
||||
|
||||
## Segurança
|
||||
|
||||
Os arquivos `.env` foram higienizados para não conter chaves reais. Configure suas credenciais localmente antes de usar OCI/Langfuse.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Ajustes operacionais finais — padrão FIRST
|
||||
|
||||
Esta versão corrige os gaps identificados na comparação contra o FIRST.
|
||||
|
||||
## Correções aplicadas
|
||||
|
||||
### 1. Checkpoint LangGraph operacional
|
||||
|
||||
O workflow não compila mais com `MemorySaver()` diretamente. Foi criado o adaptador:
|
||||
|
||||
```text
|
||||
agent_framework/checkpoints/langgraph_saver.py
|
||||
```
|
||||
|
||||
Ele conecta o LangGraph ao repository configurado do framework:
|
||||
|
||||
- `memory`
|
||||
- `sqlite`
|
||||
- `oracle` / `autonomous`
|
||||
|
||||
No workflow:
|
||||
|
||||
```python
|
||||
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
|
||||
```
|
||||
|
||||
### 2. Telemetria LangGraph envolvendo a execução real
|
||||
|
||||
Foi adicionado wrapper de nó no workflow:
|
||||
|
||||
```python
|
||||
self._node("billing_agent", self.billing_agent)
|
||||
```
|
||||
|
||||
Assim o span/evento `langgraph.node.*` envolve a execução real do nó, não apenas um bloco vazio.
|
||||
|
||||
Eventos emitidos:
|
||||
|
||||
- `langgraph.node.started`
|
||||
- `langgraph.node.completed`
|
||||
- `langgraph.node.failed`
|
||||
- `langgraph.edge.selected`
|
||||
|
||||
### 3. RAG integrado aos agentes
|
||||
|
||||
Os agentes agora recebem `RagService` e usam o contexto recuperado no prompt:
|
||||
|
||||
- BillingAgent
|
||||
- ProductAgent
|
||||
- OrdersAgent
|
||||
- SupportAgent
|
||||
|
||||
O RAG usa:
|
||||
|
||||
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
|
||||
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
|
||||
- `RAG_TOP_K`
|
||||
|
||||
### 4. Cache integrado ao runtime dos agentes
|
||||
|
||||
Criado mixin:
|
||||
|
||||
```text
|
||||
agent_template_backend/app/agents/runtime.py
|
||||
```
|
||||
|
||||
Ele adiciona:
|
||||
|
||||
- busca RAG padronizada;
|
||||
- chave de cache para chamada LLM;
|
||||
- hit/miss com telemetria;
|
||||
- cache distribuído via `create_cache(settings)`.
|
||||
|
||||
### 5. Testes unitários
|
||||
|
||||
Criada pasta:
|
||||
|
||||
```text
|
||||
tests/unit
|
||||
```
|
||||
|
||||
Cobertura inicial:
|
||||
|
||||
- cache;
|
||||
- SSE;
|
||||
- RAG;
|
||||
- checkpoint saver;
|
||||
- telemetria LangGraph;
|
||||
- runtime dos agentes;
|
||||
- verificação estática do workflow;
|
||||
- imports principais.
|
||||
|
||||
Validação local executada:
|
||||
|
||||
```text
|
||||
12 passed
|
||||
```
|
||||
|
||||
## Como testar
|
||||
|
||||
```bash
|
||||
cd projeto_agent_framework_first_ready
|
||||
pip install -r agent_template_backend/requirements.txt
|
||||
pytest -q tests/unit
|
||||
```
|
||||
379
agent_framework_oci/Documentacao/README_FIRST_READY.md
Normal file
379
agent_framework_oci/Documentacao/README_FIRST_READY.md
Normal file
@@ -0,0 +1,379 @@
|
||||
# Projeto Agent Framework FIRST-ready
|
||||
|
||||
Esta versão mantém a arquitetura do `meu_projeto_agent_framework` e adiciona os padrões operacionais encontrados no projeto FIRST.
|
||||
|
||||
## Recursos adicionados
|
||||
|
||||
1. **SSE no padrão FIRST**
|
||||
- `GET /gateway/events/{session_id}` para stream `text/event-stream`.
|
||||
- `POST /gateway/message/sse` para processar mensagem emitindo eventos SSE.
|
||||
- Eventos: `connected`, `flow.start`, `session.upserted`, `message.received`, `workflow.started`, `workflow.completed`, `message.responded`, `flow.end`.
|
||||
- Keepalive configurável por `SSE_KEEPALIVE_SECONDS`.
|
||||
- Lock por sessão para evitar concorrência dentro da mesma conversa.
|
||||
- Replay de eventos via `Last-Event-ID` ou query param `last_event_id`.
|
||||
|
||||
2. **Persistência de sessão e mensagens**
|
||||
- Implementado provider `sqlite`, executável localmente.
|
||||
- `SESSION_REPOSITORY_PROVIDER=sqlite`.
|
||||
- `MEMORY_REPOSITORY_PROVIDER=sqlite`.
|
||||
- Tabelas locais: `agent_sessions`, `agent_messages`.
|
||||
- Idempotência por `message_id`.
|
||||
|
||||
3. **Checkpoint persistente**
|
||||
- Implementado provider `sqlite` para checkpoint final do workflow.
|
||||
- `CHECKPOINT_REPOSITORY_PROVIDER=sqlite`.
|
||||
- Endpoint de leitura: `GET /sessions/{session_id}/checkpoint`.
|
||||
|
||||
4. **Histórico de mensagens**
|
||||
- Endpoint: `GET /sessions/{session_id}/messages`.
|
||||
- Histórico usado como memória conversacional antes de chamar o LangGraph.
|
||||
|
||||
5. **Cache**
|
||||
- Novo módulo `agent_framework.cache.cache`.
|
||||
- Suporta cache local em memória e Redis se `ENABLE_REDIS_CACHE=true`.
|
||||
|
||||
6. **RAG / Vector Store**
|
||||
- `agent_framework.rag.vector_store` agora possui `InMemoryVectorStore`, `SQLiteVectorStore` e contrato `AutonomousVectorStore`.
|
||||
- A versão SQLite usa busca lexical local para desenvolvimento.
|
||||
- O contrato permite trocar por Oracle Vector Search sem alterar a camada de aplicação.
|
||||
|
||||
7. **Observabilidade**
|
||||
- Mantém Langfuse existente.
|
||||
- Acrescenta eventos de gateway/SSE/workflow com `session_id`, `agent_id`, `tenant_id`, `message_id`, rota e intenção.
|
||||
|
||||
## Arquitetura resultante
|
||||
|
||||
```text
|
||||
Browser
|
||||
|-- POST /gateway/message/sse
|
||||
|-- GET /gateway/events/{session_id}
|
||||
|
|
||||
FastAPI Template Backend
|
||||
|
|
||||
ChannelGateway
|
||||
|
|
||||
SessionRepository + MessageHistory + CheckpointRepository
|
||||
|
|
||||
LangGraph AgentWorkflow
|
||||
|
|
||||
Guardrails -> Router/Supervisor -> Agent -> Output Guardrails -> Judges
|
||||
|
|
||||
Telemetry / Langfuse / OCI Streaming
|
||||
```
|
||||
|
||||
## Como rodar localmente
|
||||
|
||||
```bash
|
||||
cd agent_template_backend
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
pip install -e ../agent_framework
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
Frontend:
|
||||
|
||||
```bash
|
||||
cd agent_frontend
|
||||
python -m http.server 3000
|
||||
```
|
||||
|
||||
Abra:
|
||||
|
||||
```text
|
||||
http://localhost:3000
|
||||
```
|
||||
|
||||
## Variáveis principais
|
||||
|
||||
```env
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
SQLITE_DB_PATH=./data/agent_framework.db
|
||||
ENABLE_SSE=true
|
||||
SSE_KEEPALIVE_SECONDS=15
|
||||
ENABLE_MESSAGE_IDEMPOTENCY=true
|
||||
```
|
||||
|
||||
## Teste via curl
|
||||
|
||||
Mensagem normal:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"text":"teste","message":"teste","session_id":"s1","user_id":"u1","message_id":"m1"}}'
|
||||
```
|
||||
|
||||
Mensagem com SSE:
|
||||
|
||||
```bash
|
||||
curl -N http://localhost:8000/gateway/events/s1
|
||||
```
|
||||
|
||||
Em outro terminal:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message/sse \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"text":"teste","message":"teste","session_id":"s1","user_id":"u1","message_id":"m2"}}'
|
||||
```
|
||||
|
||||
Histórico:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/sessions/s1/messages
|
||||
```
|
||||
|
||||
Checkpoint:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/sessions/s1/checkpoint
|
||||
```
|
||||
|
||||
## Observação importante
|
||||
|
||||
A versão adicionada é executável localmente com SQLite. As classes `AutonomousSessionRepository`, `DatabaseMessageHistory`, `AutonomousCheckpointRepository` e `AutonomousVectorStore` mantêm o contrato para Oracle Autonomous Database, mas nesta entrega usam SQLite como backend local para permitir rodar e testar sem infraestrutura Oracle.
|
||||
|
||||
## Evolução de Observabilidade no padrão FIRST
|
||||
|
||||
Esta versão adiciona uma camada corporativa de observabilidade ao framework, mantendo os componentes reutilizáveis dentro de `agent_framework`.
|
||||
|
||||
### Componentes adicionados
|
||||
|
||||
```text
|
||||
agent_framework/observability/
|
||||
├── context.py # ContextVar: request_id, session_id, user_id, tenant_id, agent_id, channel, ura_call_id, workflow_id, message_id
|
||||
├── telemetry.py # Facade central: span, event, generation, rag_event, cache_event, checkpoint_event
|
||||
├── event_bus.py # Event bus interno para plugar logs, SSE, OCI Streaming, Elastic, Phoenix etc.
|
||||
├── otel.py # OpenTelemetry opcional via OTLP
|
||||
├── workflow_events.py # workflow.started, node.started, node.completed, edge.selected, workflow.failed
|
||||
├── guardrail_events.py # guardrail.<CODE>.evaluated e guardrail.<CODE>.blocked
|
||||
├── judge_events.py # judge.<NAME>.evaluated
|
||||
├── streaming_events.py # sse.connected, sse.keepalive, sse.event.emitted
|
||||
└── decorators.py # decorator @traced para classes do framework
|
||||
```
|
||||
|
||||
### Correlação ponta-a-ponta
|
||||
|
||||
Cada chamada HTTP cria ou propaga `x-request-id` e o fluxo de mensagem vincula:
|
||||
|
||||
```text
|
||||
request_id → tenant_id → agent_id → session_id → user_id → channel → message_id → workflow_id
|
||||
```
|
||||
|
||||
O contexto usa `ContextVar`, portanto funciona em chamadas assíncronas, FastAPI, LangGraph e providers LLM.
|
||||
|
||||
### Langfuse
|
||||
|
||||
Ative no `.env`:
|
||||
|
||||
```env
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-...
|
||||
LANGFUSE_SECRET_KEY=sk-lf-...
|
||||
LANGFUSE_HOST=http://localhost:3000
|
||||
```
|
||||
|
||||
O framework registra:
|
||||
|
||||
```text
|
||||
Trace de conversa
|
||||
├── http.request
|
||||
├── agent.gateway_message
|
||||
├── workflow.langgraph.ainvoke
|
||||
├── workflow.input_guardrails
|
||||
│ └── guardrail.<CODE>.evaluated / blocked
|
||||
├── workflow.routing_decision
|
||||
├── workflow.agent.<agent>
|
||||
│ └── generation.<model>
|
||||
├── workflow.output_guardrails
|
||||
├── workflow.judge
|
||||
│ └── judge.<NAME>.evaluated
|
||||
├── workflow.supervisor_review
|
||||
├── workflow.persist
|
||||
└── sse.event.emitted / sse.keepalive
|
||||
```
|
||||
|
||||
### OpenTelemetry
|
||||
|
||||
Ative no `.env`:
|
||||
|
||||
```env
|
||||
ENABLE_OTEL=true
|
||||
OTEL_SERVICE_NAME=agent-framework-template
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
|
||||
```
|
||||
|
||||
Com isso, os mesmos spans são exportados via OTLP para Elastic, Grafana Tempo, Jaeger, Collector ou outro backend compatível.
|
||||
|
||||
### SSE observável
|
||||
|
||||
O `SSEHub` agora registra eventos de:
|
||||
|
||||
- conexão aberta;
|
||||
- replay de eventos;
|
||||
- evento emitido;
|
||||
- keepalive;
|
||||
- lock por sessão no processamento de mensagem.
|
||||
|
||||
### Guardrails e Judges
|
||||
|
||||
Além dos eventos agregados (`guardrails.input.completed`, `judges.completed`), cada decisão individual gera telemetria própria:
|
||||
|
||||
```text
|
||||
guardrail.MSK.evaluated
|
||||
guardrail.OOS.blocked
|
||||
judge.response_quality.evaluated
|
||||
judge.groundedness.evaluated
|
||||
```
|
||||
|
||||
### Extensão para outros backends
|
||||
|
||||
A classe `Telemetry.event_bus` permite plugar novos handlers sem alterar o workflow. Exemplo:
|
||||
|
||||
```python
|
||||
async def enviar_para_elastic(event):
|
||||
...
|
||||
|
||||
telemetry.event_bus.subscribe(enviar_para_elastic)
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Evolução FIRST Enterprise Completa
|
||||
|
||||
Esta versão recebeu os componentes que faltavam para aproximar o framework do padrão operacional do projeto FIRST:
|
||||
|
||||
### Persistência Oracle Autonomous Database
|
||||
|
||||
Foram adicionados providers reais Oracle:
|
||||
|
||||
- `OracleSessionRepository`
|
||||
- `OracleMessageHistory`
|
||||
- `OracleCheckpointRepository`
|
||||
- `OracleCache`
|
||||
- `OracleVectorStore`
|
||||
- `OracleGraphStore`
|
||||
- `OracleStore`
|
||||
|
||||
Tabelas criadas automaticamente com prefixo configurável `ADB_TABLE_PREFIX`:
|
||||
|
||||
- `<PREFIX>_AGENT_SESSION`
|
||||
- `<PREFIX>_AGENT_MESSAGE`
|
||||
- `<PREFIX>_WORKFLOW_CHECKPOINT`
|
||||
- `<PREFIX>_WORKFLOW_CHECKPOINT_WRITE`
|
||||
- `<PREFIX>_WORKFLOW_CHECKPOINT_BLOB`
|
||||
- `<PREFIX>_SSE_EVENT`
|
||||
- `<PREFIX>_CACHE_ENTRY`
|
||||
- `<PREFIX>_RAG_DOCUMENT`
|
||||
- `<PREFIX>_GRAPH_EDGE`
|
||||
|
||||
### Configuração Oracle
|
||||
|
||||
```env
|
||||
SESSION_REPOSITORY_PROVIDER=oracle
|
||||
MEMORY_REPOSITORY_PROVIDER=oracle
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=oracle
|
||||
CACHE_BACKEND_PROVIDER=oracle
|
||||
VECTOR_STORE_PROVIDER=oracle
|
||||
GRAPH_STORE_PROVIDER=oracle
|
||||
SSE_STORE_PROVIDER=oracle
|
||||
|
||||
ADB_USER=ADMIN
|
||||
ADB_PASSWORD=***
|
||||
ADB_DSN=meu_adb_high
|
||||
ADB_WALLET_LOCATION=/path/wallet
|
||||
ADB_WALLET_PASSWORD=***
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
```
|
||||
|
||||
### SSE Enterprise
|
||||
|
||||
O SSE agora possui:
|
||||
|
||||
- lock por sessão (`SessionLockManager`)
|
||||
- keepalive configurável
|
||||
- replay por `Last-Event-ID`
|
||||
- persistência de eventos em SQLite ou Oracle
|
||||
- telemetria de conexão, replay, keepalive e desconexão
|
||||
|
||||
Endpoint:
|
||||
|
||||
```text
|
||||
GET /gateway/events/{session_id}?last_event_id=123
|
||||
```
|
||||
|
||||
### LangGraph Deep Telemetry
|
||||
|
||||
Foi adicionado `LangGraphDeepTelemetry` com eventos:
|
||||
|
||||
- `langgraph.node.started`
|
||||
- `langgraph.node.completed`
|
||||
- `langgraph.node.failed`
|
||||
- `langgraph.edge.selected`
|
||||
|
||||
Esses eventos são enviados para o Event Bus, Langfuse e OpenTelemetry quando habilitados.
|
||||
|
||||
### Token e Cost Accounting
|
||||
|
||||
Foi adicionado:
|
||||
|
||||
- `TokenUsageCollector`
|
||||
- `CostTracker`
|
||||
- cálculo de `prompt_tokens`, `completion_tokens`, `cached_tokens`, `total_tokens`
|
||||
- cálculo de `cost_usd` e `cost_brl`
|
||||
|
||||
Configuração opcional:
|
||||
|
||||
```env
|
||||
USD_BRL_RATE=5.0
|
||||
MODEL_PRICES_JSON={"openai.gpt-4.1":{"input_per_1m":"2.00","output_per_1m":"8.00"}}
|
||||
```
|
||||
|
||||
### Cache Enterprise
|
||||
|
||||
O cache agora é em cascata:
|
||||
|
||||
```text
|
||||
L1: InMemory
|
||||
L2: Redis, SQLite ou Oracle
|
||||
```
|
||||
|
||||
Configuração:
|
||||
|
||||
```env
|
||||
ENABLE_REDIS_CACHE=true
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
```
|
||||
|
||||
ou:
|
||||
|
||||
```env
|
||||
CACHE_BACKEND_PROVIDER=oracle
|
||||
```
|
||||
|
||||
### RAG Oracle 23ai
|
||||
|
||||
Foi adicionado `OracleVectorStore`, com suporte a coluna `VECTOR` e `VECTOR_DISTANCE()` quando um embedding provider for conectado.
|
||||
Sem embedding provider, mantém fallback lexical para desenvolvimento local.
|
||||
|
||||
Também foi adicionado `OracleGraphStore` com tabela de arestas, pronto para evoluir para PGQL/Property Graph.
|
||||
|
||||
### Langfuse
|
||||
|
||||
Cada chamada LLM agora gera `generation` com:
|
||||
|
||||
- input
|
||||
- output
|
||||
- model
|
||||
- provider
|
||||
- token usage
|
||||
- cost metadata
|
||||
|
||||
Além disso, spans de workflow, guardrails, judges, RAG, cache, checkpoint, SSE e LangGraph são publicados pelo mesmo Event Bus.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Guardrails implementados no framework
|
||||
|
||||
Esta versão adiciona uma camada pragmática de guardrails ao `agent_framework`, inspirada na separação de rails por estágio: input, output, retrieval e execução/tool.
|
||||
|
||||
## Rails de input
|
||||
|
||||
- `MSIZE` — bloqueia mensagens excessivamente grandes.
|
||||
- `MSK` — mascara CPF, CNPJ, telefone, e-mail, cartão, CEP, RG, tokens e chaves.
|
||||
- `TOX` — detecta toxicidade e registra severidade sem bloquear por padrão.
|
||||
- `PINJ` — detecta prompt injection e registra score.
|
||||
- `JBRK` — detecta jailbreak/roleplay de burla e registra score.
|
||||
- `VLOOP` — bloqueia loop conversacional repetitivo.
|
||||
|
||||
## Rails de output
|
||||
|
||||
- `PII_OUT` — mascara PII na resposta do agente.
|
||||
- `CMP` — suaviza promessas absolutas e linguagem de garantia excessiva.
|
||||
- `REVPREC` — bloqueia verbalização de ação operacional sem confirmação de tool.
|
||||
- `GND` — sinaliza groundedness/risco quando há resposta específica sem evidência.
|
||||
- `ALUC_RISK` — marca risco de alucinação para telemetria e judges.
|
||||
|
||||
## Rails opcionais
|
||||
|
||||
- `RET_REL` — valida relevância de chunks de retrieval por score mínimo.
|
||||
- `TOOL_VAL` — valida ferramenta MCP/tool, argumentos obrigatórios, valores negativos e allowlist.
|
||||
|
||||
## Arquivos alterados
|
||||
|
||||
- `agent_framework/src/agent_framework/guardrails/rails.py`
|
||||
- `agent_framework/src/agent_framework/guardrails/pipeline.py`
|
||||
- `agent_framework/src/agent_framework/guardrails/__init__.py`
|
||||
|
||||
## Uso rápido
|
||||
|
||||
```python
|
||||
from agent_framework.guardrails.pipeline import GuardrailPipeline
|
||||
|
||||
pipeline = GuardrailPipeline()
|
||||
|
||||
sanitized_input, input_decisions = await pipeline.run_input(
|
||||
user_text,
|
||||
{"history_texts": history_texts},
|
||||
)
|
||||
|
||||
final_answer, output_decisions = await pipeline.run_output(
|
||||
answer,
|
||||
context,
|
||||
)
|
||||
```
|
||||
|
||||
Para tools/MCP:
|
||||
|
||||
```python
|
||||
_, decisions = await pipeline.run_tool(
|
||||
"cancelar_produto",
|
||||
{"produto": "VAS", "valor": 0},
|
||||
{
|
||||
"required_args": ["produto"],
|
||||
"allowed_tools": ["cancelar_produto", "consultar_fatura"],
|
||||
},
|
||||
)
|
||||
```
|
||||
139
agent_framework_oci/Documentacao/README_MAX_OPERACIONAL.md
Normal file
139
agent_framework_oci/Documentacao/README_MAX_OPERACIONAL.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Projeto Agent Framework — FIRST Operational Max
|
||||
|
||||
Esta versão adiciona os ajustes operacionais que faltavam para aproximar o framework do padrão FIRST em produção.
|
||||
|
||||
## Ajustes incluídos nesta versão
|
||||
|
||||
### 1. Langfuse Enterprise Adapter
|
||||
Novo módulo:
|
||||
|
||||
```text
|
||||
agent_framework/observability/langfuse_enterprise.py
|
||||
```
|
||||
|
||||
Inclui adaptador compatível com SDKs Langfuse v2/v3 para:
|
||||
|
||||
- atualização de trace;
|
||||
- score/avaliação de trace;
|
||||
- prompt registry quando suportado pelo SDK;
|
||||
- isolamento das diferenças de API do Langfuse.
|
||||
|
||||
### 2. Token e Cost Accounting persistente
|
||||
Novo pacote:
|
||||
|
||||
```text
|
||||
agent_framework/billing/
|
||||
```
|
||||
|
||||
Inclui:
|
||||
|
||||
- `UsageRecord`
|
||||
- `SQLiteUsageRepository`
|
||||
- `OracleUsageRepository`
|
||||
- `create_usage_repository(settings)`
|
||||
|
||||
O provider LLM agora registra automaticamente:
|
||||
|
||||
- `prompt_tokens`
|
||||
- `completion_tokens`
|
||||
- `cached_tokens`
|
||||
- `total_tokens`
|
||||
- `cost_usd`
|
||||
- `cost_brl`
|
||||
- `tenant_id`
|
||||
- `agent_id`
|
||||
- `session_id`
|
||||
- `message_id`
|
||||
|
||||
Novo endpoint:
|
||||
|
||||
```http
|
||||
GET /debug/usage
|
||||
GET /debug/usage?tenant_id=default
|
||||
GET /debug/usage?session_id=<id>
|
||||
```
|
||||
|
||||
### 3. RAG Service operacional
|
||||
Novo módulo:
|
||||
|
||||
```text
|
||||
agent_framework/rag/rag_service.py
|
||||
```
|
||||
|
||||
Inclui:
|
||||
|
||||
- `RagService.add_documents()`
|
||||
- `RagService.retrieve()`
|
||||
- `RagResult.as_prompt_context()`
|
||||
- telemetria de latência, quantidade de documentos, top scores e grafo.
|
||||
|
||||
### 4. Configuração nova
|
||||
Variável adicionada:
|
||||
|
||||
```env
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
Valores:
|
||||
|
||||
```text
|
||||
sqlite
|
||||
oracle
|
||||
autonomous
|
||||
```
|
||||
|
||||
### 5. Compatibilidade operacional local
|
||||
Por padrão, a contabilização de uso usa SQLite mesmo que o restante esteja em memória. Assim é possível testar localmente sem Oracle.
|
||||
|
||||
## Teste rápido
|
||||
|
||||
```bash
|
||||
cd agent_template_backend
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
Teste uma mensagem:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"text":"teste","user_id":"u1","session_id":"s1"}}'
|
||||
```
|
||||
|
||||
Verifique uso/custo:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/debug/usage
|
||||
```
|
||||
|
||||
## Para rodar com padrão mais próximo de produção
|
||||
|
||||
```env
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
CACHE_BACKEND_PROVIDER=sqlite
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_HOST=http://localhost:3000
|
||||
LANGFUSE_PUBLIC_KEY=...
|
||||
LANGFUSE_SECRET_KEY=...
|
||||
```
|
||||
|
||||
Para Autonomous Database:
|
||||
|
||||
```env
|
||||
SESSION_REPOSITORY_PROVIDER=oracle
|
||||
MEMORY_REPOSITORY_PROVIDER=oracle
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=oracle
|
||||
USAGE_REPOSITORY_PROVIDER=oracle
|
||||
CACHE_BACKEND_PROVIDER=oracle
|
||||
VECTOR_STORE_PROVIDER=oracle
|
||||
GRAPH_STORE_PROVIDER=oracle
|
||||
ADB_USER=...
|
||||
ADB_PASSWORD=...
|
||||
ADB_DSN=...
|
||||
ADB_WALLET_LOCATION=...
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
```
|
||||
79
agent_framework_oci/Documentacao/README_MCP.md
Normal file
79
agent_framework_oci/Documentacao/README_MCP.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# AI Agent Platform com MCP Tools
|
||||
|
||||
Esta versão adiciona uma camada MCP ao framework:
|
||||
|
||||
- `agent_framework.mcp.MCPToolRouter`
|
||||
- `agent_template_backend/config/mcp_servers.yaml`
|
||||
- `agent_template_backend/config/tools.yaml`
|
||||
- `mcp_servers/telecom_mcp_server`
|
||||
- `mcp_servers/retail_mcp_server`
|
||||
|
||||
## Subir localmente
|
||||
|
||||
Terminal 1:
|
||||
|
||||
```bash
|
||||
bash ./scripts/run_mcp_servers.sh
|
||||
```
|
||||
|
||||
Terminal 2:
|
||||
|
||||
```bash
|
||||
cd agent_template_backend
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -e ../agent_framework
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --reload --reload-dir app --reload-dir config --port 8000
|
||||
```
|
||||
|
||||
Terminal 3:
|
||||
|
||||
```bash
|
||||
cd agent_frontend
|
||||
python -m http.server 5173
|
||||
```
|
||||
|
||||
## Testes rápidos
|
||||
|
||||
Listar tools MCP carregadas pelo backend:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/debug/mcp/tools
|
||||
```
|
||||
|
||||
Chamar tool diretamente via backend:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/debug/mcp/call/consultar_fatura \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"msisdn":"11999999999","invoice_id":"INV-001"}'
|
||||
```
|
||||
|
||||
Roteamento Telecom + MCP:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"session_id":"sess-tel-1","message":"Minha fatura veio alta","context":{"msisdn":"11999999999","invoice_id":"INV-001"}}}'
|
||||
```
|
||||
|
||||
Roteamento Retail + MCP:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"session_id":"sess-ret-1","message":"Meu pedido não chegou","context":{"order_id":"PED-1001","customer_id":"C-001"}}}'
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
No compose, o backend usa `config/mcp_servers.docker.yaml` para apontar para `telecom-mcp` e `retail-mcp`.
|
||||
|
||||
## Operações read-only e transacionais
|
||||
|
||||
Use `config/tool_policies.yaml` no backend para classificar somente as operações que precisam de tratamento adicional. A validação é aplicada no roteador central antes do MCP Gateway/Server. O arquivo é opcional e templates antigos continuam usando as políticas já presentes em `tools.yaml`. A configuração completa e o roteiro de migração estão em [README_TOOL_POLICIES.md](README_TOOL_POLICIES.md).
|
||||
108
agent_framework_oci/Documentacao/README_MULTI_AGENT_ISOLATION.md
Normal file
108
agent_framework_oci/Documentacao/README_MULTI_AGENT_ISOLATION.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Multi-agent isolation
|
||||
|
||||
Esta versão permite subir mais de um `agent_template` no mesmo backend e chavear por `agent_id` sem misturar estado.
|
||||
|
||||
## O que ficou isolado
|
||||
|
||||
A chave lógica usada pelo backend é:
|
||||
|
||||
```text
|
||||
tenant_id:agent_id:session_id
|
||||
```
|
||||
|
||||
Com isso ficam isolados:
|
||||
|
||||
- memória conversacional;
|
||||
- checkpoints do LangGraph (`thread_id`);
|
||||
- telemetria/tags;
|
||||
- prompts por perfil de agente;
|
||||
- configuração de guardrails por agente;
|
||||
- configuração de judges por agente;
|
||||
- metadados de sessão.
|
||||
|
||||
## Arquivo principal
|
||||
|
||||
```text
|
||||
agent_template_backend/config/agents.yaml
|
||||
```
|
||||
|
||||
Exemplo:
|
||||
|
||||
```yaml
|
||||
default_agent_id: telecom_contas
|
||||
agents:
|
||||
- agent_id: telecom_contas
|
||||
prompt_policy_path: ./config/agents/telecom_contas/prompt_policy.yaml
|
||||
guardrails_config_path: ./config/agents/telecom_contas/guardrails.yaml
|
||||
judges_config_path: ./config/agents/telecom_contas/judges.yaml
|
||||
|
||||
- agent_id: retail_orders
|
||||
prompt_policy_path: ./config/agents/retail_orders/prompt_policy.yaml
|
||||
guardrails_config_path: ./config/agents/retail_orders/guardrails.yaml
|
||||
judges_config_path: ./config/agents/retail_orders/judges.yaml
|
||||
```
|
||||
|
||||
## Como escolher o agente na chamada
|
||||
|
||||
### Telecom
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"agent_id": "telecom_contas",
|
||||
"tenant_id": "tim",
|
||||
"payload": {
|
||||
"session_id": "sessao-123",
|
||||
"user_id": "cliente-1",
|
||||
"message": "Quero entender minha fatura",
|
||||
"context": {"invoice_id": "FAT-001"}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Retail
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"agent_id": "retail_orders",
|
||||
"tenant_id": "loja",
|
||||
"payload": {
|
||||
"session_id": "sessao-123",
|
||||
"user_id": "cliente-1",
|
||||
"message": "Onde está meu pedido?",
|
||||
"context": {"order_id": "PED-001"}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Mesmo usando o mesmo `session_id`, as conversas ficam separadas porque as chaves finais serão:
|
||||
|
||||
```text
|
||||
tim:telecom_contas:sessao-123
|
||||
loja:retail_orders:sessao-123
|
||||
```
|
||||
|
||||
## Endpoints úteis
|
||||
|
||||
```text
|
||||
GET /agents
|
||||
GET /health
|
||||
POST /debug/route
|
||||
POST /gateway/message
|
||||
```
|
||||
|
||||
## Como adicionar um novo agent_template
|
||||
|
||||
1. Crie uma pasta em `agent_template_backend/config/agents/<novo_agent_id>/`.
|
||||
2. Adicione `prompt_policy.yaml`, `guardrails.yaml` e `judges.yaml`.
|
||||
3. Registre o agente em `config/agents.yaml`.
|
||||
4. Chame `/gateway/message` usando `agent_id=<novo_agent_id>`.
|
||||
|
||||
## Observação arquitetural
|
||||
|
||||
O backend continua usando um único processo FastAPI e um único framework instalado, mas o estado persistido não usa mais `session_id` sozinho. Isso evita que dois agentes compartilhem memória, checkpoints ou decisões de governança acidentalmente.
|
||||
@@ -0,0 +1,244 @@
|
||||
# Route Stickiness Semântica e Controle Global de Sessão no Agent Framework OCI
|
||||
|
||||
## Objetivo
|
||||
|
||||
A route stickiness semântica evita executar novamente o Enterprise Router quando uma nova mensagem continua claramente sob responsabilidade do agente ativo. A implementação usa um perfil LLM leve e não contém regexes, listas de frases, palavras específicas de idioma ou regras conversacionais por domínio.
|
||||
|
||||
A funcionalidade é opcional e preserva integralmente o comportamento anterior quando desabilitada, quando não existe agente ativo, quando a confiança é baixa ou quando ocorre erro na inferência.
|
||||
|
||||
## Decisão arquitetural
|
||||
|
||||
O classificador possui uma responsabilidade transversal e restrita:
|
||||
|
||||
- `CONTINUE`: a mensagem continua com o agente ativo;
|
||||
- `ROUTE`: a mensagem deve seguir para o Enterprise Router normal;
|
||||
- `HUMAN_HANDOFF`: o usuário solicitou atendimento humano;
|
||||
- `END_SESSION`: o usuário solicitou ou confirmou o encerramento do atendimento.
|
||||
|
||||
Ele não responde ao usuário, não escolhe outro agente, não executa ferramentas e não interpreta regras de negócio. As duas ações globais são encaminhadas para nós próprios do grafo, evitando que cada agente implemente prompts ou regras de sessão.
|
||||
|
||||
Fluxo:
|
||||
|
||||
```text
|
||||
Todos os turnos com a funcionalidade habilitada
|
||||
-> classificador semântico leve
|
||||
CONTINUE + agente ativo -> agente ativo
|
||||
ROUTE/baixa confiança/erro -> Enterprise Router
|
||||
HUMAN_HANDOFF -> nó global human_handoff
|
||||
END_SESSION -> nó global end_session
|
||||
|
||||
No primeiro turno, CONTINUE é normalizado para ROUTE porque ainda não existe agente ativo. Handoff e encerramento podem ser reconhecidos mesmo no primeiro turno.
|
||||
```
|
||||
|
||||
## Por que não há regras determinísticas
|
||||
|
||||
A interpretação de linguagem natural por regex exige manutenção contínua para novas construções, idiomas e domínios. Além disso, transfere aos times dos agentes a responsabilidade de manter flags e padrões de continuidade.
|
||||
|
||||
Esta implementação mantém no código apenas decisões técnicas inevitáveis:
|
||||
|
||||
- funcionalidade habilitada ou desabilitada;
|
||||
- validação de que `CONTINUE` exige agente ativo;
|
||||
- threshold de confiança;
|
||||
- fallback em timeout, erro ou JSON inválido.
|
||||
|
||||
Não existem `DEFAULT_FOLLOWUP_PATTERNS`, regras de repetição, listas de pronomes ou keywords de continuidade.
|
||||
|
||||
## Configuração
|
||||
|
||||
### `.env`
|
||||
|
||||
```dotenv
|
||||
ENABLE_ROUTE_STICKINESS=true
|
||||
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.
|
||||
```
|
||||
|
||||
- `ENABLE_ROUTE_STICKINESS`: ativa a capacidade.
|
||||
- `ROUTE_STICKINESS_LLM_PROFILE`: perfil existente em `llm_profiles.yaml`.
|
||||
- `ROUTE_STICKINESS_CONFIDENCE_THRESHOLD`: confiança mínima para bypass.
|
||||
- `ROUTE_STICKINESS_HISTORY_TURNS`: quantidade de turnos recentes enviados ao classificador.
|
||||
- `ROUTE_STICKINESS_MAX_TOKENS`: limite de saída do classificador.
|
||||
- `HUMAN_HANDOFF_MESSAGE`: mensagem devolvida pelo nó global de transferência humana.
|
||||
- `END_SESSION_MESSAGE`: mensagem devolvida pelo nó global de encerramento.
|
||||
|
||||
### Perfil leve
|
||||
|
||||
```yaml
|
||||
profiles:
|
||||
route_continuity:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
```
|
||||
|
||||
O modelo acima é apenas um exemplo. Deve ser substituído pelo menor modelo aprovado e disponível no ambiente OCI. O framework reutiliza o mecanismo já existente de `LLM_PROFILES_PATH`; não há uma segunda configuração de provider/model específica para a funcionalidade.
|
||||
|
||||
## Contexto enviado ao modelo
|
||||
|
||||
O classificador recebe somente:
|
||||
|
||||
- agente ativo;
|
||||
- descrições das capacidades dos agentes derivadas das intents já existentes;
|
||||
- intent e domínio anteriores;
|
||||
- histórico recente limitado;
|
||||
- mensagem atual.
|
||||
|
||||
Não são enviados RAG completo, resultados MCP integrais, prompt do agente ou regras de negócio.
|
||||
|
||||
## Exemplos
|
||||
|
||||
### Continuidade
|
||||
|
||||
```text
|
||||
Usuário: Qual é o meu plano?
|
||||
Agente: Seu plano é Controle 50GB.
|
||||
Usuário: O que está incluso?
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "continuity",
|
||||
"route": "product_agent",
|
||||
"route_bypassed": true
|
||||
}
|
||||
```
|
||||
|
||||
### Mudança de domínio
|
||||
|
||||
```text
|
||||
Usuário: Qual é o meu plano?
|
||||
Agente: Seu plano é Controle 50GB.
|
||||
Usuário: Agora quero contestar uma cobrança.
|
||||
```
|
||||
|
||||
O classificador retorna `ROUTE` e o Enterprise Router seleciona o agente apropriado.
|
||||
|
||||
### Baixa confiança ou falha
|
||||
|
||||
Qualquer resultado abaixo do threshold, timeout ou JSON inválido executa o Enterprise Router. A funcionalidade é fail-safe e nunca força continuidade em caso de dúvida.
|
||||
|
||||
## Telemetria
|
||||
|
||||
Evento `router.continuity`:
|
||||
|
||||
```json
|
||||
{
|
||||
"decision": "CONTINUE",
|
||||
"confidence": 0.97,
|
||||
"active_agent": "product_agent",
|
||||
"route_bypassed": true,
|
||||
"profile_name": "route_continuity"
|
||||
}
|
||||
```
|
||||
|
||||
Quando ocorre bypass, `route_decision.method` é `continuity` e o estado final contém:
|
||||
|
||||
- `active_agent`;
|
||||
- `route_bypassed`;
|
||||
- `continuity_signal`.
|
||||
|
||||
## Testes
|
||||
|
||||
```bash
|
||||
pytest -q tests/unit/test_semantic_route_stickiness.py
|
||||
```
|
||||
|
||||
Os testes validam:
|
||||
|
||||
- continuidade com bypass;
|
||||
- mudança de assunto com fallback para o router;
|
||||
- baixa confiança;
|
||||
- saída inválida;
|
||||
- primeiro turno sem chamada ao classificador.
|
||||
|
||||
## Benchmark recomendado
|
||||
|
||||
Executar a mesma conversação com a funcionalidade desabilitada e habilitada, registrando por turno:
|
||||
|
||||
- `route_bypassed`;
|
||||
- `route_decision.method`;
|
||||
- latência do `llm.route_continuity`;
|
||||
- chamadas ao `llm.router`;
|
||||
- tokens por perfil;
|
||||
- latência total p50, p95 e p99.
|
||||
|
||||
A redução de tempo total somente deve ser atribuída à stickiness quando houver `route_bypassed=true` e ausência da geração `llm.router` no mesmo turno.
|
||||
|
||||
|
||||
## Contratos globais
|
||||
|
||||
### Human handoff
|
||||
|
||||
Quando a decisão for `HUMAN_HANDOFF`, o router retorna:
|
||||
|
||||
```json
|
||||
{
|
||||
"route": "human_handoff",
|
||||
"intent": "human_handoff",
|
||||
"method": "continuity",
|
||||
"handoff": true,
|
||||
"metadata": {
|
||||
"session_control": "HUMAN_HANDOFF",
|
||||
"route_bypassed": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
O nó `human_handoff` produz os campos:
|
||||
|
||||
- `session_control=HUMAN_HANDOFF`;
|
||||
- `human_handoff_requested=true`;
|
||||
- `session_ended=false`;
|
||||
- `next_state=HUMAN_HANDOFF_REQUESTED`.
|
||||
|
||||
O evento `session.human_handoff.requested` é emitido para que o Channel Gateway ou a integração do cliente encaminhe a conversa à plataforma humana. O framework não presume uma fila, fornecedor ou protocolo específico.
|
||||
|
||||
### Encerramento
|
||||
|
||||
Quando a decisão for `END_SESSION`, o router retorna:
|
||||
|
||||
```json
|
||||
{
|
||||
"route": "end_session",
|
||||
"intent": "end_session",
|
||||
"method": "continuity",
|
||||
"metadata": {
|
||||
"session_control": "END_SESSION",
|
||||
"route_bypassed": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
O nó `end_session` produz:
|
||||
|
||||
- `session_control=END_SESSION`;
|
||||
- `session_ended=true`;
|
||||
- `human_handoff_requested=false`;
|
||||
- `next_state=SESSION_ENDED`.
|
||||
|
||||
O evento `session.end.requested` é emitido antes da persistência. O backend continua responsável por aplicar a política concreta de expiração, fechamento ou limpeza da sessão em cada canal.
|
||||
|
||||
## Exemplos
|
||||
|
||||
| Mensagem | Contexto | Decisão esperada | Destino |
|
||||
|---|---|---|---|
|
||||
| `o que está incluso?` | `product_agent` ativo | `CONTINUE` | `product_agent` |
|
||||
| `agora quero contestar uma cobrança` | `product_agent` ativo | `ROUTE` | Enterprise Router |
|
||||
| `quero falar com uma pessoa` | com ou sem agente ativo | `HUMAN_HANDOFF` | nó `human_handoff` |
|
||||
| `obrigado, pode encerrar` | com ou sem agente ativo | `END_SESSION` | nó `end_session` |
|
||||
|
||||
## Segurança e fallback
|
||||
|
||||
- Somente decisões acima do threshold são aceitas.
|
||||
- `CONTINUE` sem agente ativo vira `ROUTE`.
|
||||
- JSON inválido, timeout ou erro usa o Enterprise Router.
|
||||
- Handoff e encerramento não executam agentes de domínio nem ferramentas MCP.
|
||||
- O classificador não encerra fisicamente conexões nem seleciona filas humanas; ele emite um contrato global para integração.
|
||||
281
agent_framework_oci/Documentacao/README_ROUTING_MODES.md
Normal file
281
agent_framework_oci/Documentacao/README_ROUTING_MODES.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Modos de roteamento multi-agent: Enterprise Router e Supervisor
|
||||
|
||||
Este projeto suporta dois desenhos arquiteturais para roteamento entre agentes, sem precisar criar dois frameworks diferentes.
|
||||
|
||||
## Modos disponíveis
|
||||
|
||||
Configure por variável de ambiente:
|
||||
|
||||
```bash
|
||||
ROUTING_MODE=router
|
||||
```
|
||||
|
||||
ou:
|
||||
|
||||
```bash
|
||||
ROUTING_MODE=supervisor
|
||||
```
|
||||
|
||||
Também existe a chave documental em `agent_template_backend/config/routing.yaml`:
|
||||
|
||||
```yaml
|
||||
router:
|
||||
mode: router
|
||||
```
|
||||
|
||||
A variável de ambiente `ROUTING_MODE` é a forma recomendada para ativar um modo em runtime, especialmente em Docker, Kubernetes ou OCI.
|
||||
|
||||
---
|
||||
|
||||
## Opção 1: Enterprise Router
|
||||
|
||||
Fluxo:
|
||||
|
||||
```text
|
||||
Usuário
|
||||
-> Input Guardrails
|
||||
-> EnterpriseRouter
|
||||
-> AgentRegistry
|
||||
-> 1 agente especialista
|
||||
-> Output Guardrails
|
||||
-> Judges
|
||||
-> Supervisor Review
|
||||
-> Persistência/eventos
|
||||
```
|
||||
|
||||
Uso recomendado quando cada mensagem deve ser atendida por um único agente especialista.
|
||||
|
||||
Exemplos:
|
||||
|
||||
- `Minha fatura veio alta` -> `billing_agent`
|
||||
- `Onde está meu pedido?` -> `orders_agent`
|
||||
- `Quero trocar um produto com defeito` -> `support_agent`
|
||||
|
||||
Vantagens:
|
||||
|
||||
- Menor latência.
|
||||
- Menor custo de tokens.
|
||||
- Debug mais simples.
|
||||
- Mais fácil de operar em produção.
|
||||
|
||||
Limitação:
|
||||
|
||||
- Uma mensagem com múltiplos assuntos precisa ser roteada para um agente principal ou tratada por handoff.
|
||||
|
||||
---
|
||||
|
||||
## Opção 2: Supervisor
|
||||
|
||||
Fluxo:
|
||||
|
||||
```text
|
||||
Usuário
|
||||
-> Input Guardrails
|
||||
-> Supervisor.route_plan
|
||||
-> supervisor_agent
|
||||
-> billing_agent opcional
|
||||
-> orders_agent opcional
|
||||
-> product_agent opcional
|
||||
-> support_agent opcional
|
||||
-> Consolidação
|
||||
-> Output Guardrails
|
||||
-> Judges
|
||||
-> Supervisor Review
|
||||
-> Persistência/eventos
|
||||
```
|
||||
|
||||
Uso recomendado quando uma única mensagem pode envolver vários agentes.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```text
|
||||
Meu pedido não chegou e também fui cobrado duas vezes.
|
||||
```
|
||||
|
||||
Neste caso, o supervisor pode acionar:
|
||||
|
||||
- `orders_agent`
|
||||
- `billing_agent`
|
||||
|
||||
Vantagens:
|
||||
|
||||
- Suporta múltiplas intenções na mesma mensagem.
|
||||
- Permite consolidação de respostas.
|
||||
- Facilita cenários enterprise com vários domínios.
|
||||
|
||||
Custos:
|
||||
|
||||
- Maior latência.
|
||||
- Maior consumo de tokens.
|
||||
- Mais complexidade operacional.
|
||||
|
||||
---
|
||||
|
||||
## O que foi alterado no código
|
||||
|
||||
### 1. Configuração
|
||||
|
||||
Arquivo:
|
||||
|
||||
```text
|
||||
agent_framework/src/agent_framework/config/settings.py
|
||||
```
|
||||
|
||||
Foi adicionada a configuração:
|
||||
|
||||
```python
|
||||
ROUTING_MODE: Literal['router','supervisor'] = 'router'
|
||||
```
|
||||
|
||||
### 2. Workflow LangGraph
|
||||
|
||||
Arquivo:
|
||||
|
||||
```text
|
||||
agent_template_backend/app/workflows/agent_graph.py
|
||||
```
|
||||
|
||||
O nó `enterprise_route` foi substituído por um nó genérico:
|
||||
|
||||
```text
|
||||
routing_decision
|
||||
```
|
||||
|
||||
Esse nó decide o caminho com base em `ROUTING_MODE`:
|
||||
|
||||
- `router` usa `EnterpriseRouter`.
|
||||
- `supervisor` usa `Supervisor.route_plan`.
|
||||
|
||||
Também foi adicionado o nó:
|
||||
|
||||
```text
|
||||
supervisor_agent
|
||||
```
|
||||
|
||||
Ele executa um ou mais agentes e consolida o resultado.
|
||||
|
||||
### 3. Supervisor
|
||||
|
||||
Arquivo:
|
||||
|
||||
```text
|
||||
agent_framework/src/agent_framework/supervisor/supervisor.py
|
||||
```
|
||||
|
||||
Foi adicionada a estrutura:
|
||||
|
||||
```python
|
||||
SupervisorPlan
|
||||
```
|
||||
|
||||
E o método:
|
||||
|
||||
```python
|
||||
route_plan(state)
|
||||
```
|
||||
|
||||
Esse método retorna uma lista de agentes a executar.
|
||||
|
||||
### 4. Debug
|
||||
|
||||
Endpoint:
|
||||
|
||||
```text
|
||||
POST /debug/route
|
||||
```
|
||||
|
||||
Agora respeita `ROUTING_MODE` e permite verificar rapidamente como uma mensagem será roteada.
|
||||
|
||||
---
|
||||
|
||||
## Como testar localmente
|
||||
|
||||
### Instalação
|
||||
|
||||
```bash
|
||||
cd agent_template_backend
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -U pip setuptools wheel
|
||||
pip install -e ../agent_framework
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Modo Router
|
||||
|
||||
```bash
|
||||
export ROUTING_MODE=router
|
||||
uvicorn app.main:app --reload --port 8000
|
||||
```
|
||||
|
||||
Teste:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/debug/route \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"text":"Onde está meu pedido?","session_id":"s1"}}'
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "router",
|
||||
"route": "orders_agent"
|
||||
}
|
||||
```
|
||||
|
||||
### Modo Supervisor
|
||||
|
||||
```bash
|
||||
export ROUTING_MODE=supervisor
|
||||
uvicorn app.main:app --reload --port 8000
|
||||
```
|
||||
|
||||
Teste:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/debug/route \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"text":"Meu pedido atrasou e minha fatura veio duplicada","session_id":"s2"}}'
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "supervisor",
|
||||
"route": "supervisor_agent",
|
||||
"agents": ["billing_agent", "orders_agent"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Isolamento
|
||||
|
||||
A chave lógica de isolamento permanece:
|
||||
|
||||
```text
|
||||
tenant_id:agent_id:session_id
|
||||
```
|
||||
|
||||
Use essa chave para memória, sessão, checkpoint e telemetria. Em produção, recomenda-se padronizar `agent_id` por agente especialista ou por template, dependendo do nível de isolamento desejado.
|
||||
|
||||
---
|
||||
|
||||
## Recomendação
|
||||
|
||||
Comece em produção com:
|
||||
|
||||
```bash
|
||||
ROUTING_MODE=router
|
||||
```
|
||||
|
||||
Ative:
|
||||
|
||||
```bash
|
||||
ROUTING_MODE=supervisor
|
||||
```
|
||||
|
||||
quando houver necessidade real de múltiplos agentes na mesma mensagem.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Semantic Route Stickiness and Global Session Control in Agent Framework OCI
|
||||
|
||||
## Purpose
|
||||
|
||||
This optional capability uses a lightweight LLM profile and no regex, phrase lists, or domain-specific language rules. It classifies each turn as:
|
||||
|
||||
- `CONTINUE`: keep the active agent;
|
||||
- `ROUTE`: run the regular Enterprise Router;
|
||||
- `HUMAN_HANDOFF`: request human assistance;
|
||||
- `END_SESSION`: finish the automated session.
|
||||
|
||||
The classifier does not answer the user, execute tools, or implement domain rules. Human handoff and session ending are handled by global graph nodes.
|
||||
|
||||
## Flow
|
||||
|
||||
```text
|
||||
Incoming turn
|
||||
-> lightweight semantic classifier
|
||||
CONTINUE + active agent -> active agent
|
||||
ROUTE / low confidence / error -> Enterprise Router
|
||||
HUMAN_HANDOFF -> human_handoff node
|
||||
END_SESSION -> end_session node
|
||||
```
|
||||
|
||||
`CONTINUE` is converted to `ROUTE` when there is no active agent. Global session actions can be detected on the first turn.
|
||||
|
||||
## Configuration
|
||||
|
||||
```dotenv
|
||||
ENABLE_ROUTE_STICKINESS=true
|
||||
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=I will transfer your request to a person.
|
||||
END_SESSION_MESSAGE=The session has ended. Thank you for contacting us.
|
||||
```
|
||||
|
||||
```yaml
|
||||
profiles:
|
||||
route_continuity:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
```
|
||||
|
||||
Use the smallest approved model available in the target OCI environment.
|
||||
|
||||
## Human handoff contract
|
||||
|
||||
The router returns route `human_handoff`, intent `human_handoff`, `handoff=true`, and metadata `session_control=HUMAN_HANDOFF`. The graph node sets:
|
||||
|
||||
- `human_handoff_requested=true`;
|
||||
- `session_ended=false`;
|
||||
- `next_state=HUMAN_HANDOFF_REQUESTED`.
|
||||
|
||||
It emits `session.human_handoff.requested`. The customer integration remains responsible for choosing the human queue and protocol.
|
||||
|
||||
## End-session contract
|
||||
|
||||
The router returns route `end_session`, intent `end_session`, and metadata `session_control=END_SESSION`. The graph node sets:
|
||||
|
||||
- `session_ended=true`;
|
||||
- `human_handoff_requested=false`;
|
||||
- `next_state=SESSION_ENDED`.
|
||||
|
||||
It emits `session.end.requested`. Channel-specific session expiration or connection closing remains an integration responsibility.
|
||||
|
||||
## Safety behavior
|
||||
|
||||
- Only decisions above the configured confidence threshold are accepted.
|
||||
- Invalid JSON, timeout, low confidence, or errors fall back to the Enterprise Router.
|
||||
- Human handoff and session ending do not execute domain agents or MCP tools.
|
||||
- The classifier never selects a human queue and never physically closes a channel connection.
|
||||
|
||||
## Tests
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=libs/agent_framework/src pytest -q tests/unit/test_semantic_route_stickiness.py
|
||||
```
|
||||
|
||||
The suite covers CONTINUE, ROUTE, low confidence, invalid output, HUMAN_HANDOFF, END_SESSION, first-turn global actions, and CONTINUE without an active agent.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Template Backend/Frontend alinhado ao BusinessContext v2
|
||||
|
||||
Este pacote atualiza o `agent_template_backend` e o `agent_frontend` para refletir o framework novo, onde as chaves vindas do canal/front-end são resolvidas uma vez como chaves canônicas e propagadas pelas camadas até o MCP Server.
|
||||
|
||||
## Fluxo implementado
|
||||
|
||||
1. O front-end envia `tenant_id`, `agent_id`, `session_id` e `business_context`.
|
||||
2. O backend normaliza a mensagem via `ChannelGateway` preservando todo o payload no `context`.
|
||||
3. O backend usa `IdentityResolver` com `config/identity.yaml` para gerar `BusinessContext`:
|
||||
- `customer_key`
|
||||
- `contract_key`
|
||||
- `interaction_key`
|
||||
- `account_key`
|
||||
- `resource_key`
|
||||
- `session_key`
|
||||
4. O workflow recebe `context.business_context`.
|
||||
5. Os agentes de exemplo não montam mais argumentos específicos como `msisdn`, `invoice_id` ou `order_id` diretamente.
|
||||
6. O `MCPToolRouter` usa `config/mcp_parameter_mapping.yaml` para converter chaves canônicas em parâmetros reais de cada tool MCP.
|
||||
|
||||
## Arquivos principais ajustados
|
||||
|
||||
- `agent_template_backend/app/main.py`
|
||||
- carrega `IdentityResolver`;
|
||||
- resolve `BusinessContext` por mensagem;
|
||||
- persiste as chaves na sessão/memória/metadata/SSE;
|
||||
- adiciona `/debug/identity`.
|
||||
|
||||
- `agent_template_backend/app/agents/runtime.py`
|
||||
- adiciona `_collect_mcp_context()` centralizado;
|
||||
- repassa `business_context` e `original_context` para o MCP Router.
|
||||
|
||||
- `agent_template_backend/app/agents/*_agent.py`
|
||||
- agentes passam a usar `_collect_mcp_context()` em vez de montar argumentos específicos.
|
||||
|
||||
- `agent_template_backend/config/identity.yaml`
|
||||
- define como campos do canal/front-end alimentam as chaves canônicas.
|
||||
|
||||
- `agent_template_backend/config/mcp_parameter_mapping.yaml`
|
||||
- define como chaves canônicas viram parâmetros reais por tool MCP.
|
||||
|
||||
- `agent_frontend/index.html` e `agent_frontend/app.js`
|
||||
- adicionam campos de `tenant`, `agent` e chaves canônicas;
|
||||
- enviam `business_context` no payload;
|
||||
- mantêm aliases de domínio para compatibilidade (`msisdn`, `invoice_id`, `order_id`, etc.).
|
||||
|
||||
## Teste rápido
|
||||
|
||||
Suba backend, frontend e MCP servers. Depois teste:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8000/health | jq
|
||||
|
||||
curl -s -X POST http://localhost:8000/debug/identity \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel":"web",
|
||||
"tenant_id":"default",
|
||||
"agent_id":"telecom_contas",
|
||||
"payload":{
|
||||
"message":"Minha fatura veio alta",
|
||||
"session_id":"teste-001",
|
||||
"msisdn":"11999999999",
|
||||
"invoice_id":"3000131180",
|
||||
"ura_call_id":"URA-123",
|
||||
"business_context":{
|
||||
"customer_key":"11999999999",
|
||||
"contract_key":"3000131180",
|
||||
"interaction_key":"URA-123",
|
||||
"session_key":"teste-001"
|
||||
}
|
||||
}
|
||||
}' | jq
|
||||
|
||||
curl -s -X POST http://localhost:8000/debug/mcp/call/consultar_fatura \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"business_context": {
|
||||
"customer_key":"11999999999",
|
||||
"contract_key":"3000131180",
|
||||
"interaction_key":"URA-123",
|
||||
"session_key":"teste-001"
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
No log do backend, procure por `mcp.tool.mapped`. Ele deve indicar as chaves mapeadas e `has_msisdn=true`, `has_invoice_id=true` para o domínio telecom.
|
||||
28
agent_framework_oci/Documentacao/README_TESTES_UNITARIOS.md
Normal file
28
agent_framework_oci/Documentacao/README_TESTES_UNITARIOS.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Testes unitários do framework
|
||||
|
||||
Esta versão inclui uma pasta `tests/unit` cobrindo os componentes principais:
|
||||
|
||||
- cache local e distribuído;
|
||||
- SSE com encode, persistência e replay;
|
||||
- RAG com busca vetorial em memória;
|
||||
- checkpoint saver compatível com LangGraph;
|
||||
- telemetria profunda de LangGraph;
|
||||
- runtime dos agentes com cache/RAG;
|
||||
- verificação estática do workflow para garantir que não usa mais `MemorySaver()` diretamente.
|
||||
|
||||
## Como executar
|
||||
|
||||
```bash
|
||||
cd projeto_agent_framework_first_ready
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r agent_template_backend/requirements.txt
|
||||
pip install pytest pytest-asyncio
|
||||
pytest -q
|
||||
```
|
||||
|
||||
Para rodar apenas os testes unitários:
|
||||
|
||||
```bash
|
||||
pytest -q tests/unit
|
||||
```
|
||||
90
agent_framework_oci/Documentacao/README_TOOL_POLICIES.md
Normal file
90
agent_framework_oci/Documentacao/README_TOOL_POLICIES.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Políticas mínimas para tools MCP read-only e transacionais
|
||||
|
||||
## Objetivo
|
||||
|
||||
O framework diferencia operações de consulta (`read_only`) e operações que alteram estado (`transactional`) imediatamente antes da chamada MCP. Essa classificação não substitui autorização, idempotência ou regras de negócio do servidor MCP; ela acrescenta somente a proteção conversacional mínima, especialmente confirmação explícita.
|
||||
|
||||
## Onde configurar
|
||||
|
||||
A parametrização pertence ao backend da aplicação:
|
||||
|
||||
```text
|
||||
templates/agent_template_backend/config/tool_policies.yaml
|
||||
```
|
||||
|
||||
A biblioteca compartilhada contém apenas o loader e a validação. O caminho é opcional:
|
||||
|
||||
```dotenv
|
||||
TOOL_POLICIES_PATH=./config/tool_policies.yaml
|
||||
```
|
||||
|
||||
## Exemplo
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
|
||||
defaults:
|
||||
operation_type: read_only
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
consultar_plano:
|
||||
operation_type: read_only
|
||||
|
||||
alterar_plano:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [new_plan_id]
|
||||
```
|
||||
|
||||
Para executar `alterar_plano`, os argumentos precisam conter `new_plan_id` e um booleano literal de confirmação:
|
||||
|
||||
```json
|
||||
{"new_plan_id": "CONTROLE_100", "confirmed": true}
|
||||
```
|
||||
|
||||
Também é aceito `"confirmation": true`. Strings como `"true"` não são aceitas como confirmação.
|
||||
|
||||
## Compatibilidade
|
||||
|
||||
- Se `tool_policies.yaml` não existir, o framework continua usando `tool_type`, `requires`, `confirmation_required` e `execution_policy` de `tools.yaml`.
|
||||
- Tools antigas sem política continuam executando como antes.
|
||||
- Uma política explícita no arquivo novo prevalece para `operation_type` e confirmação daquela tool.
|
||||
- O catálogo `tools.yaml` continua sendo a fonte de endpoint, schema, habilitação e cache.
|
||||
- O novo arquivo não deve ser colocado em `libs/agent_framework`, pois as decisões variam por aplicação e domínio.
|
||||
|
||||
## Fluxo de execução
|
||||
|
||||
```text
|
||||
agente -> MCPToolRouter -> validação da política -> mapeamento de parâmetros -> MCP Gateway/Server
|
||||
```
|
||||
|
||||
Uma chamada bloqueada retorna `ok=false`, `metadata.blocked_by_policy=true`, o tipo da operação e a origem da política. O servidor MCP permanece a autoridade final para autenticação, autorização, validação, idempotência e transação de negócio.
|
||||
|
||||
## Migração recomendada
|
||||
|
||||
1. Atualize a biblioteca sem criar o arquivo: o comportamento permanece legado.
|
||||
2. Crie `config/tool_policies.yaml` no backend.
|
||||
3. Cadastre primeiro apenas operações transacionais que exigem confirmação.
|
||||
4. Teste chamadas sem confirmação, com confirmação booleana e com campos obrigatórios ausentes.
|
||||
5. Remova gradualmente duplicações de confirmação de `tools.yaml` quando todos os templates consumidores já usarem a nova configuração.
|
||||
|
||||
|
||||
## Runtime transacional mínimo (correção de amarração)
|
||||
|
||||
A lista `mcp_tools` do roteamento é uma **allowlist**, não uma ordem para executar todas as ferramentas. O runtime agora:
|
||||
|
||||
1. executa automaticamente somente ferramentas `read_only`;
|
||||
2. seleciona no máximo uma ação transacional compatível com o pedido do usuário;
|
||||
3. quando `require_confirmation: true`, persiste `pending_tool_call` e `transaction_status: AWAITING_CONFIRMATION`;
|
||||
4. no turno de confirmação, reutiliza a mesma chamada e executa com `confirmed: true`;
|
||||
5. publica no estado `available_mcp_tools`, `selected_tool_call`, `tool_policy_result`, `confirmation_required` e `confirmation_received`.
|
||||
|
||||
Para o cenário de exemplo, o pedido `123` (ou `PED-ENTREGUE`) retorna `ENTREGUE` no MCP Retail. Use:
|
||||
|
||||
```text
|
||||
Quero devolver o pedido 123 porque me arrependi da compra.
|
||||
Sim, confirmo a devolução.
|
||||
```
|
||||
|
||||
O contrato MCP foi padronizado para usar `reason` tanto no catálogo quanto no servidor FastMCP. `tool_policies.yaml` prevalece sobre os campos legados de `tools.yaml`; estes permanecem alinhados nos templates para compatibilidade.
|
||||
113
agent_framework_oci/Documentacao/README_old.md
Normal file
113
agent_framework_oci/Documentacao/README_old.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# AI Agent Platform — LangGraph + OCI
|
||||
|
||||
Monorepo com três projetos independentes:
|
||||
|
||||
- `agent_framework/`: biblioteca reutilizável para agentes escaláveis.
|
||||
- `agent_template_backend/`: backend FastAPI usando o framework, com dois agentes, roteador, máquina de estados, sessão persistente e gateway de canais.
|
||||
- `agent_frontend/`: frontend web simples e independente para conversar com o backend via gateway HTTP.
|
||||
|
||||
## Visão de arquitetura
|
||||
|
||||
```text
|
||||
Frontend Web / WhatsApp / Voz / Texto
|
||||
↓
|
||||
Channel Gateway + Adapters
|
||||
↓
|
||||
SessionRepository persistente
|
||||
↓
|
||||
Supervisor / Router Agent
|
||||
↓
|
||||
LangGraph StateGraph
|
||||
↓
|
||||
Agent A Agent B
|
||||
↓ ↓
|
||||
Guardrails → LLM OCI Generative AI → Output Guardrails → Judges
|
||||
↓
|
||||
Memory / RAG / Vector / Graph / Telemetry / Streaming
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Quickstart local
|
||||
|
||||
Suba a estrutura de Langfuse, MongoDB, REDIS para seu ambiente de desenvolvimento:
|
||||
|
||||
Vá até o folder ./agent_framework/Infrastructure_Langfuse/, onde existe o docker-compose.yml e execute:
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
O langfuse estará em:
|
||||
|
||||
```bash
|
||||
http://localhost:3005
|
||||
```
|
||||
Crie sua Organização e seu projeto
|
||||
|
||||

|
||||
|
||||
Será criado também um MongoDB e um REDIS, logo seu .env terá a configuração para apontar para estes recursos conteinerizados.
|
||||
Você pode também apontar para um banco de dados Autonomous Oracle, basta configurar no arquivo .env.
|
||||
|
||||
Depois compile do Agent Framework dentro do agent_template_backend (agente template que se utiliza do Framework):
|
||||
|
||||
Obs: configure o arquivo .env.
|
||||
|
||||
Terminal 1:
|
||||
|
||||
```bash
|
||||
cd agent_framework_oci
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
cd agent_template_backend
|
||||
pip install -e ../agent_framework
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --reload --reload-dir app --reload-dir config --port 8000
|
||||
```
|
||||
|
||||
Terminal 2:
|
||||
|
||||
```bash
|
||||
cd agent_framework_oci
|
||||
bash ./scripts/run_mcp_servers.sh
|
||||
```
|
||||
|
||||
Terminal 3:
|
||||
|
||||
```bash
|
||||
cd agent_framework_oci
|
||||
cd agent_frontend
|
||||
python -m http.server 5173
|
||||
```
|
||||
|
||||
Abra `http://localhost:5173`.
|
||||
|
||||
## OCI LLM
|
||||
|
||||
Configure no `.env`:
|
||||
|
||||
```env
|
||||
LLM_PROVIDER=oci_openai
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com/openai/v1
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=...
|
||||
```
|
||||
|
||||
Para rodar sem credenciais, use:
|
||||
|
||||
```env
|
||||
LLM_PROVIDER=mock
|
||||
```
|
||||
|
||||
## Estado do projeto
|
||||
|
||||
Este é um template de referência funcional/simulável. Conectores reais de Autonomous Database, MongoDB, Redis, Langfuse, OCI Streaming e OCI GenAI estão isolados por interfaces/adapters para facilitar evolução e deploy.
|
||||
|
||||
## Enterprise Routing Edition
|
||||
|
||||
Esta versão também possui `README_ENTERPRISE_ROUTING.md`, com detalhes sobre roteamento por estado, intents configuráveis, LLM Router opcional e dois templates de exemplo.
|
||||
|
||||
## Multi-agent isolation
|
||||
|
||||
Esta distribuição inclui suporte para múltiplos `agent_template` no mesmo backend.
|
||||
Consulte `README_MULTI_AGENT_ISOLATION.md`.
|
||||
1545
agent_framework_oci/Documentacao/README_old2.md
Normal file
1545
agent_framework_oci/Documentacao/README_old2.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
# Correção da extração de parâmetros MCP
|
||||
|
||||
## Problema corrigido
|
||||
|
||||
O bloco `extract` do `mcp_parameter_mapping.yaml` existia na configuração e na
|
||||
documentação, mas não era executado pelo runtime. Além disso, valores do
|
||||
Business Context podiam sobrescrever argumentos explícitos, fazendo
|
||||
`contract_key` substituir o `order_id` informado pelo usuário.
|
||||
|
||||
## Correções
|
||||
|
||||
- implementação da extração genérica `strategy: llm` após a escolha da tool;
|
||||
- suporte preservado para `strategy: month_name_pt`;
|
||||
- profile dedicado `mcp_parameter_extraction`;
|
||||
- telemetria `llm.mcp_parameter_extraction`;
|
||||
- `extract` deixou de ser interpretado como mapeamento simples;
|
||||
- argumentos explícitos/extraídos têm precedência sobre Business Context;
|
||||
- remoção de `contract_key: order_id` dos templates;
|
||||
- `order_id` configurado como `string`;
|
||||
- atualização das variantes em `Tuning-Performance`.
|
||||
|
||||
## Resultado esperado
|
||||
|
||||
Para a mensagem `consultar pedido 123`, a chamada MCP deve receber
|
||||
`order_id=123`, mesmo quando o Business Context contém outro `contract_key`.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Correção — mudança de consulta para ação transacional
|
||||
|
||||
## Problema
|
||||
|
||||
Após `consultar pedido 123`, a mensagem `Quero devolver o pedido 123` podia permanecer no `orders_agent` por route stickiness. Como a intent anterior só expunha tools de consulta, o runtime executava novamente `consultar_pedido` e a resposta direta repetia o status do pedido.
|
||||
|
||||
## Correções
|
||||
|
||||
- Keywords explícitas configuradas no `routing.yaml` podem preemptar a route stickiness quando apontam para outra intent/agente.
|
||||
- `retail_support_exchange_return` passa a ter prioridade maior que `retail_order_tracking` para mensagens de troca/devolução.
|
||||
- Tools transacionais declaram `selection_keywords` no `tools.yaml`.
|
||||
- A resposta direta read-only é bloqueada quando a mensagem contém uma ação transacional registrada, mesmo que a intent anterior ainda esteja ativa.
|
||||
- A seleção da action tool usa configuração, não aliases de domínio fixos no runtime.
|
||||
|
||||
## Fluxo esperado
|
||||
|
||||
1. `consultar pedido 123` → `orders_agent` → `consultar_pedido` → resposta direta.
|
||||
2. `Quero devolver o pedido 123` → preempção da stickiness → `support_agent` / `retail_support_exchange_return`.
|
||||
3. `consultar_pedido` valida o pedido.
|
||||
4. `solicitar_devolucao` é selecionada e, com confirmação obrigatória, gera `AWAITING_CONFIRMATION`.
|
||||
5. `Sim, confirmo` executa a action tool uma única vez.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Release notes - políticas read-only/transacionais
|
||||
|
||||
## Alterações
|
||||
|
||||
- Novo `ToolPolicyRegistry` opcional na biblioteca compartilhada.
|
||||
- Validação central no `MCPToolRouter`, inclusive para chamadas diretas.
|
||||
- Tipos mínimos `read_only` e `transactional`.
|
||||
- Confirmação estrita por `confirmed: true` ou `confirmation: true`.
|
||||
- Suporte opcional a campos obrigatórios por política.
|
||||
- Fallback automático para `tool_type`, `requires`, `confirmation_required` e `execution_policy` de `tools.yaml`.
|
||||
- `config/tool_policies.yaml` e variável `TOOL_POLICIES_PATH` nos templates principais, Day Zero e variantes de `Tuning-Performance/Normal` e `Tuning-Performance/Route_Stickness`.
|
||||
- Testes unitários de política e compatibilidade adicionados em `tests/unit/test_tool_policies.py`.
|
||||
|
||||
## Verificações executadas
|
||||
|
||||
- Compilação de `libs`, `templates`, `Tuning-Performance` e `tests`: aprovada.
|
||||
- Validação estrutural dos seis arquivos YAML: aprovada.
|
||||
- Casos isolados do loader (política transacional, confirmação, ausência de arquivo e ausência de cadastro): aprovados.
|
||||
- Renderização dos dois manuais Word atualizados: aprovada, sem cortes ou sobreposição nas páginas adicionadas.
|
||||
|
||||
## Limitação do ambiente de validação
|
||||
|
||||
A suíte `pytest` foi preparada, mas não pôde ser executada integralmente neste ambiente porque `pytest` e as dependências de runtime do projeto não estavam instalados e o acesso ao índice de pacotes expirou. Para reproduzir em um ambiente do projeto:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=libs/agent_framework/src:templates/agent_template_backend python -m pytest -q
|
||||
```
|
||||
|
||||
## Correção de integração backend/MCP
|
||||
- `mcp_tools` passou a ser tratado como allowlist.
|
||||
- Ações não são mais executadas automaticamente junto com consultas.
|
||||
- Confirmação transacional é persistida e retomada no turno seguinte.
|
||||
- Corrigida incompatibilidade `reason`/`motivo` no MCP Retail.
|
||||
- Adicionado pedido entregue determinístico para testes (`123`).
|
||||
- Removida keyword genérica `produto` da intenção Telecom para evitar colisão com devoluções Retail.
|
||||
- Templates Normal e Route_Stickness em `Tuning-Performance` foram sincronizados.
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,35 @@
|
||||
# Test Results - Semantic Route Stickiness and Global Session Control
|
||||
|
||||
Date: 2026-07-31
|
||||
|
||||
## Command
|
||||
|
||||
```bash
|
||||
PYTHONPATH=libs/agent_framework/src pytest -q tests/unit/test_semantic_route_stickiness.py
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
```text
|
||||
9 passed
|
||||
```
|
||||
|
||||
## Covered scenarios
|
||||
|
||||
1. `CONTINUE` bypasses the Enterprise Router.
|
||||
2. `ROUTE` falls back to the Enterprise Router.
|
||||
3. Low-confidence `CONTINUE` falls back safely.
|
||||
4. Invalid model output falls back safely.
|
||||
5. With no active agent, the lightweight classifier can still detect global session actions.
|
||||
6. `HUMAN_HANDOFF` returns the global `human_handoff` route and session-control metadata.
|
||||
7. `END_SESSION` returns the global `end_session` route and session-control metadata.
|
||||
8. Global actions work on the first turn.
|
||||
9. `CONTINUE` without an active agent is normalized to `ROUTE`.
|
||||
|
||||
## Additional validation
|
||||
|
||||
```bash
|
||||
python -m compileall -q libs/agent_framework/src templates/agent_template_backend/app
|
||||
```
|
||||
|
||||
Compilation completed successfully.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Validação — integração transacional Agent Template Backend / MCP
|
||||
|
||||
## Correções implementadas
|
||||
|
||||
- `mcp_tools` é tratado como allowlist, não como lista de execução automática.
|
||||
- Tools `read_only` continuam disponíveis para enriquecimento de contexto.
|
||||
- Somente uma tool transacional compatível com a solicitação é selecionada.
|
||||
- `require_confirmation: true` cria `pending_tool_call` e `AWAITING_CONFIRMATION`.
|
||||
- O turno de confirmação executa a chamada pendente com `confirmed: true`.
|
||||
- O estado expõe `selected_tool_call`, `tool_policy_result`, `confirmation_required`, `confirmation_received` e `transaction_status`.
|
||||
- `reason` foi padronizado entre catálogo, mapping e FastMCP Retail.
|
||||
- Pedido `123` e `PED-ENTREGUE` retornam status `ENTREGUE` para testes positivos.
|
||||
- A keyword genérica `produto` foi removida da intenção Telecom para não capturar devoluções Retail.
|
||||
- Templates `Normal` e `Route_Stickness` em `Tuning-Performance` foram atualizados.
|
||||
|
||||
## Teste recomendado
|
||||
|
||||
1. `Quero devolver o pedido 123 porque me arrependi da compra.`
|
||||
2. Esperado: `transaction_status=AWAITING_CONFIRMATION`, sem execução de `solicitar_devolucao`.
|
||||
3. `Sim, confirmo a devolução.`
|
||||
4. Esperado: `transaction_status=COMPLETED` e execução única de `solicitar_devolucao`.
|
||||
|
||||
## Resultado automatizado
|
||||
|
||||
```text
|
||||
7 passed
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
VALIDAÇÃO - GLOBAL SUPERVISOR
|
||||
|
||||
Alterações implementadas:
|
||||
|
||||
1. Framework
|
||||
- agent_framework.global_supervisor.models
|
||||
- agent_framework.global_supervisor.config
|
||||
- agent_framework.global_supervisor.session_store
|
||||
- agent_framework.global_supervisor.router
|
||||
- agent_framework.global_supervisor.client
|
||||
|
||||
2. Novo serviço
|
||||
- agent_gateway/app/main.py
|
||||
- agent_gateway/app/settings.py
|
||||
- agent_gateway/config/backends.yaml
|
||||
- agent_gateway/README.md
|
||||
- agent_gateway/Dockerfile
|
||||
- agent_gateway/docs/ARQUITETURA_GLOBAL_SUPERVISOR.md
|
||||
|
||||
3. Docker Compose
|
||||
- serviço agent-gateway adicionado na porta 8010.
|
||||
|
||||
Validações executadas:
|
||||
|
||||
- python3 -m compileall -q agent_framework/src/agent_framework/global_supervisor agent_gateway/app
|
||||
Resultado: OK
|
||||
|
||||
- Smoke test do roteamento híbrido:
|
||||
Entrada 1: "Minha fatura veio alta" -> contas
|
||||
Entrada 2: "e esse valor?" na mesma session_id -> contas por active_backend
|
||||
Resultado: OK
|
||||
|
||||
- Smoke test de import do app FastAPI:
|
||||
from app.main import app, registry, router
|
||||
Resultado: OK
|
||||
|
||||
Observação:
|
||||
- O proxy SSE do gateway foi deixado como etapa futura. O endpoint /gateway/message/sse já roteia e encaminha como mensagem normal; para SSE fim-a-fim, pode-se implementar proxy de /gateway/events/{session_id} para o backend ativo.
|
||||
@@ -0,0 +1,5 @@
|
||||
VALIDATION REPORT - guardrails parallel fail-fast + observer IC
|
||||
Date: 2026-06-03
|
||||
|
||||
compileall: OK
|
||||
smoke-tests: OK
|
||||
BIN
agent_framework_oci/Documentacao/img.png
Normal file
BIN
agent_framework_oci/Documentacao/img.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 186 KiB |
64
agent_framework_oci/IMPLEMENTACAO_WORKFLOWS_TRANSACIONAIS.md
Normal file
64
agent_framework_oci/IMPLEMENTACAO_WORKFLOWS_TRANSACIONAIS.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Implementação — workflows transacionais determinísticos
|
||||
|
||||
## Entrega
|
||||
|
||||
Foi adicionada ao `agent_framework_oci` uma capacidade opcional para executar transações multi-etapas como workflows determinísticos compilados em LangGraph.
|
||||
|
||||
### Módulo novo
|
||||
|
||||
`libs/agent_framework/src/agent_framework/workflows/`
|
||||
|
||||
- `models.py`: contratos Pydantic e validação estrutural;
|
||||
- `repository.py`: resolução de versão ativa e leitura de YAML imutável;
|
||||
- `registry.py`: registro desacoplado de actions sync/async;
|
||||
- `runtime.py`: compilação, cache e execução do StateGraph;
|
||||
- `tool_executor.py`: integração com a política da tool;
|
||||
- `__init__.py`: API pública.
|
||||
|
||||
### Política expandida
|
||||
|
||||
`ToolPolicy` agora aceita:
|
||||
|
||||
```yaml
|
||||
execution:
|
||||
mode: direct_tool | workflow | agent
|
||||
workflow: nome_do_workflow
|
||||
version: active | 1
|
||||
```
|
||||
|
||||
O default permanece `direct_tool`, preservando compatibilidade.
|
||||
|
||||
### Configuração
|
||||
|
||||
Foram adicionados:
|
||||
|
||||
- `ENABLE_TRANSACTIONAL_WORKFLOWS=false`;
|
||||
- `WORKFLOWS_PATH=./workflows`.
|
||||
|
||||
### Template
|
||||
|
||||
Inclui um exemplo completo de devolução de pedido com:
|
||||
|
||||
- confirmação e campos obrigatórios pela política;
|
||||
- workflow YAML versionado;
|
||||
- actions de domínio no backend;
|
||||
- bifurcação determinística baseada no resultado da validação.
|
||||
|
||||
## Validação realizada
|
||||
|
||||
- `tests/unit/test_tool_policies.py`: 4 testes aprovados;
|
||||
- compilação Python de framework, template e novos testes: aprovada;
|
||||
- o teste funcional novo do LangGraph foi criado, mas não pôde ser executado neste container porque `langgraph` não está instalado no ambiente. A dependência já está declarada no `pyproject.toml` do framework.
|
||||
|
||||
## Escopo e segurança
|
||||
|
||||
Esta entrega cria o motor e a integração de política. Para operações críticas em produção ainda é necessário conectar:
|
||||
|
||||
- execution store persistente;
|
||||
- idempotência de negócio nas actions/APIs;
|
||||
- autorização por escopo;
|
||||
- telemetria IC/NOC específica de workflow;
|
||||
- compensação/Saga quando aplicável;
|
||||
- estratégia corporativa de timeout e retry.
|
||||
|
||||
Esses itens foram explicitamente documentados para evitar a falsa impressão de que retry por si só garante segurança transacional.
|
||||
987
agent_framework_oci/Implementando_Basic_Auth.md
Normal file
987
agent_framework_oci/Implementando_Basic_Auth.md
Normal file
@@ -0,0 +1,987 @@
|
||||
# Implementando Basic Auth
|
||||
|
||||
Para validar **todo o circuito com Basic Auth**, você precisa configurar três relações distintas:
|
||||
|
||||
```text
|
||||
Cliente de teste
|
||||
└─ Basic Auth A ─► Agent Gateway :8010
|
||||
└─ Basic Auth B ─► Agent Backend :8000
|
||||
└─ Basic Auth C ─► MCP Gateway :8300
|
||||
```
|
||||
|
||||
Há um detalhe importante: no pacote atual, a autenticação Basic já funciona para chamadas **de entrada**, mas os clientes internos ainda não enviam Basic Auth:
|
||||
|
||||
* `Agent Gateway → Agent Backend` não envia credencial;
|
||||
* `Agent Backend → MCP Gateway` envia apenas Bearer Token.
|
||||
|
||||
Portanto, para testar o circuito inteiro com Basic Auth, faça os dois pequenos ajustes de código descritos abaixo.
|
||||
|
||||
---
|
||||
|
||||
# 1. Preparar o ambiente
|
||||
|
||||
Considere que o ZIP foi extraído em:
|
||||
|
||||
```bash
|
||||
cd agent_framework_oci_authentication_v2_1
|
||||
```
|
||||
|
||||
Crie um único ambiente virtual para facilitar o teste:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
No Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
Instale o framework e as dependências dos três componentes:
|
||||
|
||||
```bash
|
||||
pip install -U pip
|
||||
|
||||
pip install -e ./libs/agent_framework
|
||||
|
||||
pip install \
|
||||
-r ./Tuning-Performance/Authentication/agent_template_backend_authentication/requirements.txt \
|
||||
-r ./apps/agent_gateway/requirements.txt \
|
||||
-r ./apps/mcp_gateway/requirements.txt
|
||||
```
|
||||
|
||||
Confirme a importação:
|
||||
|
||||
```bash
|
||||
python -c "from agent_framework.security import install_authentication; print('framework ok')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 2. Criar três pares de Client ID e Secret
|
||||
|
||||
Use credenciais diferentes para cada trecho. Para teste local:
|
||||
|
||||
| Fluxo | Client ID | Secret de teste |
|
||||
| ----------------------- | -------------------- | --------------------------- |
|
||||
| Cliente → Agent Gateway | `tia-test` | `TiaGateway-Test-2026!` |
|
||||
| Agent Gateway → Backend | `agent-gateway-test` | `GatewayBackend-Test-2026!` |
|
||||
| Backend → MCP Gateway | `agent-backend-test` | `BackendMcp-Test-2026!` |
|
||||
|
||||
Esses valores são apenas para ambiente local. Não os reutilize em produção.
|
||||
|
||||
## Gerar os hashes
|
||||
|
||||
O script está em:
|
||||
|
||||
```text
|
||||
Tuning-Performance/Authentication/
|
||||
agent_template_backend_authentication/
|
||||
scripts/generate_secret_hash.py
|
||||
```
|
||||
|
||||
Execute:
|
||||
|
||||
```bash
|
||||
python Tuning-Performance/Authentication/agent_template_backend_authentication/scripts/generate_secret_hash.py \
|
||||
--secret 'TiaGateway-Test-2026!'
|
||||
```
|
||||
|
||||
Depois:
|
||||
|
||||
```bash
|
||||
python Tuning-Performance/Authentication/agent_template_backend_authentication/scripts/generate_secret_hash.py \
|
||||
--secret 'GatewayBackend-Test-2026!'
|
||||
```
|
||||
|
||||
E:
|
||||
|
||||
```bash
|
||||
python Tuning-Performance/Authentication/agent_template_backend_authentication/scripts/generate_secret_hash.py \
|
||||
--secret 'BackendMcp-Test-2026!'
|
||||
```
|
||||
|
||||
Você receberá três valores semelhantes a:
|
||||
|
||||
```text
|
||||
pbkdf2_sha256:310000:<salt>:<digest>
|
||||
```
|
||||
|
||||
Guarde-os temporariamente:
|
||||
|
||||
```bash
|
||||
HASH_CLIENT_GATEWAY='pbkdf2_sha256:310000:...'
|
||||
HASH_GATEWAY_BACKEND='pbkdf2_sha256:310000:...'
|
||||
HASH_BACKEND_MCP='pbkdf2_sha256:310000:...'
|
||||
```
|
||||
|
||||
O hash muda a cada execução porque o salt é aleatório. Isso é esperado.
|
||||
|
||||
---
|
||||
|
||||
# 3. Configurar o Agent Gateway
|
||||
|
||||
Entre no diretório:
|
||||
|
||||
```bash
|
||||
cd apps/agent_gateway
|
||||
```
|
||||
|
||||
Copie o exemplo:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Adicione ao final do `.env`:
|
||||
|
||||
```env
|
||||
# Entrada: cliente/TIA -> Agent Gateway
|
||||
AGENT_GATEWAY_AUTH_ENABLED=true
|
||||
AGENT_GATEWAY_AUTH_MODE=basic
|
||||
AGENT_GATEWAY_AUTH_BASIC_CLIENT_ID=tia-test
|
||||
AGENT_GATEWAY_AUTH_BASIC_SECRET_HASH=COLE_AQUI_HASH_CLIENT_GATEWAY
|
||||
AGENT_GATEWAY_AUTH_BASIC_REALM=agent-gateway
|
||||
|
||||
AGENT_GATEWAY_AUTH_PUBLIC_PATHS=/health,/docs,/openapi.json,/redoc
|
||||
AGENT_GATEWAY_AUTH_PUBLIC_PREFIXES=
|
||||
|
||||
# Saída: Agent Gateway -> Agent Backend
|
||||
BACKEND_AUTH_MODE=basic
|
||||
BACKEND_AUTH_CLIENT_ID=agent-gateway-test
|
||||
BACKEND_AUTH_SECRET=GatewayBackend-Test-2026!
|
||||
```
|
||||
|
||||
Não coloque aspas no `.env`:
|
||||
|
||||
```env
|
||||
BACKEND_AUTH_SECRET=GatewayBackend-Test-2026!
|
||||
```
|
||||
|
||||
O arquivo de backends já aponta o backend Contas para:
|
||||
|
||||
```yaml
|
||||
contas:
|
||||
url: http://localhost:8000
|
||||
```
|
||||
|
||||
Arquivo:
|
||||
|
||||
```text
|
||||
apps/agent_gateway/config/backends.yaml
|
||||
```
|
||||
|
||||
Para este teste, mantenha apenas o backend `contas` ou force o backend no payload. Caso contrário, pedidos sobre ofertas e suporte podem ser roteados para portas em que nenhum backend está rodando.
|
||||
|
||||
---
|
||||
|
||||
# 4. Fazer o Agent Gateway enviar Basic Auth ao backend
|
||||
|
||||
Abra:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/global_supervisor/client.py
|
||||
```
|
||||
|
||||
Substitua a classe `BackendClient` por uma versão que aceite autenticação Basic.
|
||||
|
||||
No início do arquivo, adicione:
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
Altere o construtor:
|
||||
|
||||
```python
|
||||
class BackendClient:
|
||||
def __init__(
|
||||
self,
|
||||
timeout_seconds: float = 120.0,
|
||||
basic_client_id: str | None = None,
|
||||
basic_secret: str | None = None,
|
||||
):
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.basic_client_id = basic_client_id
|
||||
self.basic_secret = basic_secret
|
||||
|
||||
def _auth(self) -> httpx.BasicAuth | None:
|
||||
if self.basic_client_id and self.basic_secret:
|
||||
return httpx.BasicAuth(
|
||||
username=self.basic_client_id,
|
||||
password=self.basic_secret,
|
||||
)
|
||||
return None
|
||||
```
|
||||
|
||||
No método `call_message`, troque:
|
||||
|
||||
```python
|
||||
resp = await client.post(url, json=payload)
|
||||
```
|
||||
|
||||
por:
|
||||
|
||||
```python
|
||||
resp = await client.post(
|
||||
url,
|
||||
json=payload,
|
||||
auth=self._auth(),
|
||||
)
|
||||
```
|
||||
|
||||
No método `health`, você pode manter `/health` público. Caso queira enviar autenticação também, use:
|
||||
|
||||
```python
|
||||
resp = await client.get(url, auth=self._auth())
|
||||
```
|
||||
|
||||
Agora abra:
|
||||
|
||||
```text
|
||||
apps/agent_gateway/app/main.py
|
||||
```
|
||||
|
||||
Adicione:
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
Troque:
|
||||
|
||||
```python
|
||||
backend_client = BackendClient(
|
||||
timeout_seconds=settings.BACKEND_TIMEOUT_SECONDS
|
||||
)
|
||||
```
|
||||
|
||||
por:
|
||||
|
||||
```python
|
||||
backend_client = BackendClient(
|
||||
timeout_seconds=settings.BACKEND_TIMEOUT_SECONDS,
|
||||
basic_client_id=os.getenv("BACKEND_AUTH_CLIENT_ID"),
|
||||
basic_secret=os.getenv("BACKEND_AUTH_SECRET"),
|
||||
)
|
||||
```
|
||||
|
||||
Isso implementa:
|
||||
|
||||
```text
|
||||
Agent Gateway → Agent Backend
|
||||
Authorization: Basic base64(agent-gateway-test:GatewayBackend-Test-2026!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 5. Configurar o Agent Backend autenticado
|
||||
|
||||
Entre no diretório:
|
||||
|
||||
```bash
|
||||
cd Tuning-Performance/Authentication/agent_template_backend_authentication
|
||||
```
|
||||
|
||||
Copie o exemplo:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Ajuste a seção de autenticação:
|
||||
|
||||
```env
|
||||
# Entrada: Agent Gateway -> Agent Backend
|
||||
AGENT_AUTH_ENABLED=true
|
||||
AGENT_AUTH_MODE=basic
|
||||
AGENT_AUTH_BASIC_CLIENT_ID=agent-gateway-test
|
||||
AGENT_AUTH_BASIC_SECRET_HASH=COLE_AQUI_HASH_GATEWAY_BACKEND
|
||||
AGENT_AUTH_BASIC_REALM=agent-contas
|
||||
|
||||
AGENT_AUTH_PUBLIC_PATHS=/health,/docs,/openapi.json,/redoc
|
||||
AGENT_AUTH_PUBLIC_PREFIXES=
|
||||
```
|
||||
|
||||
Para usar o MCP Gateway:
|
||||
|
||||
```env
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
|
||||
# Saída: Agent Backend -> MCP Gateway
|
||||
MCP_GATEWAY_AUTH_MODE=basic
|
||||
MCP_GATEWAY_BASIC_CLIENT_ID=agent-backend-test
|
||||
MCP_GATEWAY_BASIC_SECRET=BackendMcp-Test-2026!
|
||||
```
|
||||
|
||||
Para evitar dependências externas durante o primeiro teste, configure também:
|
||||
|
||||
```env
|
||||
LLM_PROVIDER=mock
|
||||
ENABLE_LANGFUSE=false
|
||||
ENABLE_ANALYTICS=false
|
||||
|
||||
SESSION_REPOSITORY_PROVIDER=memory
|
||||
MEMORY_REPOSITORY_PROVIDER=memory
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=memory
|
||||
CACHE_PROVIDER=memory
|
||||
USAGE_REPOSITORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
Os nomes exatos de alguns providers podem depender do arquivo de configuração atual do framework. Caso o `.env.example` já contenha valores locais ou mock, preserve-os.
|
||||
|
||||
---
|
||||
|
||||
# 6. Fazer o Backend enviar Basic Auth ao MCP Gateway
|
||||
|
||||
Abra:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/gateways/mcp_gateway_client.py
|
||||
```
|
||||
|
||||
Substitua a implementação por:
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class MCPGatewayClient:
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
token: str | None = None,
|
||||
timeout_seconds: int = 60,
|
||||
auth_mode: str | None = None,
|
||||
basic_client_id: str | None = None,
|
||||
basic_secret: str | None = None,
|
||||
):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.token = token
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.auth_mode = (auth_mode or "").strip().lower()
|
||||
self.basic_client_id = basic_client_id
|
||||
self.basic_secret = basic_secret
|
||||
|
||||
def _headers(self) -> dict[str, str]:
|
||||
if (
|
||||
self.auth_mode == "basic"
|
||||
and self.basic_client_id
|
||||
and self.basic_secret
|
||||
):
|
||||
raw = f"{self.basic_client_id}:{self.basic_secret}".encode("utf-8")
|
||||
encoded = base64.b64encode(raw).decode("ascii")
|
||||
return {"Authorization": f"Basic {encoded}"}
|
||||
|
||||
if self.token:
|
||||
return {"Authorization": f"Bearer {self.token}"}
|
||||
|
||||
return {}
|
||||
|
||||
async def list_tools(self) -> dict[str, Any]:
|
||||
async with httpx.AsyncClient(
|
||||
timeout=self.timeout_seconds
|
||||
) as client:
|
||||
response = await client.get(
|
||||
f"{self.base_url}/v1/tools",
|
||||
headers=self._headers(),
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def invoke_tool(
|
||||
self,
|
||||
*,
|
||||
tenant_id: str,
|
||||
agent_id: str,
|
||||
channel: str | None,
|
||||
tool_name: str,
|
||||
arguments: dict[str, Any] | None = None,
|
||||
business_context: dict[str, Any] | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
payload = {
|
||||
"tenant_id": tenant_id,
|
||||
"agent_id": agent_id,
|
||||
"channel": channel,
|
||||
"tool_name": tool_name,
|
||||
"arguments": arguments or {},
|
||||
"business_context": business_context or {},
|
||||
"metadata": metadata or {},
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
timeout=self.timeout_seconds
|
||||
) as client:
|
||||
response = await client.post(
|
||||
f"{self.base_url}/v1/tools/{tool_name}/invoke",
|
||||
json=payload,
|
||||
headers=self._headers(),
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
```
|
||||
|
||||
Agora abra:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/mcp/tool_router.py
|
||||
```
|
||||
|
||||
Localize:
|
||||
|
||||
```python
|
||||
MCPGatewayClient(
|
||||
base_url=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_URL",
|
||||
"http://localhost:8300",
|
||||
),
|
||||
token=getattr(settings, "MCP_GATEWAY_TOKEN", None),
|
||||
timeout_seconds=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_TIMEOUT_SECONDS",
|
||||
settings.MCP_TOOL_TIMEOUT_SECONDS,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Altere para:
|
||||
|
||||
```python
|
||||
MCPGatewayClient(
|
||||
base_url=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_URL",
|
||||
"http://localhost:8300",
|
||||
),
|
||||
token=getattr(settings, "MCP_GATEWAY_TOKEN", None),
|
||||
timeout_seconds=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_TIMEOUT_SECONDS",
|
||||
settings.MCP_TOOL_TIMEOUT_SECONDS,
|
||||
),
|
||||
auth_mode=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_AUTH_MODE",
|
||||
None,
|
||||
),
|
||||
basic_client_id=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_BASIC_CLIENT_ID",
|
||||
None,
|
||||
),
|
||||
basic_secret=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_BASIC_SECRET",
|
||||
None,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Adicione estes campos em:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/config/settings.py
|
||||
```
|
||||
|
||||
Próximo das configurações existentes de MCP Gateway:
|
||||
|
||||
```python
|
||||
MCP_GATEWAY_AUTH_MODE: str | None = None
|
||||
MCP_GATEWAY_BASIC_CLIENT_ID: str | None = None
|
||||
MCP_GATEWAY_BASIC_SECRET: str | None = None
|
||||
```
|
||||
|
||||
Há também uma factory local em:
|
||||
|
||||
```text
|
||||
Tuning-Performance/Authentication/
|
||||
agent_template_backend_authentication/
|
||||
app/mcp_gateway_client_factory.py
|
||||
```
|
||||
|
||||
Ajuste para:
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from agent_framework.gateways import MCPGatewayClient
|
||||
|
||||
|
||||
def build_mcp_gateway_client() -> MCPGatewayClient | None:
|
||||
if os.getenv("MCP_GATEWAY_ENABLED", "true").lower() != "true":
|
||||
return None
|
||||
|
||||
return MCPGatewayClient(
|
||||
base_url=os.getenv(
|
||||
"MCP_GATEWAY_URL",
|
||||
"http://localhost:8300",
|
||||
),
|
||||
token=os.getenv("MCP_GATEWAY_TOKEN") or None,
|
||||
timeout_seconds=int(
|
||||
os.getenv("MCP_GATEWAY_TIMEOUT_SECONDS", "60")
|
||||
),
|
||||
auth_mode=os.getenv("MCP_GATEWAY_AUTH_MODE"),
|
||||
basic_client_id=os.getenv(
|
||||
"MCP_GATEWAY_BASIC_CLIENT_ID"
|
||||
),
|
||||
basic_secret=os.getenv(
|
||||
"MCP_GATEWAY_BASIC_SECRET"
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 7. Configurar o MCP Gateway
|
||||
|
||||
Entre no diretório:
|
||||
|
||||
```bash
|
||||
cd apps/mcp_gateway
|
||||
```
|
||||
|
||||
Crie `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Adicione:
|
||||
|
||||
```env
|
||||
# Entrada: Agent Backend -> MCP Gateway
|
||||
MCP_GATEWAY_AUTH_ENABLED=true
|
||||
MCP_GATEWAY_AUTH_MODE=basic
|
||||
MCP_GATEWAY_AUTH_BASIC_CLIENT_ID=agent-backend-test
|
||||
MCP_GATEWAY_AUTH_BASIC_SECRET_HASH=COLE_AQUI_HASH_BACKEND_MCP
|
||||
MCP_GATEWAY_AUTH_BASIC_REALM=mcp-gateway
|
||||
|
||||
MCP_GATEWAY_AUTH_PUBLIC_PATHS=/health,/ready,/docs,/openapi.json,/redoc
|
||||
MCP_GATEWAY_AUTH_PUBLIC_PREFIXES=
|
||||
|
||||
MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
```
|
||||
|
||||
## Desabilitar o mecanismo Bearer legado
|
||||
|
||||
O MCP Gateway ainda possui um segundo mecanismo antigo, configurado dentro de:
|
||||
|
||||
```text
|
||||
apps/mcp_gateway/config/mcp_gateway.yaml
|
||||
```
|
||||
|
||||
Localize a seção:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
Altere para:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
Isso é necessário porque o novo middleware já faz a autenticação Basic. Caso o `auth_check()` legado continue habilitado, a requisição passará pelo Basic e depois será rejeitada por não possuir Bearer Token.
|
||||
|
||||
---
|
||||
|
||||
# 8. Subir os componentes
|
||||
|
||||
Use quatro terminais.
|
||||
|
||||
## Terminal 1 — MCP Servers
|
||||
|
||||
O MCP Gateway precisa ter pelo menos um servidor MCP disponível para demonstrar uma chamada real.
|
||||
|
||||
Na raiz do projeto:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Suba o servidor telecom:
|
||||
|
||||
```bash
|
||||
uvicorn mcp.servers.telecom_mcp_server.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8100 \
|
||||
--reload
|
||||
```
|
||||
|
||||
Em outro terminal, caso queira também o retail:
|
||||
|
||||
```bash
|
||||
uvicorn mcp.servers.retail_mcp_server.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8200 \
|
||||
--reload
|
||||
```
|
||||
|
||||
Confira as URLs configuradas em:
|
||||
|
||||
```text
|
||||
apps/mcp_gateway/config/mcp_gateway.yaml
|
||||
```
|
||||
|
||||
Para execução local, devem apontar para:
|
||||
|
||||
```yaml
|
||||
url: http://localhost:8100
|
||||
```
|
||||
|
||||
e:
|
||||
|
||||
```yaml
|
||||
url: http://localhost:8200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Terminal 2 — MCP Gateway
|
||||
|
||||
```bash
|
||||
cd apps/mcp_gateway
|
||||
source ../../.venv/bin/activate
|
||||
```
|
||||
|
||||
Suba usando `--env-file`. Isso é importante porque o middleware lê variáveis com `os.getenv()`:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8300 \
|
||||
--reload \
|
||||
--env-file .env
|
||||
```
|
||||
|
||||
Teste a saúde pública:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8300/health
|
||||
```
|
||||
|
||||
Teste um endpoint protegido sem credencial:
|
||||
|
||||
```bash
|
||||
curl -i http://localhost:8300/v1/tools
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 401 Unauthorized
|
||||
```
|
||||
|
||||
Teste com Basic Auth:
|
||||
|
||||
```bash
|
||||
curl -i \
|
||||
-u 'agent-backend-test:BackendMcp-Test-2026!' \
|
||||
http://localhost:8300/v1/tools
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Terminal 3 — Agent Backend
|
||||
|
||||
```bash
|
||||
cd Tuning-Performance/Authentication/agent_template_backend_authentication
|
||||
source ../../../.venv/bin/activate
|
||||
```
|
||||
|
||||
Suba:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8000 \
|
||||
--reload \
|
||||
--env-file .env
|
||||
```
|
||||
|
||||
Teste saúde:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
```
|
||||
|
||||
Teste endpoint protegido sem credencial:
|
||||
|
||||
```bash
|
||||
curl -i http://localhost:8000/agents
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 401 Unauthorized
|
||||
```
|
||||
|
||||
Teste com a credencial usada pelo Agent Gateway:
|
||||
|
||||
```bash
|
||||
curl -i \
|
||||
-u 'agent-gateway-test:GatewayBackend-Test-2026!' \
|
||||
http://localhost:8000/agents
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
Teste mensagem diretamente:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-u 'agent-gateway-test:GatewayBackend-Test-2026!' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"agent_id": "telecom_contas",
|
||||
"tenant_id": "default",
|
||||
"payload": {
|
||||
"text": "Quero consultar minha fatura",
|
||||
"session_id": "teste-backend-001",
|
||||
"user_id": "user-001",
|
||||
"customer_id": "12345",
|
||||
"message_id": "msg-001"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Terminal 4 — Agent Gateway
|
||||
|
||||
```bash
|
||||
cd apps/agent_gateway
|
||||
source ../../.venv/bin/activate
|
||||
```
|
||||
|
||||
Suba:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8010 \
|
||||
--reload \
|
||||
--env-file .env
|
||||
```
|
||||
|
||||
Teste saúde:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8010/health
|
||||
```
|
||||
|
||||
Teste endpoint protegido sem credencial:
|
||||
|
||||
```bash
|
||||
curl -i http://localhost:8010/backends
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 401 Unauthorized
|
||||
```
|
||||
|
||||
Teste com a credencial externa:
|
||||
|
||||
```bash
|
||||
curl -i \
|
||||
-u 'tia-test:TiaGateway-Test-2026!' \
|
||||
http://localhost:8010/backends
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 9. Validar o circuito completo
|
||||
|
||||
Force o backend `contas` para evitar que o roteador selecione um backend não iniciado:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8010/gateway/message \
|
||||
-u 'tia-test:TiaGateway-Test-2026!' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"backend_id": "contas",
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"session_id": "circuito-basic-001",
|
||||
"payload": {
|
||||
"text": "Quero consultar minha fatura",
|
||||
"session_id": "circuito-basic-001",
|
||||
"user_id": "user-001",
|
||||
"customer_id": "12345",
|
||||
"message_id": "msg-circuito-001"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
O circuito esperado é:
|
||||
|
||||
```text
|
||||
curl
|
||||
│ Basic tia-test
|
||||
▼
|
||||
Agent Gateway :8010
|
||||
│ Basic agent-gateway-test
|
||||
▼
|
||||
Agent Backend :8000
|
||||
│ Basic agent-backend-test
|
||||
▼
|
||||
MCP Gateway :8300
|
||||
▼
|
||||
MCP Server :8100 ou :8200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 10. Como comprovar cada autenticação
|
||||
|
||||
Faça testes negativos em cada trecho.
|
||||
|
||||
## Secret externo incorreto
|
||||
|
||||
```bash
|
||||
curl -i \
|
||||
-u 'tia-test:senha-errada' \
|
||||
http://localhost:8010/backends
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```text
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
## Secret do gateway para backend incorreto
|
||||
|
||||
Altere temporariamente no `apps/agent_gateway/.env`:
|
||||
|
||||
```env
|
||||
BACKEND_AUTH_SECRET=senha-errada
|
||||
```
|
||||
|
||||
Reinicie o Agent Gateway e envie uma mensagem.
|
||||
|
||||
O gateway deverá retornar erro de backend, normalmente:
|
||||
|
||||
```text
|
||||
502 Bad Gateway
|
||||
```
|
||||
|
||||
O erro interno será originado por um:
|
||||
|
||||
```text
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
do Agent Backend.
|
||||
|
||||
## Secret do backend para MCP incorreto
|
||||
|
||||
Altere temporariamente:
|
||||
|
||||
```env
|
||||
MCP_GATEWAY_BASIC_SECRET=senha-errada
|
||||
```
|
||||
|
||||
Reinicie o backend e execute uma frase que acione uma ferramenta MCP.
|
||||
|
||||
O backend deverá registrar falha na chamada ao MCP Gateway com:
|
||||
|
||||
```text
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 11. Verificação rápida de portas
|
||||
|
||||
No Linux ou WSL:
|
||||
|
||||
```bash
|
||||
ss -lntp | grep -E ':8000|:8010|:8100|:8200|:8300'
|
||||
```
|
||||
|
||||
No Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
Get-NetTCPConnection -State Listen |
|
||||
Where-Object LocalPort -in 8000,8010,8100,8200,8300 |
|
||||
Sort-Object LocalPort
|
||||
```
|
||||
|
||||
Você deverá ver:
|
||||
|
||||
```text
|
||||
8000 Agent Backend
|
||||
8010 Agent Gateway
|
||||
8100 Telecom MCP Server
|
||||
8200 Retail MCP Server
|
||||
8300 MCP Gateway
|
||||
```
|
||||
|
||||
## Observação importante
|
||||
|
||||
O segredo original precisa existir no componente cliente:
|
||||
|
||||
```text
|
||||
TIA ou curl:
|
||||
TiaGateway-Test-2026!
|
||||
|
||||
Agent Gateway:
|
||||
GatewayBackend-Test-2026!
|
||||
|
||||
Agent Backend:
|
||||
BackendMcp-Test-2026!
|
||||
```
|
||||
|
||||
Os componentes servidores armazenam apenas os hashes:
|
||||
|
||||
```text
|
||||
Agent Gateway:
|
||||
hash de TiaGateway-Test-2026!
|
||||
|
||||
Agent Backend:
|
||||
hash de GatewayBackend-Test-2026!
|
||||
|
||||
MCP Gateway:
|
||||
hash de BackendMcp-Test-2026!
|
||||
```
|
||||
|
||||
Em produção, os segredos originais e hashes devem vir de Vault ou Kubernetes Secret, não de arquivos `.env`.
|
||||
520
agent_framework_oci/Long_Term_Memory_Implementation_Guide_EN.md
Normal file
520
agent_framework_oci/Long_Term_Memory_Implementation_Guide_EN.md
Normal file
@@ -0,0 +1,520 @@
|
||||
### Long-Term Memory Implementation Guide
|
||||
|
||||
### Concept
|
||||
|
||||
Long-Term Memory (LTM) is the `agent_framework` capability that stores and retrieves durable facts beyond the lifetime of a conversation session.
|
||||
|
||||
Unlike message history, which is normally associated with a `session_id`, Long-Term Memory is associated with the business identity of the user or customer. In the current implementation, this identity consists of:
|
||||
|
||||
```text
|
||||
tenant_id
|
||||
agent_id
|
||||
customer_key
|
||||
```
|
||||
|
||||
This allows an agent to retrieve preferences, identity information, projects and constraints even when a new session is created.
|
||||
|
||||
### Purpose
|
||||
|
||||
Long-Term Memory is used to:
|
||||
|
||||
- maintain continuity across sessions;
|
||||
- personalize responses;
|
||||
- prevent users from repeating previously supplied information;
|
||||
- reduce the need to send the full conversation history to the model;
|
||||
- store preferences, current projects, preferred names and constraints;
|
||||
- isolate memory across tenants, agents and customers.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Session A:
|
||||
"Call me Cris. My preferred language is Python."
|
||||
|
||||
Session B, with another session_id and the same customer_key:
|
||||
"What do you remember about me?"
|
||||
|
||||
Expected response:
|
||||
"Your preferred name is Cris and your preferred language is Python."
|
||||
```
|
||||
|
||||
### Memory type differences
|
||||
|
||||
#### Conversation Memory
|
||||
|
||||
Stores messages from the current conversation and is normally associated with the `session_id`.
|
||||
|
||||
#### Summary Memory
|
||||
|
||||
Stores a summary of the conversation to reduce the context size sent to the model.
|
||||
|
||||
#### Long-Term Memory
|
||||
|
||||
Stores durable facts across sessions and is associated with the business identity, primarily the `customer_key`.
|
||||
|
||||
### Components
|
||||
|
||||
#### LongTermMemoryManager
|
||||
|
||||
Coordinates:
|
||||
|
||||
- memory loading;
|
||||
- identity-based retrieval;
|
||||
- context rendering;
|
||||
- durable fact extraction;
|
||||
- fact persistence;
|
||||
- deduplication and updates.
|
||||
|
||||
#### LongTermMemoryStore
|
||||
|
||||
Persistence interface used by the manager.
|
||||
|
||||
#### SQLiteLongTermMemoryStore
|
||||
|
||||
Reference implementation based on SQLite.
|
||||
|
||||
It is suitable for:
|
||||
|
||||
- local development;
|
||||
- testing;
|
||||
- demonstrations;
|
||||
- low-scale environments.
|
||||
|
||||
#### InMemoryLongTermMemoryStore
|
||||
|
||||
In-memory implementation used for quick tests.
|
||||
|
||||
Its content is lost when the backend process stops.
|
||||
|
||||
#### LongTermMemoryExtractor
|
||||
|
||||
Identifies durable facts in messages.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
preferred_name = Cris
|
||||
preferred_language = Python
|
||||
current_project = Atlas
|
||||
```
|
||||
|
||||
#### LongTermMemoryItem
|
||||
|
||||
Data model representing a persisted item, including identity, key, value, category, confidence and metadata.
|
||||
|
||||
#### AgentRuntime
|
||||
|
||||
Loads memory before agent execution and injects the rendered context into the prompt.
|
||||
|
||||
#### persist_long_term_memory node
|
||||
|
||||
LangGraph node responsible for persisting facts after the final response has been generated and validated.
|
||||
|
||||
### File structure
|
||||
|
||||
```text
|
||||
libs/
|
||||
└── agent_framework/
|
||||
└── src/
|
||||
└── agent_framework/
|
||||
└── memory/
|
||||
├── __init__.py
|
||||
├── long_term_extractor.py
|
||||
├── long_term_memory.py
|
||||
├── long_term_models.py
|
||||
└── long_term_store.py
|
||||
```
|
||||
|
||||
### Execution flow
|
||||
|
||||
```text
|
||||
User message
|
||||
│
|
||||
▼
|
||||
AgentRuntime.prepare_memory_context()
|
||||
│
|
||||
├── Conversation Memory
|
||||
├── Summary Memory
|
||||
└── Long-Term Memory
|
||||
│
|
||||
▼
|
||||
long_term_memory_context
|
||||
│
|
||||
▼
|
||||
Agent prompt
|
||||
│
|
||||
▼
|
||||
Agent
|
||||
│
|
||||
▼
|
||||
Guardrails / Judges / Supervisor
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
LongTermMemoryExtractor
|
||||
│
|
||||
▼
|
||||
LongTermMemoryStore
|
||||
```
|
||||
|
||||
### Framework configuration
|
||||
|
||||
### New modules
|
||||
|
||||
Copy:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_extractor.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_memory.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_models.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_store.py
|
||||
```
|
||||
|
||||
### Update memory/__init__.py
|
||||
|
||||
Export the Long-Term Memory components:
|
||||
|
||||
```python
|
||||
from agent_framework.memory.long_term_memory import (
|
||||
LongTermMemoryManager,
|
||||
create_long_term_memory_manager,
|
||||
)
|
||||
from agent_framework.memory.long_term_models import LongTermMemoryItem
|
||||
from agent_framework.memory.long_term_store import (
|
||||
InMemoryLongTermMemoryStore,
|
||||
LongTermMemoryStore,
|
||||
SQLiteLongTermMemoryStore,
|
||||
create_long_term_memory_store,
|
||||
)
|
||||
```
|
||||
|
||||
### Update settings.py
|
||||
|
||||
Add:
|
||||
|
||||
```python
|
||||
ENABLE_LONG_TERM_MEMORY: bool = False
|
||||
LONG_TERM_MEMORY_PROVIDER: str = "sqlite"
|
||||
LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db"
|
||||
LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory"
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True
|
||||
```
|
||||
|
||||
### AgentRuntime integration
|
||||
|
||||
The runtime must:
|
||||
|
||||
1. verify that the feature is enabled;
|
||||
2. create the manager when needed;
|
||||
3. retrieve facts using the identity;
|
||||
4. populate the workflow state;
|
||||
5. inject the rendered context into the prompt.
|
||||
|
||||
State fields:
|
||||
|
||||
```python
|
||||
long_term_memories: list[dict]
|
||||
long_term_memory_context: str
|
||||
long_term_memory_write_result: dict
|
||||
```
|
||||
|
||||
### AgentWorkflow initialization
|
||||
|
||||
Create the manager in `AgentWorkflow`:
|
||||
|
||||
```python
|
||||
self.long_term_memory_manager = create_long_term_memory_manager(
|
||||
settings,
|
||||
telemetry=telemetry,
|
||||
)
|
||||
```
|
||||
|
||||
### Correct agent initialization
|
||||
|
||||
Do not pass `long_term_memory_manager` through `agent_kwargs` when the constructors of `BillingAgent`, `ProductAgent`, `OrdersAgent` and `SupportAgent` do not declare that parameter.
|
||||
|
||||
This initialization causes an error:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"settings": settings,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
"long_term_memory_manager": self.long_term_memory_manager,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
```
|
||||
|
||||
Resulting error:
|
||||
|
||||
```text
|
||||
TypeError: BillingAgent.__init__() got an unexpected keyword argument
|
||||
'long_term_memory_manager'
|
||||
```
|
||||
|
||||
The recommended approach is to create agents using their existing signatures and inject the manager as an attribute after initialization:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"tool_router": getattr(self, "tool_router", None),
|
||||
"rag_service": self.rag_service,
|
||||
"cache": self.cache,
|
||||
"settings": settings,
|
||||
"observer": self.observer,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
self.product = ProductAgent(llm, **agent_kwargs)
|
||||
self.orders = OrdersAgent(llm, **agent_kwargs)
|
||||
self.support = SupportAgent(llm, **agent_kwargs)
|
||||
|
||||
for agent in (
|
||||
self.billing,
|
||||
self.product,
|
||||
self.orders,
|
||||
self.support,
|
||||
):
|
||||
agent.long_term_memory_manager = self.long_term_memory_manager
|
||||
```
|
||||
|
||||
This approach avoids changing every agent constructor and keeps the feature encapsulated in the framework.
|
||||
|
||||
### LangGraph configuration
|
||||
|
||||
Register the node:
|
||||
|
||||
```python
|
||||
builder.add_node(
|
||||
"persist_long_term_memory",
|
||||
self._node(
|
||||
"persist_long_term_memory",
|
||||
self.persist_long_term_memory,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Update the edges:
|
||||
|
||||
```python
|
||||
builder.add_edge(
|
||||
"supervisor_review",
|
||||
"persist_long_term_memory",
|
||||
)
|
||||
builder.add_edge(
|
||||
"persist_long_term_memory",
|
||||
"persist",
|
||||
)
|
||||
```
|
||||
|
||||
Implement:
|
||||
|
||||
```python
|
||||
async def persist_long_term_memory(
|
||||
self,
|
||||
state: AgentState,
|
||||
) -> dict[str, object]:
|
||||
result = await self.long_term_memory_manager.persist_turn(state)
|
||||
|
||||
return {
|
||||
"long_term_memory_write_result": result,
|
||||
}
|
||||
```
|
||||
|
||||
Final flow:
|
||||
|
||||
```text
|
||||
supervisor_review
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
persist
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
```env
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### SQLite database path
|
||||
|
||||
A relative path is resolved from the directory in which the backend is started.
|
||||
|
||||
To prevent different databases from being created accidentally, prefer an absolute path in development environments:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db
|
||||
```
|
||||
|
||||
Create the directory before starting:
|
||||
|
||||
```bash
|
||||
mkdir -p data
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
### Test 1 — Persistence
|
||||
|
||||
Send:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-a",
|
||||
"customer_key": "11999999999",
|
||||
"message": "Call me Cris. My preferred language is Python and my current project is Atlas."
|
||||
}
|
||||
```
|
||||
|
||||
### Test 2 — Retrieval in another session
|
||||
|
||||
Use another `session_id` while keeping the same `customer_key`:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-b",
|
||||
"customer_key": "11999999999",
|
||||
"message": "What do you remember about me, my preferences and my project?"
|
||||
}
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
```text
|
||||
Your preferred name is Cris.
|
||||
Your preferred language is Python.
|
||||
Your current project is Atlas.
|
||||
```
|
||||
|
||||
### Test 3 — Isolation
|
||||
|
||||
Use another customer:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-c",
|
||||
"customer_key": "another-customer",
|
||||
"message": "What is my preferred name and current project?"
|
||||
}
|
||||
```
|
||||
|
||||
The data associated with `11999999999` must not be returned.
|
||||
|
||||
### Test 4 — Frontend reset
|
||||
|
||||
Restart or reset the frontend and verify that it still sends the same `customer_key`.
|
||||
|
||||
Memory must survive a `session_id` change. Resetting the frontend does not delete the SQLite database.
|
||||
|
||||
### Test 5 — Backend restart
|
||||
|
||||
Restart Uvicorn and repeat the query.
|
||||
|
||||
With:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
memory must remain available.
|
||||
|
||||
With:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
memory is lost when the process stops.
|
||||
|
||||
### Direct SQLite verification
|
||||
|
||||
Find the database:
|
||||
|
||||
```bash
|
||||
find . -name "agent_framework.db" -type f
|
||||
```
|
||||
|
||||
Open it:
|
||||
|
||||
```bash
|
||||
sqlite3 ./data/agent_framework.db
|
||||
```
|
||||
|
||||
Query:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
tenant_id,
|
||||
agent_id,
|
||||
customer_key,
|
||||
memory_type,
|
||||
memory_key,
|
||||
memory_value,
|
||||
confidence,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM agentfw_long_term_memory
|
||||
ORDER BY updated_at DESC;
|
||||
```
|
||||
|
||||
### Success criteria
|
||||
|
||||
The implementation is working when:
|
||||
|
||||
- memory is retrieved with another `session_id`;
|
||||
- the same `customer_key` retrieves previous facts;
|
||||
- another `customer_key` cannot access those facts;
|
||||
- restarting the frontend does not erase memory;
|
||||
- restarting the backend does not erase memory when using SQLite;
|
||||
- the `persist_long_term_memory` node runs;
|
||||
- the prompt receives `long_term_memory_context`.
|
||||
|
||||
### Best practices
|
||||
|
||||
- Persist only durable facts.
|
||||
- Do not store the complete conversation as Long-Term Memory.
|
||||
- Isolate data by `tenant_id`, `agent_id` and `customer_key`.
|
||||
- Do not use `session_id` as the permanent user identity.
|
||||
- Persist only after final validations.
|
||||
- Avoid persisting temporary tool results.
|
||||
- Record telemetry for reads, writes, updates and failures.
|
||||
- Define retention and deletion policies.
|
||||
- Use an absolute SQLite path in environments with multiple working directories.
|
||||
- Move to an enterprise database for production and high-availability environments.
|
||||
|
||||
### Reference implementation limitations
|
||||
|
||||
The current implementation uses rule-based extraction and SQLite as the reference provider.
|
||||
|
||||
Recommended future enhancements:
|
||||
|
||||
- LLM-based fact extraction;
|
||||
- vector-based semantic memory;
|
||||
- episodic memory;
|
||||
- expiration and versioning;
|
||||
- semantic deduplication;
|
||||
- consent policies;
|
||||
- query and deletion APIs;
|
||||
- Oracle Autonomous Database provider;
|
||||
- encryption and sensitive-data classification.
|
||||
272
agent_framework_oci/MANUAL_AGENT_PLATFORM_GATEWAYS.md
Normal file
272
agent_framework_oci/MANUAL_AGENT_PLATFORM_GATEWAYS.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Agent Platform OCI — Manual Oficial de Agent Gateway e MCP Gateway
|
||||
|
||||
## Objetivo
|
||||
|
||||
Este documento consolida:
|
||||
- Arquitetura oficial
|
||||
- Inventário dos componentes
|
||||
- Procedimento completo de execução local
|
||||
- MCP Gateway
|
||||
- Agent Gateway
|
||||
- Backend Runtime
|
||||
- Frontend
|
||||
- Testes E2E
|
||||
- Troubleshooting
|
||||
- Decisões arquiteturais
|
||||
|
||||
---
|
||||
|
||||
# Arquitetura Oficial
|
||||
|
||||
Frontend (5173)
|
||||
↓
|
||||
Agent Gateway (9000)
|
||||
↓
|
||||
Agent Template Backend / Runtime (8000)
|
||||
↓
|
||||
MCP Gateway (8300)
|
||||
↓
|
||||
Telecom MCP Server (8100)
|
||||
Retail MCP Server (8200)
|
||||
|
||||
---
|
||||
|
||||
# Portas Oficiais
|
||||
|
||||
| Componente | Porta |
|
||||
|------------|--------|
|
||||
| Frontend | 5173 |
|
||||
| Agent Gateway | 9000 |
|
||||
| Backend Runtime | 8000 |
|
||||
| MCP Gateway | 8300 |
|
||||
| Telecom MCP Server | 8100 |
|
||||
| Retail MCP Server | 8200 |
|
||||
|
||||
---
|
||||
|
||||
# Variáveis Oficiais
|
||||
|
||||
## Agent Template Backend
|
||||
|
||||
ENABLE_MCP_TOOLS=true
|
||||
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
|
||||
## Agent Gateway
|
||||
|
||||
DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
|
||||
AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
|
||||
|
||||
## MCP Gateway
|
||||
|
||||
MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
|
||||
---
|
||||
|
||||
# Ordem de Inicialização
|
||||
|
||||
1. Telecom MCP Server
|
||||
2. Retail MCP Server
|
||||
3. MCP Gateway
|
||||
4. Agent Template Backend
|
||||
5. Agent Gateway
|
||||
6. Frontend
|
||||
|
||||
---
|
||||
|
||||
# Terminal 1 — Telecom MCP Server
|
||||
|
||||
cd mcp/servers/telecom_mcp_server
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
python -m uvicorn main:app --host 0.0.0.0 --port 8100 --reload
|
||||
|
||||
Validação:
|
||||
|
||||
curl http://localhost:8100/health
|
||||
|
||||
---
|
||||
|
||||
# Terminal 2 — Retail MCP Server
|
||||
|
||||
cd mcp/servers/retail_mcp_server
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
python -m uvicorn main:app --host 0.0.0.0 --port 8200 --reload
|
||||
|
||||
Validação:
|
||||
|
||||
curl http://localhost:8200/health
|
||||
|
||||
---
|
||||
|
||||
# Terminal 3 — MCP Gateway
|
||||
|
||||
cd apps/mcp_gateway
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
export MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload
|
||||
|
||||
Validações:
|
||||
|
||||
curl http://localhost:8300/health
|
||||
curl http://localhost:8300/ready
|
||||
curl http://localhost:8300/v1/tools
|
||||
|
||||
Teste:
|
||||
|
||||
curl -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke
|
||||
|
||||
---
|
||||
|
||||
# Terminal 4 — Agent Template Backend
|
||||
|
||||
cd templates/agent_template_backend
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
|
||||
Validações:
|
||||
|
||||
curl http://localhost:8000/health
|
||||
curl http://localhost:8000/agents
|
||||
|
||||
---
|
||||
|
||||
# Terminal 5 — Agent Gateway
|
||||
|
||||
cd apps/agent_gateway
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
export DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
|
||||
export AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
|
||||
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 9000 --reload
|
||||
|
||||
Validações:
|
||||
|
||||
curl http://localhost:9000/health
|
||||
|
||||
Teste:
|
||||
|
||||
curl -X POST http://localhost:9000/gateway/message
|
||||
|
||||
---
|
||||
|
||||
# Terminal 6 — Frontend
|
||||
|
||||
cd agent_frontend
|
||||
|
||||
npm install
|
||||
|
||||
npm run dev -- --host 0.0.0.0 --port 5173
|
||||
|
||||
Abrir:
|
||||
|
||||
http://localhost:5173
|
||||
|
||||
Backend URL:
|
||||
|
||||
http://localhost:9000
|
||||
|
||||
---
|
||||
|
||||
# Fluxo de Tools
|
||||
|
||||
Agent
|
||||
↓
|
||||
MCPToolRouter
|
||||
↓
|
||||
MCPGatewayClient
|
||||
↓
|
||||
MCP Gateway
|
||||
↓
|
||||
MCP Server
|
||||
|
||||
---
|
||||
|
||||
# Teste Integrado E2E
|
||||
|
||||
Frontend
|
||||
↓
|
||||
Agent Gateway
|
||||
↓
|
||||
Backend Runtime
|
||||
↓
|
||||
MCP Gateway
|
||||
↓
|
||||
Telecom MCP Server
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
- Agent Gateway recebe requisição
|
||||
- Runtime executa LangGraph
|
||||
- MCP Gateway resolve tool
|
||||
- MCP Server responde
|
||||
- Usuário recebe resposta
|
||||
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Backend chamando MCP Server direto
|
||||
|
||||
Confirmar:
|
||||
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
|
||||
## Porta incorreta
|
||||
|
||||
A porta oficial do MCP Gateway é:
|
||||
|
||||
8300
|
||||
|
||||
## Agent Gateway não encontra Backend
|
||||
|
||||
Validar:
|
||||
|
||||
curl http://localhost:8000/health
|
||||
|
||||
## MCP Gateway não encontra MCP Server
|
||||
|
||||
Validar:
|
||||
|
||||
curl http://localhost:8100/health
|
||||
curl http://localhost:8200/health
|
||||
|
||||
---
|
||||
|
||||
# Decisões Arquiteturais Oficiais
|
||||
|
||||
- Agent Gateway centraliza governança
|
||||
- Runtime executa LangGraph
|
||||
- Runtime executa LLM
|
||||
- MCP Gateway centraliza tools
|
||||
- MCP Servers executam tools
|
||||
- Backend usa MCP Gateway
|
||||
- gateway_runtime.env.example foi removido
|
||||
- MCP_GATEWAY_* fica no .env do backend
|
||||
- Porta oficial MCP Gateway = 8300
|
||||
520
agent_framework_oci/Manual_Long_Term_Memory_PT.md
Normal file
520
agent_framework_oci/Manual_Long_Term_Memory_PT.md
Normal file
@@ -0,0 +1,520 @@
|
||||
### Manual de Implementação — Long-Term Memory
|
||||
|
||||
### Conceito
|
||||
|
||||
A Long-Term Memory (LTM) é a capacidade do `agent_framework` de armazenar e recuperar fatos duradouros além da duração de uma sessão de conversa.
|
||||
|
||||
Diferentemente do histórico de mensagens, que normalmente está associado a um `session_id`, a memória de longo prazo é associada à identidade de negócio do usuário ou cliente. Na implementação atual, essa identidade é composta por:
|
||||
|
||||
```text
|
||||
tenant_id
|
||||
agent_id
|
||||
customer_key
|
||||
```
|
||||
|
||||
Isso permite que um agente recupere preferências, informações de identidade, projetos e restrições mesmo quando uma nova sessão é criada.
|
||||
|
||||
### Para que serve
|
||||
|
||||
A Long-Term Memory serve para:
|
||||
|
||||
- manter continuidade entre sessões;
|
||||
- personalizar respostas;
|
||||
- evitar que o usuário repita informações já fornecidas;
|
||||
- reduzir a necessidade de enviar todo o histórico ao modelo;
|
||||
- armazenar preferências, projetos atuais, nomes preferidos e restrições;
|
||||
- isolar a memória entre tenants, agentes e clientes.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```text
|
||||
Sessão A:
|
||||
"Me chame de Cris. Minha linguagem preferida é Python."
|
||||
|
||||
Sessão B, com outro session_id e o mesmo customer_key:
|
||||
"O que você lembra sobre mim?"
|
||||
|
||||
Resposta esperada:
|
||||
"Seu nome preferido é Cris e sua linguagem preferida é Python."
|
||||
```
|
||||
|
||||
### Diferença entre os tipos de memória
|
||||
|
||||
#### Conversation Memory
|
||||
|
||||
Mantém as mensagens da conversa atual e normalmente está associada ao `session_id`.
|
||||
|
||||
#### Summary Memory
|
||||
|
||||
Mantém um resumo da conversa para reduzir o tamanho do contexto enviado ao modelo.
|
||||
|
||||
#### Long-Term Memory
|
||||
|
||||
Mantém fatos duradouros entre sessões e é associada à identidade de negócio, principalmente ao `customer_key`.
|
||||
|
||||
### Componentes da funcionalidade
|
||||
|
||||
#### LongTermMemoryManager
|
||||
|
||||
Responsável por coordenar:
|
||||
|
||||
- carregamento das memórias;
|
||||
- recuperação por identidade;
|
||||
- renderização do contexto;
|
||||
- extração de novos fatos;
|
||||
- persistência dos fatos;
|
||||
- deduplicação e atualização.
|
||||
|
||||
#### LongTermMemoryStore
|
||||
|
||||
Interface de persistência utilizada pelo manager.
|
||||
|
||||
#### SQLiteLongTermMemoryStore
|
||||
|
||||
Implementação de referência baseada em SQLite.
|
||||
|
||||
É apropriada para:
|
||||
|
||||
- desenvolvimento local;
|
||||
- testes;
|
||||
- demonstrações;
|
||||
- ambientes de baixa escala.
|
||||
|
||||
#### InMemoryLongTermMemoryStore
|
||||
|
||||
Implementação em memória utilizada para testes rápidos.
|
||||
|
||||
O conteúdo é perdido quando o processo do backend é encerrado.
|
||||
|
||||
#### LongTermMemoryExtractor
|
||||
|
||||
Responsável por identificar fatos duradouros nas mensagens.
|
||||
|
||||
Exemplos de fatos:
|
||||
|
||||
```text
|
||||
preferred_name = Cris
|
||||
preferred_language = Python
|
||||
current_project = Atlas
|
||||
```
|
||||
|
||||
#### LongTermMemoryItem
|
||||
|
||||
Modelo que representa um item persistido, incluindo identidade, chave, valor, categoria, confiança e metadados.
|
||||
|
||||
#### AgentRuntime
|
||||
|
||||
Carrega a memória antes da execução do agente e injeta o contexto no prompt.
|
||||
|
||||
#### Nó persist_long_term_memory
|
||||
|
||||
Nó do LangGraph responsável por persistir os fatos após a geração e validação da resposta final.
|
||||
|
||||
### Estrutura dos arquivos
|
||||
|
||||
```text
|
||||
libs/
|
||||
└── agent_framework/
|
||||
└── src/
|
||||
└── agent_framework/
|
||||
└── memory/
|
||||
├── __init__.py
|
||||
├── long_term_extractor.py
|
||||
├── long_term_memory.py
|
||||
├── long_term_models.py
|
||||
└── long_term_store.py
|
||||
```
|
||||
|
||||
### Fluxo de execução
|
||||
|
||||
```text
|
||||
Mensagem do usuário
|
||||
│
|
||||
▼
|
||||
AgentRuntime.prepare_memory_context()
|
||||
│
|
||||
├── Conversation Memory
|
||||
├── Summary Memory
|
||||
└── Long-Term Memory
|
||||
│
|
||||
▼
|
||||
long_term_memory_context
|
||||
│
|
||||
▼
|
||||
Prompt do agente
|
||||
│
|
||||
▼
|
||||
Agente
|
||||
│
|
||||
▼
|
||||
Guardrails / Judges / Supervisor
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
LongTermMemoryExtractor
|
||||
│
|
||||
▼
|
||||
LongTermMemoryStore
|
||||
```
|
||||
|
||||
### Configuração do framework
|
||||
|
||||
### Novos módulos
|
||||
|
||||
Copie os arquivos:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_extractor.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_memory.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_models.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_store.py
|
||||
```
|
||||
|
||||
### Atualização de memory/__init__.py
|
||||
|
||||
Exporte os componentes da Long-Term Memory:
|
||||
|
||||
```python
|
||||
from agent_framework.memory.long_term_memory import (
|
||||
LongTermMemoryManager,
|
||||
create_long_term_memory_manager,
|
||||
)
|
||||
from agent_framework.memory.long_term_models import LongTermMemoryItem
|
||||
from agent_framework.memory.long_term_store import (
|
||||
InMemoryLongTermMemoryStore,
|
||||
LongTermMemoryStore,
|
||||
SQLiteLongTermMemoryStore,
|
||||
create_long_term_memory_store,
|
||||
)
|
||||
```
|
||||
|
||||
### Atualização de settings.py
|
||||
|
||||
Adicione as configurações:
|
||||
|
||||
```python
|
||||
ENABLE_LONG_TERM_MEMORY: bool = False
|
||||
LONG_TERM_MEMORY_PROVIDER: str = "sqlite"
|
||||
LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db"
|
||||
LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory"
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True
|
||||
```
|
||||
|
||||
### Integração com AgentRuntime
|
||||
|
||||
O runtime deve:
|
||||
|
||||
1. verificar se a funcionalidade está habilitada;
|
||||
2. criar o manager quando necessário;
|
||||
3. recuperar os fatos pela identidade;
|
||||
4. preencher o estado;
|
||||
5. injetar o contexto no prompt.
|
||||
|
||||
Campos adicionados ao estado:
|
||||
|
||||
```python
|
||||
long_term_memories: list[dict]
|
||||
long_term_memory_context: str
|
||||
long_term_memory_write_result: dict
|
||||
```
|
||||
|
||||
### Inicialização no AgentWorkflow
|
||||
|
||||
O manager deve ser criado no `AgentWorkflow`:
|
||||
|
||||
```python
|
||||
self.long_term_memory_manager = create_long_term_memory_manager(
|
||||
settings,
|
||||
telemetry=telemetry,
|
||||
)
|
||||
```
|
||||
|
||||
### Inicialização correta dos agentes
|
||||
|
||||
O `long_term_memory_manager` não deve ser passado pelo `agent_kwargs` caso os construtores de `BillingAgent`, `ProductAgent`, `OrdersAgent` e `SupportAgent` não declarem esse parâmetro.
|
||||
|
||||
Esta inicialização causa erro:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"settings": settings,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
"long_term_memory_manager": self.long_term_memory_manager,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
```
|
||||
|
||||
Erro resultante:
|
||||
|
||||
```text
|
||||
TypeError: BillingAgent.__init__() got an unexpected keyword argument
|
||||
'long_term_memory_manager'
|
||||
```
|
||||
|
||||
A forma recomendada é criar os agentes com a assinatura já existente e injetar o manager como atributo após a inicialização:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"tool_router": getattr(self, "tool_router", None),
|
||||
"rag_service": self.rag_service,
|
||||
"cache": self.cache,
|
||||
"settings": settings,
|
||||
"observer": self.observer,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
self.product = ProductAgent(llm, **agent_kwargs)
|
||||
self.orders = OrdersAgent(llm, **agent_kwargs)
|
||||
self.support = SupportAgent(llm, **agent_kwargs)
|
||||
|
||||
for agent in (
|
||||
self.billing,
|
||||
self.product,
|
||||
self.orders,
|
||||
self.support,
|
||||
):
|
||||
agent.long_term_memory_manager = self.long_term_memory_manager
|
||||
```
|
||||
|
||||
Essa abordagem evita alterar os construtores de todos os agentes e mantém a funcionalidade encapsulada no framework.
|
||||
|
||||
### Configuração do LangGraph
|
||||
|
||||
Registre o nó:
|
||||
|
||||
```python
|
||||
builder.add_node(
|
||||
"persist_long_term_memory",
|
||||
self._node(
|
||||
"persist_long_term_memory",
|
||||
self.persist_long_term_memory,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Altere o fluxo:
|
||||
|
||||
```python
|
||||
builder.add_edge(
|
||||
"supervisor_review",
|
||||
"persist_long_term_memory",
|
||||
)
|
||||
builder.add_edge(
|
||||
"persist_long_term_memory",
|
||||
"persist",
|
||||
)
|
||||
```
|
||||
|
||||
Implemente o método:
|
||||
|
||||
```python
|
||||
async def persist_long_term_memory(
|
||||
self,
|
||||
state: AgentState,
|
||||
) -> dict[str, object]:
|
||||
result = await self.long_term_memory_manager.persist_turn(state)
|
||||
|
||||
return {
|
||||
"long_term_memory_write_result": result,
|
||||
}
|
||||
```
|
||||
|
||||
Fluxo final:
|
||||
|
||||
```text
|
||||
supervisor_review
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
persist
|
||||
```
|
||||
|
||||
### Variáveis de ambiente
|
||||
|
||||
```env
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Caminho do banco SQLite
|
||||
|
||||
O caminho relativo é resolvido a partir do diretório em que o backend é iniciado.
|
||||
|
||||
Para evitar que bancos diferentes sejam criados acidentalmente, prefira um caminho absoluto em ambientes de desenvolvimento:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db
|
||||
```
|
||||
|
||||
Crie a pasta antes de iniciar:
|
||||
|
||||
```bash
|
||||
mkdir -p data
|
||||
```
|
||||
|
||||
### Como testar
|
||||
|
||||
### Teste 1 — Gravação
|
||||
|
||||
Envie:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-a",
|
||||
"customer_key": "11999999999",
|
||||
"message": "Me chame de Cris. Minha linguagem preferida é Python e meu projeto atual se chama Atlas."
|
||||
}
|
||||
```
|
||||
|
||||
### Teste 2 — Recuperação em outra sessão
|
||||
|
||||
Utilize outro `session_id`, mantendo o mesmo `customer_key`:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-b",
|
||||
"customer_key": "11999999999",
|
||||
"message": "O que você lembra sobre mim, minhas preferências e meu projeto?"
|
||||
}
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```text
|
||||
Seu nome preferido é Cris.
|
||||
Sua linguagem preferida é Python.
|
||||
Seu projeto atual se chama Atlas.
|
||||
```
|
||||
|
||||
### Teste 3 — Isolamento
|
||||
|
||||
Utilize outro cliente:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-c",
|
||||
"customer_key": "outro-cliente",
|
||||
"message": "Qual é meu nome preferido e qual é meu projeto atual?"
|
||||
}
|
||||
```
|
||||
|
||||
Os dados de `11999999999` não devem aparecer.
|
||||
|
||||
### Teste 4 — Reinicialização do frontend
|
||||
|
||||
Reinicie ou resete o frontend e confirme que ele continua enviando o mesmo `customer_key`.
|
||||
|
||||
A memória deve sobreviver à troca do `session_id`. O reset do frontend não apaga o SQLite.
|
||||
|
||||
### Teste 5 — Reinicialização do backend
|
||||
|
||||
Reinicie o Uvicorn e repita a consulta.
|
||||
|
||||
Com:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
a memória deve continuar disponível.
|
||||
|
||||
Com:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
a memória será perdida quando o processo for encerrado.
|
||||
|
||||
### Verificação direta no SQLite
|
||||
|
||||
Localize o banco:
|
||||
|
||||
```bash
|
||||
find . -name "agent_framework.db" -type f
|
||||
```
|
||||
|
||||
Abra:
|
||||
|
||||
```bash
|
||||
sqlite3 ./data/agent_framework.db
|
||||
```
|
||||
|
||||
Consulte:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
tenant_id,
|
||||
agent_id,
|
||||
customer_key,
|
||||
memory_type,
|
||||
memory_key,
|
||||
memory_value,
|
||||
confidence,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM agentfw_long_term_memory
|
||||
ORDER BY updated_at DESC;
|
||||
```
|
||||
|
||||
### Critérios de sucesso
|
||||
|
||||
A implementação está funcionando quando:
|
||||
|
||||
- a memória é recuperada com outro `session_id`;
|
||||
- o mesmo `customer_key` recupera os fatos anteriores;
|
||||
- outro `customer_key` não acessa esses fatos;
|
||||
- reiniciar o frontend não apaga a memória;
|
||||
- reiniciar o backend não apaga a memória quando o provider é SQLite;
|
||||
- o nó `persist_long_term_memory` é executado;
|
||||
- o prompt recebe `long_term_memory_context`.
|
||||
|
||||
### Boas práticas
|
||||
|
||||
- Persistir somente fatos duradouros.
|
||||
- Não armazenar a conversa completa como Long-Term Memory.
|
||||
- Isolar dados por `tenant_id`, `agent_id` e `customer_key`.
|
||||
- Não utilizar `session_id` como identidade permanente do usuário.
|
||||
- Persistir somente depois das validações finais.
|
||||
- Evitar armazenar resultados temporários de ferramentas.
|
||||
- Registrar telemetria de leitura, escrita, atualização e falha.
|
||||
- Definir políticas de retenção e exclusão.
|
||||
- Usar caminho absoluto para SQLite em ambientes com múltiplos diretórios de execução.
|
||||
- Migrar para um banco corporativo em ambientes de produção e alta disponibilidade.
|
||||
|
||||
### Limitações da implementação de referência
|
||||
|
||||
A implementação atual utiliza extração baseada em regras e SQLite como provider de referência.
|
||||
|
||||
Evoluções recomendadas:
|
||||
|
||||
- extração de fatos com LLM;
|
||||
- memória semântica com vetores;
|
||||
- memória episódica;
|
||||
- expiração e versionamento;
|
||||
- deduplicação semântica;
|
||||
- política de consentimento;
|
||||
- API de consulta e exclusão;
|
||||
- provider Oracle Autonomous Database;
|
||||
- criptografia e classificação de dados sensíveis.
|
||||
11178
agent_framework_oci/README.md
Normal file
11178
agent_framework_oci/README.md
Normal file
File diff suppressed because it is too large
Load Diff
11084
agent_framework_oci/README_en.md
Normal file
11084
agent_framework_oci/README_en.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
# Disclaimer — template de autenticação
|
||||
|
||||
Este conteúdo é um **template de referência técnica** criado para demonstrar como integrar mecanismos genéricos de autenticação ao Agent Framework OCI, ao Agent Gateway, ao MCP Gateway e a aplicações FastAPI independentes.
|
||||
|
||||
O código, os arquivos YAML, as variáveis de ambiente, os providers, as políticas por rota e os exemplos de deployment **não constituem uma implementação final ou automaticamente adequada para produção**. Cada projeto deve lapidar e adaptar a solução conforme sua arquitetura, seus fluxos de confiança e suas exigências de segurança.
|
||||
|
||||
Antes de usar em homologação ou produção, é responsabilidade da equipe do projeto avaliar e implementar, conforme aplicável:
|
||||
|
||||
- integração com o provedor corporativo de identidade;
|
||||
- definição de autenticação e autorização por sistema, rota, método, tenant, role e scope;
|
||||
- armazenamento, distribuição e rotação de credenciais e chaves;
|
||||
- TLS ou mTLS e proteção das comunicações internas e externas;
|
||||
- bloqueio de acessos que contornem gateways ou proxies de confiança;
|
||||
- validação de issuer, audience, algoritmo, expiração e revogação de tokens;
|
||||
- proteção contra replay, brute force, credential stuffing e abuso de endpoints;
|
||||
- rate limiting, timeout, circuit breaker e controles de disponibilidade;
|
||||
- mascaramento de dados sensíveis em logs, traces e mensagens de erro;
|
||||
- auditoria, observabilidade, alertas e resposta a incidentes;
|
||||
- requisitos legais, regulatórios e políticas corporativas;
|
||||
- threat modeling, security review, testes de integração, testes de carga e testes de segurança.
|
||||
|
||||
Os exemplos de Basic Authentication, API Key, Bearer estático, JWT, OAuth2 Introspection e Trusted Proxy devem ser entendidos como pontos de extensão. A seleção e a configuração finais dependem do cliente, da infraestrutura e do modelo de risco.
|
||||
|
||||
A promoção para produção deve ocorrer somente após aprovação formal das equipes responsáveis por arquitetura, segurança, infraestrutura e operação.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Implementação técnica
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Template de referência — requer adequação antes do uso produtivo.**
|
||||
> Esta implementação demonstra pontos de extensão, providers, middleware e exemplos de configuração para autenticação. Ela não deve ser considerada uma solução pronta para produção nem substitui o desenho de segurança do projeto. Antes da implantação, a equipe responsável deve revisar, testar e adaptar o código às políticas corporativas, ao modelo de identidade, à topologia de rede, à gestão e rotação de segredos, aos requisitos regulatórios, à observabilidade, à alta disponibilidade e ao processo de resposta a incidentes do ambiente do cliente. Recomenda-se executar security review, threat modeling, testes de integração e testes de segurança antes da homologação e da produção.
|
||||
## Biblioteca
|
||||
|
||||
`libs/agent_framework/src/agent_framework/security` contém:
|
||||
|
||||
- contratos e resultados de autenticação;
|
||||
- Basic, API Key, Bearer estático, JWT, OAuth2 Introspection e Trusted Proxy;
|
||||
- provider `none` para rotas públicas;
|
||||
- provider `deny` para default seguro;
|
||||
- middleware de provider único;
|
||||
- middleware de políticas por rota;
|
||||
- factory por ambiente ou mapping;
|
||||
- instalador reutilizável para qualquer app FastAPI.
|
||||
|
||||
## Integrações
|
||||
|
||||
- `apps/agent_gateway/app/main.py`: `AGENT_GATEWAY_AUTH_*`
|
||||
- `apps/mcp_gateway/app/main.py`: `MCP_GATEWAY_AUTH_*`
|
||||
- `Tuning-Performance/Authentication/agent_template_backend_authentication/app/main.py`: `AGENT_AUTH_*`
|
||||
|
||||
Nenhuma integração é obrigatória. A instalação ocorre somente quando `*_AUTH_ENABLED=true`, quando um modo diferente de `none` é configurado ou quando existe `*_AUTH_POLICIES_FILE`.
|
||||
|
||||
## Compatibilidade
|
||||
|
||||
`AuthenticationMiddleware` e `create_authentication_provider()` foram mantidos para compatibilidade. O caminho recomendado para novos projetos é `install_authentication()`.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Authentication
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Template de referência — requer adequação antes do uso produtivo.**
|
||||
> Esta implementação demonstra pontos de extensão, providers, middleware e exemplos de configuração para autenticação. Ela não deve ser considerada uma solução pronta para produção nem substitui o desenho de segurança do projeto. Antes da implantação, a equipe responsável deve revisar, testar e adaptar o código às políticas corporativas, ao modelo de identidade, à topologia de rede, à gestão e rotação de segredos, aos requisitos regulatórios, à observabilidade, à alta disponibilidade e ao processo de resposta a incidentes do ambiente do cliente. Recomenda-se executar security review, threat modeling, testes de integração e testes de segurança antes da homologação e da produção.
|
||||
Implementação de referência para autenticação transversal no Agent Framework OCI.
|
||||
|
||||
Inclui:
|
||||
|
||||
- providers genéricos em `libs/agent_framework/security`;
|
||||
- instalação opcional por `install_authentication()`;
|
||||
- políticas por rota, método, roles e scopes;
|
||||
- integração opcional em `apps/agent_gateway`;
|
||||
- integração opcional em `apps/mcp_gateway`;
|
||||
- backend independente autenticado em `agent_template_backend_authentication`;
|
||||
- exemplos YAML sem secrets embutidos;
|
||||
- manual completo no diretório `docs` do agente.
|
||||
|
||||
A implementação não pressupõe o uso de gateways. Cada fronteira HTTP pode ativar autenticação com um prefixo de ambiente isolado.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Select: none | basic | api_key | bearer_static | jwt | oauth2_introspection | trusted_proxy
|
||||
AGENT_AUTH_MODE=basic
|
||||
|
||||
# Public endpoints. Avoid exposing /debug in production.
|
||||
AGENT_AUTH_PUBLIC_PATHS=/health,/docs,/openapi.json,/redoc
|
||||
AGENT_AUTH_PUBLIC_PREFIXES=
|
||||
|
||||
# HTTP Basic - TIA -> Agent example
|
||||
AGENT_AUTH_BASIC_CLIENT_ID=tia-contas
|
||||
# Supported formats: plain:value | sha256:hex | pbkdf2_sha256:iterations:salt:digest
|
||||
AGENT_AUTH_BASIC_SECRET_HASH=pbkdf2_sha256:310000:replace-salt:replace-digest
|
||||
AGENT_AUTH_BASIC_REALM=agent-contas
|
||||
|
||||
# API Key
|
||||
# AGENT_AUTH_API_KEY_HEADER=x-api-key
|
||||
# AGENT_AUTH_API_KEY_HASH=sha256:replace-hex
|
||||
# AGENT_AUTH_API_KEY_PRINCIPAL=tia
|
||||
|
||||
# Static Bearer token
|
||||
# AGENT_AUTH_BEARER_TOKEN_HASH=sha256:replace-hex
|
||||
# AGENT_AUTH_BEARER_PRINCIPAL=tia
|
||||
|
||||
# JWT / OIDC access token validation. For production, prefer asymmetric algorithms.
|
||||
# AGENT_AUTH_JWT_KEY=-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----
|
||||
# AGENT_AUTH_JWT_ALGORITHMS=RS256
|
||||
# AGENT_AUTH_JWT_AUDIENCE=agent-contas
|
||||
# AGENT_AUTH_JWT_ISSUER=https://identity.example.com/
|
||||
|
||||
# OAuth2 opaque-token introspection
|
||||
# AGENT_AUTH_OAUTH2_INTROSPECTION_URL=https://identity.example.com/oauth2/introspect
|
||||
# AGENT_AUTH_OAUTH2_CLIENT_ID=agent-contas
|
||||
# AGENT_AUTH_OAUTH2_CLIENT_SECRET=replace-from-vault
|
||||
# AGENT_AUTH_OAUTH2_TIMEOUT_SECONDS=5
|
||||
|
||||
# Authentication delegated to API Gateway / service mesh.
|
||||
# Only trust these headers when direct access to the pod is blocked.
|
||||
# AGENT_AUTH_PROXY_SUBJECT_HEADER=x-authenticated-subject
|
||||
# AGENT_AUTH_PROXY_SHARED_SECRET_HEADER=x-internal-auth
|
||||
# AGENT_AUTH_PROXY_SHARED_SECRET_HASH=sha256:replace-hex
|
||||
@@ -0,0 +1,6 @@
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY agent_framework /agent_framework
|
||||
COPY agent_template_backend /app
|
||||
RUN pip install --no-cache-dir -e /agent_framework -r requirements.txt
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
# Agent Template Backend Enterprise
|
||||
|
||||
Este folder é uma cópia completa do `agent_template_backend`, sem cortes de
|
||||
arquitetura. Ele mantém workflow, router, output supervisor, guardrails,
|
||||
analytics, observer, MCP, memória, checkpoints e configurações.
|
||||
|
||||
A diferença é que a lógica de negócio dos agentes de exemplo foi removida da
|
||||
execução e preservada comentada nos próprios arquivos:
|
||||
|
||||
- `app/agents/billing_agent.py`
|
||||
- `app/agents/product_agent.py`
|
||||
- `app/agents/orders_agent.py`
|
||||
- `app/agents/support_agent.py`
|
||||
|
||||
## O que o desenvolvedor deve alterar
|
||||
|
||||
1. Escolher ou criar um agente em `app/agents/`.
|
||||
2. Implementar o método `run()`.
|
||||
3. Ajustar prompts e tools, se necessário.
|
||||
4. Emitir ICs de negócio relevantes para a jornada.
|
||||
5. Manter NOC/GRL nos pontos operacionais e de guardrails.
|
||||
|
||||
## O que já está integrado
|
||||
|
||||
- `AgentObserver`
|
||||
- `observer.emit_ic()`
|
||||
- `observer.emit_noc()`
|
||||
- `observer.emit_grl()`
|
||||
- `AnalyticsPublisher`
|
||||
- OCI Streaming
|
||||
- GCP Pub/Sub
|
||||
- OutputSupervisor
|
||||
- GuardrailPipeline com suporte a execução paralela/fail-fast no framework
|
||||
- MCP Tool Router
|
||||
- LangGraph
|
||||
- Memory
|
||||
- Checkpoint
|
||||
- Langfuse / OpenTelemetry
|
||||
|
||||
## Exemplos adicionados
|
||||
|
||||
Veja `app/examples/`:
|
||||
|
||||
- `ic_examples.py`
|
||||
- `noc_examples.py`
|
||||
- `grl_examples.py`
|
||||
- `mcp_examples.py`
|
||||
- `observer_examples.py`
|
||||
|
||||
## Convenção rápida
|
||||
|
||||
- IC = evento de negócio / curadoria / informacional.
|
||||
- NOC = evento operacional / saúde técnica.
|
||||
- GRL = evento de guardrail / segurança / validação.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
# Agentes do Template Backend Enterprise
|
||||
|
||||
Os arquivos desta pasta preservam a estrutura real esperada pelo workflow, mas
|
||||
não executam lógica de negócio pronta.
|
||||
|
||||
Cada agente mostra:
|
||||
|
||||
- como emitir IC;
|
||||
- como emitir NOC;
|
||||
- como emitir GRL;
|
||||
- como coletar MCP via `_collect_tool_context()`;
|
||||
- como recuperar RAG via `_retrieve_rag_context()`;
|
||||
- onde chamar LLM/cache.
|
||||
|
||||
A implementação original do exemplo está comentada no fim de cada arquivo.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,129 @@
|
||||
from app.agents.prompting import apply_agent_profile_prompt
|
||||
from app.agents.runtime import AgentRuntimeMixin
|
||||
|
||||
|
||||
class BillingAgent(AgentRuntimeMixin):
|
||||
name = "billingAgent"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
telemetry=None,
|
||||
tool_router=None,
|
||||
rag_service=None,
|
||||
cache=None,
|
||||
settings=None,
|
||||
observer=None,
|
||||
memory=None,
|
||||
summary_memory=None,
|
||||
):
|
||||
self.llm = llm
|
||||
self.telemetry = telemetry
|
||||
self.tool_router = tool_router
|
||||
self.rag_service = rag_service
|
||||
self.cache = cache
|
||||
self.settings = settings
|
||||
self.observer = observer
|
||||
self.memory = memory
|
||||
self.summary_memory = summary_memory
|
||||
|
||||
async def run(self, state):
|
||||
await self._emit_ic(
|
||||
"IC.BILLING_AGENT_STARTED",
|
||||
state,
|
||||
{"business_component": "faturas"},
|
||||
component="agent.billing.start",
|
||||
)
|
||||
|
||||
tool_context = await self._collect_tool_context(state)
|
||||
if tool_context:
|
||||
await self._emit_ic(
|
||||
"IC.BILLING_MCP_CONTEXT_COLLECTED",
|
||||
state,
|
||||
{"tool_result_count": len(tool_context)},
|
||||
component="agent.billing.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="BillingAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
"IC.BILLING_RAG_CONTEXT_RETRIEVED",
|
||||
state,
|
||||
{
|
||||
"document_count": rag_metadata.get("document_count"),
|
||||
"graph_neighbors": rag_metadata.get("graph_neighbors"),
|
||||
"latency_ms": rag_metadata.get("latency_ms"),
|
||||
},
|
||||
component="agent.billing.rag",
|
||||
)
|
||||
|
||||
# Prepara ConversationSummaryMemory antes de montar o prompt.
|
||||
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=apply_agent_profile_prompt(
|
||||
state,
|
||||
"Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.",
|
||||
),
|
||||
mcp_results=tool_context,
|
||||
rag_context=rag_context,
|
||||
rag_metadata=rag_metadata,
|
||||
)
|
||||
|
||||
answer = await self._invoke_llm_cached(state, "BillingAgent", messages)
|
||||
result = {
|
||||
"answer": f"[BillingAgent] {answer}",
|
||||
"next_state": "BILLING_ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
"IC.BILLING_AGENT_COMPLETED",
|
||||
state,
|
||||
{
|
||||
"answer_chars": len(result.get("answer") or ""),
|
||||
"has_mcp_results": bool(tool_context),
|
||||
"rag_enabled": bool(rag_metadata.get("enabled")),
|
||||
"memory_context": state.get("memory_context_metadata"),
|
||||
},
|
||||
component="agent.billing.completed",
|
||||
)
|
||||
return result
|
||||
|
||||
async def _collect_tool_context(self, state):
|
||||
return await self._collect_mcp_context(state)
|
||||
@@ -0,0 +1,129 @@
|
||||
from app.agents.prompting import apply_agent_profile_prompt
|
||||
from app.agents.runtime import AgentRuntimeMixin
|
||||
|
||||
|
||||
class OrdersAgent(AgentRuntimeMixin):
|
||||
name = "orders_agent"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
telemetry=None,
|
||||
tool_router=None,
|
||||
rag_service=None,
|
||||
cache=None,
|
||||
settings=None,
|
||||
observer=None,
|
||||
memory=None,
|
||||
summary_memory=None,
|
||||
):
|
||||
self.llm = llm
|
||||
self.telemetry = telemetry
|
||||
self.tool_router = tool_router
|
||||
self.rag_service = rag_service
|
||||
self.cache = cache
|
||||
self.settings = settings
|
||||
self.observer = observer
|
||||
self.memory = memory
|
||||
self.summary_memory = summary_memory
|
||||
|
||||
async def run(self, state):
|
||||
await self._emit_ic(
|
||||
"IC.ORDERS_AGENT_STARTED",
|
||||
state,
|
||||
{"business_component": "pedidos"},
|
||||
component="agent.orders.start",
|
||||
)
|
||||
|
||||
tool_context = await self._collect_tool_context(state)
|
||||
if tool_context:
|
||||
await self._emit_ic(
|
||||
"IC.ORDERS_MCP_CONTEXT_COLLECTED",
|
||||
state,
|
||||
{"tool_result_count": len(tool_context)},
|
||||
component="agent.orders.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="OrdersAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
"IC.ORDERS_RAG_CONTEXT_RETRIEVED",
|
||||
state,
|
||||
{
|
||||
"document_count": rag_metadata.get("document_count"),
|
||||
"graph_neighbors": rag_metadata.get("graph_neighbors"),
|
||||
"latency_ms": rag_metadata.get("latency_ms"),
|
||||
},
|
||||
component="agent.orders.rag",
|
||||
)
|
||||
|
||||
# Prepara ConversationSummaryMemory antes de montar o prompt.
|
||||
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=apply_agent_profile_prompt(
|
||||
state,
|
||||
"Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.",
|
||||
),
|
||||
mcp_results=tool_context,
|
||||
rag_context=rag_context,
|
||||
rag_metadata=rag_metadata,
|
||||
)
|
||||
|
||||
answer = await self._invoke_llm_cached(state, "OrdersAgent", messages)
|
||||
result = {
|
||||
"answer": f"[OrdersAgent] {answer}",
|
||||
"next_state": "ORDER_ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
"IC.ORDERS_AGENT_COMPLETED",
|
||||
state,
|
||||
{
|
||||
"answer_chars": len(result.get("answer") or ""),
|
||||
"has_mcp_results": bool(tool_context),
|
||||
"rag_enabled": bool(rag_metadata.get("enabled")),
|
||||
"memory_context": state.get("memory_context_metadata"),
|
||||
},
|
||||
component="agent.orders.completed",
|
||||
)
|
||||
return result
|
||||
|
||||
async def _collect_tool_context(self, state):
|
||||
return await self._collect_mcp_context(state)
|
||||
@@ -0,0 +1,129 @@
|
||||
from app.agents.prompting import apply_agent_profile_prompt
|
||||
from app.agents.runtime import AgentRuntimeMixin
|
||||
|
||||
|
||||
class ProductAgent(AgentRuntimeMixin):
|
||||
name = "productAgent"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
telemetry=None,
|
||||
tool_router=None,
|
||||
rag_service=None,
|
||||
cache=None,
|
||||
settings=None,
|
||||
observer=None,
|
||||
memory=None,
|
||||
summary_memory=None,
|
||||
):
|
||||
self.llm = llm
|
||||
self.telemetry = telemetry
|
||||
self.tool_router = tool_router
|
||||
self.rag_service = rag_service
|
||||
self.cache = cache
|
||||
self.settings = settings
|
||||
self.observer = observer
|
||||
self.memory = memory
|
||||
self.summary_memory = summary_memory
|
||||
|
||||
async def run(self, state):
|
||||
await self._emit_ic(
|
||||
"IC.PRODUCT_AGENT_STARTED",
|
||||
state,
|
||||
{"business_component": "produtos"},
|
||||
component="agent.product.start",
|
||||
)
|
||||
|
||||
tool_context = await self._collect_tool_context(state)
|
||||
if tool_context:
|
||||
await self._emit_ic(
|
||||
"IC.PRODUCT_MCP_CONTEXT_COLLECTED",
|
||||
state,
|
||||
{"tool_result_count": len(tool_context)},
|
||||
component="agent.product.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="ProductAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
"IC.PRODUCT_RAG_CONTEXT_RETRIEVED",
|
||||
state,
|
||||
{
|
||||
"document_count": rag_metadata.get("document_count"),
|
||||
"graph_neighbors": rag_metadata.get("graph_neighbors"),
|
||||
"latency_ms": rag_metadata.get("latency_ms"),
|
||||
},
|
||||
component="agent.product.rag",
|
||||
)
|
||||
|
||||
# Prepara ConversationSummaryMemory antes de montar o prompt.
|
||||
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=apply_agent_profile_prompt(
|
||||
state,
|
||||
"Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.",
|
||||
),
|
||||
mcp_results=tool_context,
|
||||
rag_context=rag_context,
|
||||
rag_metadata=rag_metadata,
|
||||
)
|
||||
|
||||
answer = await self._invoke_llm_cached(state, "ProductAgent", messages)
|
||||
result = {
|
||||
"answer": f"[ProductAgent] {answer}",
|
||||
"next_state": "PRODUCT_ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
"IC.PRODUCT_AGENT_COMPLETED",
|
||||
state,
|
||||
{
|
||||
"answer_chars": len(result.get("answer") or ""),
|
||||
"has_mcp_results": bool(tool_context),
|
||||
"rag_enabled": bool(rag_metadata.get("enabled")),
|
||||
"memory_context": state.get("memory_context_metadata"),
|
||||
},
|
||||
component="agent.product.completed",
|
||||
)
|
||||
return result
|
||||
|
||||
async def _collect_tool_context(self, state):
|
||||
return await self._collect_mcp_context(state)
|
||||
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def apply_agent_profile_prompt(state: dict, default_prompt: str) -> str:
|
||||
"""Adiciona o prefixo de prompt configurado para o agent_template selecionado.
|
||||
|
||||
Cada agent_id pode definir metadata.system_prefix em config/agents.yaml. Isso
|
||||
mantém prompts isolados sem duplicar o código dos agentes especializados.
|
||||
"""
|
||||
profile = state.get("agent_profile") or (state.get("context") or {}).get("agent_profile") or {}
|
||||
metadata = profile.get("metadata") or {}
|
||||
prefix = (metadata.get("system_prefix") or "").strip()
|
||||
if not prefix:
|
||||
return default_prompt
|
||||
return f"{prefix}\n\n{default_prompt}"
|
||||
@@ -0,0 +1,7 @@
|
||||
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
|
||||
|
||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
||||
@@ -0,0 +1,129 @@
|
||||
from app.agents.prompting import apply_agent_profile_prompt
|
||||
from app.agents.runtime import AgentRuntimeMixin
|
||||
|
||||
|
||||
class SupportAgent(AgentRuntimeMixin):
|
||||
name = "support_agent"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
telemetry=None,
|
||||
tool_router=None,
|
||||
rag_service=None,
|
||||
cache=None,
|
||||
settings=None,
|
||||
observer=None,
|
||||
memory=None,
|
||||
summary_memory=None,
|
||||
):
|
||||
self.llm = llm
|
||||
self.telemetry = telemetry
|
||||
self.tool_router = tool_router
|
||||
self.rag_service = rag_service
|
||||
self.cache = cache
|
||||
self.settings = settings
|
||||
self.observer = observer
|
||||
self.memory = memory
|
||||
self.summary_memory = summary_memory
|
||||
|
||||
async def run(self, state):
|
||||
await self._emit_ic(
|
||||
"IC.SUPPORT_AGENT_STARTED",
|
||||
state,
|
||||
{"business_component": "suporte"},
|
||||
component="agent.support.start",
|
||||
)
|
||||
|
||||
tool_context = await self._collect_tool_context(state)
|
||||
if tool_context:
|
||||
await self._emit_ic(
|
||||
"IC.SUPPORT_MCP_CONTEXT_COLLECTED",
|
||||
state,
|
||||
{"tool_result_count": len(tool_context)},
|
||||
component="agent.support.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="SupportAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
"IC.SUPPORT_RAG_CONTEXT_RETRIEVED",
|
||||
state,
|
||||
{
|
||||
"document_count": rag_metadata.get("document_count"),
|
||||
"graph_neighbors": rag_metadata.get("graph_neighbors"),
|
||||
"latency_ms": rag_metadata.get("latency_ms"),
|
||||
},
|
||||
component="agent.support.rag",
|
||||
)
|
||||
|
||||
# Prepara ConversationSummaryMemory antes de montar o prompt.
|
||||
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=apply_agent_profile_prompt(
|
||||
state,
|
||||
"Você é um agente de suporte de varejo para troca, devolução e garantia.",
|
||||
),
|
||||
mcp_results=tool_context,
|
||||
rag_context=rag_context,
|
||||
rag_metadata=rag_metadata,
|
||||
)
|
||||
|
||||
answer = await self._invoke_llm_cached(state, "SupportAgent", messages)
|
||||
result = {
|
||||
"answer": f"[SupportAgent] {answer}",
|
||||
"next_state": "SUPPORT_ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
"IC.SUPPORT_AGENT_COMPLETED",
|
||||
state,
|
||||
{
|
||||
"answer_chars": len(result.get("answer") or ""),
|
||||
"has_mcp_results": bool(tool_context),
|
||||
"rag_enabled": bool(rag_metadata.get("enabled")),
|
||||
"memory_context": state.get("memory_context_metadata"),
|
||||
},
|
||||
component="agent.support.completed",
|
||||
)
|
||||
return result
|
||||
|
||||
async def _collect_tool_context(self, state):
|
||||
return await self._collect_mcp_context(state)
|
||||
@@ -0,0 +1 @@
|
||||
"""Exemplos de uso do template backend enterprise."""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
"""Exemplos de GRL.
|
||||
|
||||
GRL representa eventos de guardrails. Em regra, GRL.001..GRL.009 são emitidos
|
||||
pelo pipeline de guardrails e pelo OutputSupervisor do framework. Use emissão
|
||||
manual apenas para validações customizadas do agente.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
async def exemplo_guardrail_observado(observer: Any, state: dict[str, Any], rail_code: str, reason: str) -> None:
|
||||
await observer.emit_grl(
|
||||
"OBSERVE",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"rail_code": rail_code,
|
||||
"reason": reason,
|
||||
},
|
||||
component="examples.grl",
|
||||
)
|
||||
|
||||
|
||||
async def exemplo_guardrail_block(observer: Any, state: dict[str, Any], rail_code: str, reason: str) -> None:
|
||||
await observer.emit_grl(
|
||||
"004",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"rail_code": rail_code,
|
||||
"reason": reason,
|
||||
"action": "block",
|
||||
},
|
||||
component="examples.grl",
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Exemplos de IC - Item de Controle.
|
||||
|
||||
ICs representam eventos de negócio. Eles alimentam Informacional, Curadoria,
|
||||
analytics, BigQuery ou qualquer publisher configurado no framework.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
async def exemplo_fatura_consultada(observer: Any, state: dict[str, Any], invoice_id: str) -> None:
|
||||
await observer.emit_ic(
|
||||
"IC.FATURA_CONSULTADA",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"invoice_id": invoice_id,
|
||||
},
|
||||
component="examples.ic",
|
||||
)
|
||||
|
||||
|
||||
async def exemplo_acao_concluida(observer: Any, state: dict[str, Any], action_name: str, ok: bool) -> None:
|
||||
await observer.emit_ic(
|
||||
"IC.ACAO_CONCLUIDA",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"action_name": action_name,
|
||||
"ok": ok,
|
||||
},
|
||||
component="examples.ic",
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Exemplos de MCP + IC.
|
||||
|
||||
O AgentRuntimeMixin já possui _collect_mcp_context(), mas este arquivo mostra o
|
||||
padrão para chamadas explícitas ao tool_router quando necessário.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
async def exemplo_chamada_mcp(tool_router: Any, observer: Any, state: dict[str, Any], tool_name: str, payload: dict[str, Any]) -> Any:
|
||||
session_id = state.get("conversation_key") or state.get("session_id")
|
||||
|
||||
await observer.emit_ic(
|
||||
"IC.MCP_TOOL_CALLED",
|
||||
{
|
||||
"session_id": session_id,
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"tool_name": tool_name,
|
||||
},
|
||||
component="examples.mcp",
|
||||
)
|
||||
|
||||
result = await tool_router.call(
|
||||
tool_name,
|
||||
payload,
|
||||
business_context=(state.get("context") or {}).get("business_context") or {},
|
||||
original_context=state.get("context") or {},
|
||||
)
|
||||
|
||||
await observer.emit_ic(
|
||||
"IC.TOOL_CALLED",
|
||||
{
|
||||
"session_id": session_id,
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"tool_name": tool_name,
|
||||
"ok": getattr(result, "ok", None),
|
||||
},
|
||||
component="examples.mcp",
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Exemplos de NOC.
|
||||
|
||||
NOC representa telemetria operacional. O workflow do template já emite NOC.001,
|
||||
NOC.005 e NOC.006. Estes exemplos mostram eventos adicionais que a squad pode
|
||||
emitir em pontos críticos.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
async def exemplo_api_invalida(observer: Any, state: dict[str, Any], api_url: str, status_code: int, latency_ms: int) -> None:
|
||||
await observer.emit_noc(
|
||||
"002",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"apiUrl": api_url,
|
||||
"statusCode": status_code,
|
||||
"latencyMs": latency_ms,
|
||||
},
|
||||
component="examples.noc",
|
||||
)
|
||||
|
||||
|
||||
async def exemplo_latencia_banco(observer: Any, state: dict[str, Any], resource_name: str, latency_ms: int) -> None:
|
||||
await observer.emit_noc(
|
||||
"003",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"resourceName": resource_name,
|
||||
"latencyMs": latency_ms,
|
||||
},
|
||||
component="examples.noc",
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Resumo prático do Observer corporativo.
|
||||
|
||||
Use este arquivo como cola rápida para IC, NOC e GRL.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
async def emitir_eventos_basicos(observer: Any, state: dict[str, Any]) -> None:
|
||||
session_id = state.get("conversation_key") or state.get("session_id")
|
||||
|
||||
await observer.emit_ic(
|
||||
"IC.EXEMPLO_NEGOCIO",
|
||||
{"session_id": session_id, "agent_id": state.get("agent_id")},
|
||||
component="examples.observer",
|
||||
)
|
||||
|
||||
await observer.emit_noc(
|
||||
"EXEMPLO_OPERACIONAL",
|
||||
{"session_id": session_id, "agent_id": state.get("agent_id")},
|
||||
component="examples.observer",
|
||||
)
|
||||
|
||||
await observer.emit_grl(
|
||||
"OBSERVE",
|
||||
{"session_id": session_id, "agent_id": state.get("agent_id"), "rail_code": "CUSTOM"},
|
||||
component="examples.observer",
|
||||
)
|
||||
@@ -0,0 +1,558 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from uuid import uuid4
|
||||
import time
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agent_framework.channels.base import ChannelResponse
|
||||
from agent_framework.channels.gateway import ChannelGateway
|
||||
from agent_framework.config.agent_registry import AgentProfileRegistry
|
||||
from agent_framework.config.settings import settings
|
||||
from agent_framework.analytics.factory import create_analytics_publisher
|
||||
from agent_framework.observer import configure as configure_global_observer
|
||||
from agent_framework.llm.providers import create_llm
|
||||
from agent_framework.memory.message_history import create_memory
|
||||
from agent_framework.memory.summary_memory import create_conversation_summary_memory
|
||||
from agent_framework.mcp.tool_router import create_mcp_tool_router
|
||||
from agent_framework.models.identity import AgentIdentity
|
||||
from agent_framework.identity import IdentityResolver, BusinessContext
|
||||
from agent_framework.models.session import ChatMessage, SessionContext
|
||||
from agent_framework.observability.telemetry import Telemetry
|
||||
from agent_framework.observability.context import set_observability_context, clear_observability_context
|
||||
from agent_framework.repositories.session_repository import create_session_repository
|
||||
from agent_framework.checkpoints.checkpoint_repository import create_checkpoint_repository
|
||||
from agent_framework.cache.cache import create_cache
|
||||
from agent_framework.billing.usage_repository import create_usage_repository
|
||||
from agent_framework.sse.events import SSEHub
|
||||
from agent_framework.security import install_authentication
|
||||
from app.workflows.agent_graph import AgentWorkflow
|
||||
from app.observability.telemetry_observer import TelemetryBackedAgentObserver
|
||||
|
||||
logging.basicConfig(level=settings.LOG_LEVEL)
|
||||
logger = logging.getLogger("agent_template_backend")
|
||||
|
||||
app = FastAPI(title="Agent Template Backend FIRST-ready")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[o.strip() for o in settings.CORS_ORIGINS.split(",")],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Authentication is project-configured. The framework only provides generic providers.
|
||||
auth_enabled = install_authentication(app, prefix="AGENT_AUTH")
|
||||
|
||||
telemetry = Telemetry(settings)
|
||||
usage_repository = create_usage_repository(settings)
|
||||
llm = create_llm(settings, telemetry=telemetry, usage_repository=usage_repository)
|
||||
memory = create_memory(settings)
|
||||
summary_memory = create_conversation_summary_memory(settings, message_history=memory, llm=llm, telemetry=telemetry)
|
||||
sessions = create_session_repository(settings)
|
||||
checkpoints = create_checkpoint_repository(settings)
|
||||
cache = create_cache(settings, telemetry=telemetry)
|
||||
gateway = ChannelGateway(input_mode=settings.FRAMEWORK_CHANNEL_INPUT_MODE)
|
||||
analytics = create_analytics_publisher(settings)
|
||||
observer = TelemetryBackedAgentObserver(telemetry=telemetry)
|
||||
configure_global_observer({
|
||||
"enabled": getattr(settings, "ENABLE_ANALYTICS", False),
|
||||
"providers": getattr(settings, "ANALYTICS_PROVIDERS", "oci_streaming"),
|
||||
"topic_path": getattr(settings, "GCP_PUBSUB_TOPIC_PATH", None) or getattr(settings, "AGENT_PUBSUB_TOPIC", None),
|
||||
})
|
||||
tool_router = create_mcp_tool_router(settings, telemetry=telemetry)
|
||||
identity_resolver = IdentityResolver.from_yaml(settings.IDENTITY_CONFIG_PATH)
|
||||
agent_profiles = AgentProfileRegistry(settings)
|
||||
sse_hub = SSEHub(settings, telemetry=telemetry)
|
||||
workflow = AgentWorkflow(llm, memory, telemetry, analytics, settings, observer=observer, tool_router=tool_router, summary_memory=summary_memory)
|
||||
|
||||
logger.info("LLM provider carregado: %s", llm.__class__.__name__)
|
||||
logger.info("Langfuse habilitado: %s host=%s", telemetry.is_enabled(), settings.LANGFUSE_HOST)
|
||||
logger.info("Analytics habilitado: %s providers=%s", getattr(settings, "ENABLE_ANALYTICS", False), getattr(settings, "ANALYTICS_PROVIDERS", ""))
|
||||
logger.info("Agentes disponíveis: %s", [p.agent_id for p in agent_profiles.list_profiles()])
|
||||
logger.info("Framework channel input mode: %s", gateway.input_mode)
|
||||
logger.info("Authentication enabled=%s mode=%s policies=%s", auth_enabled, os.getenv("AGENT_AUTH_MODE", "none"), os.getenv("AGENT_AUTH_POLICIES_FILE"))
|
||||
|
||||
@app.middleware("http")
|
||||
async def observability_context_middleware(request: Request, call_next):
|
||||
clear_observability_context()
|
||||
request_id = request.headers.get("x-request-id") or str(uuid4())
|
||||
set_observability_context(
|
||||
request_id=request_id,
|
||||
channel=request.headers.get("x-channel") or "http",
|
||||
ura_call_id=request.headers.get("x-ura-call-id"),
|
||||
)
|
||||
started = time.time()
|
||||
try:
|
||||
response = await call_next(request)
|
||||
response.headers["x-request-id"] = request_id
|
||||
await telemetry.event("http.request.completed", {
|
||||
"method": request.method,
|
||||
"path": request.url.path,
|
||||
"status_code": response.status_code,
|
||||
"duration_ms": int((time.time() - started) * 1000),
|
||||
}, kind="http")
|
||||
return response
|
||||
except Exception as exc:
|
||||
await telemetry.event("http.request.failed", {
|
||||
"method": request.method,
|
||||
"path": request.url.path,
|
||||
"error": str(exc),
|
||||
"duration_ms": int((time.time() - started) * 1000),
|
||||
}, kind="http")
|
||||
raise
|
||||
finally:
|
||||
clear_observability_context()
|
||||
|
||||
|
||||
class GatewayRequest(BaseModel):
|
||||
channel: str = "web"
|
||||
payload: dict
|
||||
agent_id: str | None = None
|
||||
tenant_id: str | None = None
|
||||
|
||||
|
||||
def _metadata_value(payload: dict, key: str):
|
||||
metadata = payload.get("metadata")
|
||||
if isinstance(metadata, dict):
|
||||
return metadata.get(key)
|
||||
return None
|
||||
|
||||
|
||||
def _extract_workflow_id(payload: dict) -> str | None:
|
||||
return (
|
||||
payload.get("workflow_id")
|
||||
or payload.get("workflowId")
|
||||
or _metadata_value(payload, "workflow_id")
|
||||
or _metadata_value(payload, "workflowId")
|
||||
)
|
||||
|
||||
|
||||
def _format_root_span_name(template: str | None, values: dict) -> str:
|
||||
template = template or "agent.gateway_message"
|
||||
try:
|
||||
return template.format(**{k: v or "unknown" for k, v in values.items()})
|
||||
except Exception:
|
||||
logger.warning("LANGFUSE_ROOT_SPAN_NAME inválido: %s", template)
|
||||
return "agent.gateway_message"
|
||||
|
||||
|
||||
def _resolve_identity(req: GatewayRequest, msg) -> tuple[AgentIdentity, dict, BusinessContext, list[str]]:
|
||||
payload = req.payload or {}
|
||||
context = dict(msg.context or {})
|
||||
tenant_id = req.tenant_id or payload.get("tenant_id") or context.get("tenant_id") or "default"
|
||||
agent_id = req.agent_id or payload.get("agent_id") or context.get("agent_id") or agent_profiles.default_agent_id
|
||||
profile = agent_profiles.get(agent_id)
|
||||
|
||||
# 1) Identidade técnica do framework: isola tenant/agente/sessão.
|
||||
context.update({"tenant_id": tenant_id, "agent_id": profile.agent_id, "agent_profile": profile.__dict__})
|
||||
identity = AgentIdentity.from_context(context, session_id=msg.session_id)
|
||||
|
||||
# 2) Identidade de negócio: chaves canônicas vindas do front/canal.
|
||||
# Estas chaves são estáveis na sessão e seguem até agentes e MCP Router.
|
||||
previous_business_context = context.get("business_context") or context.get("identity") or {}
|
||||
business_context = identity_resolver.resolve(
|
||||
{**payload, **context},
|
||||
session_id=identity.conversation_key(),
|
||||
previous=previous_business_context,
|
||||
)
|
||||
missing_identity_keys = identity_resolver.validate(business_context)
|
||||
context.update({
|
||||
"business_context": business_context.model_dump(),
|
||||
"business_keys": business_context.to_context_dict(),
|
||||
"identity_missing": missing_identity_keys,
|
||||
"conversation_key": identity.conversation_key(),
|
||||
"original_session_id": msg.session_id,
|
||||
})
|
||||
return identity, context, business_context, missing_identity_keys
|
||||
|
||||
|
||||
async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False) -> dict:
|
||||
try:
|
||||
msg = await gateway.normalize(req.channel, req.payload)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
payload = req.payload or {}
|
||||
identity, normalized_context, business_context, missing_identity_keys = _resolve_identity(req, msg)
|
||||
agent_session_id = identity.conversation_key()
|
||||
message_id = payload.get("message_id") or str(uuid4())
|
||||
workflow_id = _extract_workflow_id(payload)
|
||||
set_observability_context(
|
||||
session_id=agent_session_id,
|
||||
user_id=msg.user_id,
|
||||
tenant_id=identity.tenant_id,
|
||||
agent_id=identity.agent_id,
|
||||
channel=msg.channel,
|
||||
message_id=message_id,
|
||||
workflow_id=workflow_id,
|
||||
ura_call_id=payload.get("ura_call_id") or normalized_context.get("ura_call_id") or business_context.interaction_key,
|
||||
)
|
||||
|
||||
stream = sse_hub.stream_for(agent_session_id)
|
||||
async with stream.lock:
|
||||
await sse_hub.emit(agent_session_id, "flow.start", {"session_id": agent_session_id, "message_id": message_id, "agent_id": identity.agent_id}) if emit_sse else None
|
||||
|
||||
session = await sessions.get(agent_session_id)
|
||||
if not session:
|
||||
context_fields = {
|
||||
k: v
|
||||
for k, v in normalized_context.items()
|
||||
if k in SessionContext.model_fields
|
||||
and k not in {"tenant_id", "agent_id", "session_id", "user_id", "channel", "channel_id"}
|
||||
}
|
||||
session = SessionContext(
|
||||
tenant_id=identity.tenant_id,
|
||||
agent_id=identity.agent_id,
|
||||
session_id=agent_session_id,
|
||||
user_id=msg.user_id,
|
||||
channel=msg.channel,
|
||||
channel_id=msg.channel_id,
|
||||
**context_fields,
|
||||
)
|
||||
|
||||
session.tenant_id = identity.tenant_id
|
||||
session.agent_id = identity.agent_id
|
||||
session.channel = msg.channel
|
||||
session.channel_id = msg.channel_id or session.channel_id
|
||||
await sessions.upsert(session)
|
||||
session.metadata = {
|
||||
**(session.metadata or {}),
|
||||
"business_context": business_context.model_dump(),
|
||||
"identity_missing": missing_identity_keys,
|
||||
"original_context": normalized_context,
|
||||
}
|
||||
await sse_hub.emit(agent_session_id, "session.upserted", {"session_id": agent_session_id, "business_context": business_context.model_dump()}) if emit_sse else None
|
||||
|
||||
await memory.append(
|
||||
agent_session_id,
|
||||
ChatMessage(
|
||||
role="user",
|
||||
content=msg.text,
|
||||
metadata={
|
||||
**normalized_context,
|
||||
"agent_id": identity.agent_id,
|
||||
"tenant_id": identity.tenant_id,
|
||||
"message_id": message_id,
|
||||
"business_context": business_context.model_dump(),
|
||||
"identity_missing": missing_identity_keys,
|
||||
},
|
||||
),
|
||||
)
|
||||
await sse_hub.emit(agent_session_id, "message.received", {"session_id": agent_session_id, "role": "user"}) if emit_sse else None
|
||||
history = [m.model_dump(mode="json") for m in await memory.list(agent_session_id)]
|
||||
|
||||
cms_input = {
|
||||
"channel": req.channel,
|
||||
"tenant_id": req.tenant_id,
|
||||
"agent_id": req.agent_id,
|
||||
"payload": payload,
|
||||
}
|
||||
trace_context = {
|
||||
"text": msg.text,
|
||||
"channel": msg.channel,
|
||||
"channel_id": msg.channel_id,
|
||||
"tenant_id": identity.tenant_id,
|
||||
"agent_id": identity.agent_id,
|
||||
"conversation_key": agent_session_id,
|
||||
"workflow_id": workflow_id,
|
||||
"message_id": message_id,
|
||||
"business_context": business_context.model_dump(),
|
||||
"identity_missing": missing_identity_keys,
|
||||
}
|
||||
root_span_name = _format_root_span_name(
|
||||
getattr(settings, "LANGFUSE_ROOT_SPAN_NAME", "agent.gateway_message"),
|
||||
{
|
||||
"workflow_id": workflow_id,
|
||||
"channel": msg.channel,
|
||||
"agent_id": identity.agent_id,
|
||||
"tenant_id": identity.tenant_id,
|
||||
},
|
||||
)
|
||||
root_tags = ["agent-template", msg.channel, f"agent:{identity.agent_id}", f"tenant:{identity.tenant_id}"]
|
||||
if workflow_id:
|
||||
root_tags.append(f"workflow:{workflow_id}")
|
||||
|
||||
async with telemetry.span(
|
||||
root_span_name,
|
||||
session_id=agent_session_id,
|
||||
user_id=session.user_id,
|
||||
channel=msg.channel,
|
||||
workflow_id=workflow_id,
|
||||
input=cms_input,
|
||||
tags=root_tags,
|
||||
_root_span=True,
|
||||
) as root_span:
|
||||
await telemetry.event("gateway.message.received", trace_context)
|
||||
await sse_hub.emit(agent_session_id, "workflow.started", trace_context) if emit_sse else None
|
||||
result = await workflow.ainvoke(
|
||||
{
|
||||
"tenant_id": identity.tenant_id,
|
||||
"agent_id": identity.agent_id,
|
||||
"session_id": agent_session_id,
|
||||
"conversation_key": agent_session_id,
|
||||
"workflow_id": workflow_id,
|
||||
"agent_profile": normalized_context["agent_profile"],
|
||||
# Chave estável de LTM. Nunca use session_id como identidade de longo prazo.
|
||||
"long_term_memory_subject_key": business_context.customer_key or session.user_id,
|
||||
"customer_key": business_context.customer_key,
|
||||
"user_id": session.user_id,
|
||||
"business_context": business_context.model_dump(),
|
||||
"user_text": msg.text,
|
||||
"history": history,
|
||||
"context": {
|
||||
**normalized_context,
|
||||
"session": session.model_dump(mode="json"),
|
||||
"original_session_id": msg.session_id,
|
||||
"session_id": agent_session_id,
|
||||
"conversation_key": agent_session_id,
|
||||
"workflow_id": workflow_id,
|
||||
"user_id": session.user_id,
|
||||
"channel": msg.channel,
|
||||
"message_id": message_id,
|
||||
"business_context": business_context.model_dump(),
|
||||
"business_keys": business_context.to_context_dict(),
|
||||
"identity_missing": missing_identity_keys,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
await checkpoints.put(agent_session_id, {"state": result, "message_id": message_id})
|
||||
await sse_hub.emit(agent_session_id, "workflow.completed", {"session_id": agent_session_id, "route": result.get("route"), "intent": result.get("intent")}) if emit_sse else None
|
||||
|
||||
answer = result.get("final_answer") or result.get("answer") or ""
|
||||
await memory.append(
|
||||
agent_session_id,
|
||||
ChatMessage(
|
||||
role="assistant",
|
||||
content=answer,
|
||||
metadata={
|
||||
"tenant_id": identity.tenant_id,
|
||||
"agent_id": identity.agent_id,
|
||||
"message_id": f"assistant-{message_id}",
|
||||
"route": result.get("route"),
|
||||
"intent": result.get("intent"),
|
||||
"route_decision": result.get("route_decision"),
|
||||
"judges": result.get("judge_results"),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
await telemetry.event(
|
||||
"gateway.message.responded",
|
||||
{
|
||||
"session_id": agent_session_id,
|
||||
"tenant_id": identity.tenant_id,
|
||||
"agent_id": identity.agent_id,
|
||||
"route": result.get("route"),
|
||||
"intent": result.get("intent"),
|
||||
"answer_chars": len(answer),
|
||||
},
|
||||
)
|
||||
|
||||
response = ChannelResponse(
|
||||
channel=msg.channel,
|
||||
session_id=agent_session_id,
|
||||
text=answer,
|
||||
metadata={
|
||||
"channel_id": msg.channel_id,
|
||||
"tenant_id": identity.tenant_id,
|
||||
"agent_id": identity.agent_id,
|
||||
"original_session_id": msg.session_id,
|
||||
"conversation_key": agent_session_id,
|
||||
"workflow_id": workflow_id,
|
||||
"message_id": message_id,
|
||||
"route": result.get("route"),
|
||||
"intent": result.get("intent"),
|
||||
"route_decision": result.get("route_decision"),
|
||||
"domain": result.get("domain"),
|
||||
"mcp_tools": result.get("mcp_tools"),
|
||||
"mcp_results": result.get("mcp_results"),
|
||||
"business_context": business_context.model_dump(),
|
||||
"identity_missing": missing_identity_keys,
|
||||
"judges": result.get("judge_results"),
|
||||
"guardrails": result.get("guardrail_decisions"),
|
||||
"long_term_memory": {
|
||||
"subject_key": business_context.customer_key or session.user_id,
|
||||
"loaded": result.get("long_term_memories", []),
|
||||
"context": result.get("long_term_memory_context", ""),
|
||||
"load_error": result.get("long_term_memory_load_error"),
|
||||
"write_result": result.get("long_term_memory_write_result", {}),
|
||||
},
|
||||
},
|
||||
)
|
||||
rendered = await gateway.render(response)
|
||||
root_span.set_output(rendered)
|
||||
await sse_hub.emit(agent_session_id, "message.responded", rendered) if emit_sse else None
|
||||
await sse_hub.emit(agent_session_id, "flow.end", {"session_id": agent_session_id, "message_id": message_id}) if emit_sse else None
|
||||
return rendered
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {
|
||||
"status": "ok",
|
||||
"llm_provider": settings.LLM_PROVIDER,
|
||||
"llm_class": llm.__class__.__name__,
|
||||
"langfuse_enabled": telemetry.is_enabled(),
|
||||
"agents": [p.agent_id for p in agent_profiles.list_profiles()],
|
||||
"default_agent_id": agent_profiles.default_agent_id,
|
||||
"routing_mode": settings.ROUTING_MODE,
|
||||
"sse_enabled": settings.ENABLE_SSE,
|
||||
"session_repository": settings.SESSION_REPOSITORY_PROVIDER,
|
||||
"memory_repository": settings.MEMORY_REPOSITORY_PROVIDER,
|
||||
"long_term_memory": {
|
||||
"enabled": getattr(settings, "ENABLE_LONG_TERM_MEMORY", False),
|
||||
"provider": getattr(settings, "LONG_TERM_MEMORY_PROVIDER", None),
|
||||
"sqlite_path": getattr(settings, "LONG_TERM_MEMORY_SQLITE_PATH", None),
|
||||
"table": getattr(settings, "LONG_TERM_MEMORY_TABLE", None),
|
||||
"auto_extract": getattr(settings, "LONG_TERM_MEMORY_AUTO_EXTRACT", None),
|
||||
"inject_context": getattr(settings, "LONG_TERM_MEMORY_INJECT_CONTEXT", None),
|
||||
},
|
||||
"checkpoint_repository": settings.CHECKPOINT_REPOSITORY_PROVIDER,
|
||||
"usage_repository": settings.USAGE_REPOSITORY_PROVIDER,
|
||||
"identity_config_path": settings.IDENTITY_CONFIG_PATH,
|
||||
"mcp_parameter_mapping_path": settings.MCP_PARAMETER_MAPPING_PATH,
|
||||
"framework_channel_input_mode": settings.FRAMEWORK_CHANNEL_INPUT_MODE,
|
||||
"legacy_channel_gateway_mode": settings.CHANNEL_GATEWAY_MODE,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/agents")
|
||||
async def list_agents():
|
||||
return {"default_agent_id": agent_profiles.default_agent_id, "agents": [p.__dict__ for p in agent_profiles.list_profiles()]}
|
||||
|
||||
|
||||
@app.get("/debug/env")
|
||||
async def debug_env():
|
||||
return {
|
||||
"APP_ENV": settings.APP_ENV,
|
||||
"LLM_PROVIDER": settings.LLM_PROVIDER,
|
||||
"ENABLE_LANGFUSE": settings.ENABLE_LANGFUSE,
|
||||
"LANGFUSE_HOST": settings.LANGFUSE_HOST,
|
||||
"TELEMETRY_ENABLED": telemetry.is_enabled(),
|
||||
"SQLITE_DB_PATH": settings.SQLITE_DB_PATH,
|
||||
"SESSION_REPOSITORY_PROVIDER": settings.SESSION_REPOSITORY_PROVIDER,
|
||||
"MEMORY_REPOSITORY_PROVIDER": settings.MEMORY_REPOSITORY_PROVIDER,
|
||||
"CHECKPOINT_REPOSITORY_PROVIDER": settings.CHECKPOINT_REPOSITORY_PROVIDER,
|
||||
"AGENTS_CONFIG_PATH": settings.AGENTS_CONFIG_PATH,
|
||||
"ROUTING_CONFIG_PATH": settings.ROUTING_CONFIG_PATH,
|
||||
"ROUTING_MODE": settings.ROUTING_MODE,
|
||||
"FRAMEWORK_CHANNEL_INPUT_MODE": settings.FRAMEWORK_CHANNEL_INPUT_MODE,
|
||||
"CHANNEL_GATEWAY_MODE": settings.CHANNEL_GATEWAY_MODE,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/test-llm")
|
||||
async def test_llm():
|
||||
async with telemetry.span("debug.test_llm", input={"message": "Diga apenas OK"}):
|
||||
answer = await llm.ainvoke([
|
||||
{"role": "system", "content": "Responda de forma curta."},
|
||||
{"role": "user", "content": "Diga apenas OK"},
|
||||
])
|
||||
telemetry.flush()
|
||||
return {"provider": llm.__class__.__name__, "answer": answer}
|
||||
|
||||
|
||||
@app.post("/debug/route")
|
||||
async def debug_route(req: GatewayRequest):
|
||||
msg = await gateway.normalize(req.channel, req.payload)
|
||||
identity, context, business_context, missing_identity_keys = _resolve_identity(req, msg)
|
||||
state = {
|
||||
"tenant_id": identity.tenant_id,
|
||||
"agent_id": identity.agent_id,
|
||||
"session_id": msg.session_id or "debug-session",
|
||||
"conversation_key": identity.conversation_key(),
|
||||
"agent_profile": context["agent_profile"],
|
||||
"user_text": msg.text,
|
||||
"sanitized_input": msg.text,
|
||||
"history": [],
|
||||
"context": {**context, "session": context.get("session", {}), "channel": msg.channel, "business_context": business_context.model_dump()},
|
||||
}
|
||||
if settings.ROUTING_MODE == "supervisor":
|
||||
plan = await workflow.supervisor.route_plan(state)
|
||||
return {"mode": "supervisor", "route": "supervisor_agent", "agents": plan.agents, "intent": plan.intent, "confidence": plan.confidence, "reason": plan.reason, "metadata": plan.metadata}
|
||||
decision = await workflow.router.route(state)
|
||||
data = decision.model_dump(mode="json")
|
||||
data["mode"] = "router"
|
||||
return data
|
||||
|
||||
|
||||
|
||||
|
||||
@app.post("/debug/identity")
|
||||
async def debug_identity(req: GatewayRequest):
|
||||
msg = await gateway.normalize(req.channel, req.payload)
|
||||
identity, context, business_context, missing_identity_keys = _resolve_identity(req, msg)
|
||||
return {
|
||||
"technical_identity": {
|
||||
"tenant_id": identity.tenant_id,
|
||||
"agent_id": identity.agent_id,
|
||||
"conversation_key": identity.conversation_key(),
|
||||
"original_session_id": msg.session_id,
|
||||
},
|
||||
"business_context": business_context.model_dump(),
|
||||
"identity_missing": missing_identity_keys,
|
||||
"context_keys": sorted(context.keys()),
|
||||
}
|
||||
|
||||
@app.get("/debug/usage")
|
||||
async def debug_usage(tenant_id: str | None = None, session_id: str | None = None):
|
||||
return await usage_repository.summarize(tenant_id=tenant_id, session_id=session_id)
|
||||
|
||||
|
||||
@app.get("/debug/mcp/tools")
|
||||
async def debug_mcp_tools():
|
||||
return {"enabled": tool_router.enabled, "tools": tool_router.describe_tools()}
|
||||
|
||||
|
||||
@app.post("/debug/mcp/call/{tool_name}")
|
||||
async def debug_mcp_call(tool_name: str, arguments: dict | None = None):
|
||||
arguments = arguments or {}
|
||||
ctx = arguments.get("business_context") or arguments.get("identity") or {}
|
||||
result = await tool_router.call(
|
||||
tool_name,
|
||||
arguments,
|
||||
business_context=ctx,
|
||||
original_context=arguments,
|
||||
)
|
||||
return result.model_dump(mode="json")
|
||||
|
||||
|
||||
@app.post("/gateway/message")
|
||||
async def gateway_message(req: GatewayRequest):
|
||||
return await _process_gateway_message(req, emit_sse=False)
|
||||
|
||||
|
||||
@app.post("/gateway/message/sse")
|
||||
async def gateway_message_sse(req: GatewayRequest):
|
||||
return await _process_gateway_message(req, emit_sse=True)
|
||||
|
||||
|
||||
@app.get("/gateway/events/{session_id}")
|
||||
async def gateway_events(session_id: str, request: Request):
|
||||
last = request.headers.get("last-event-id") or request.query_params.get("last_event_id") or "0"
|
||||
return StreamingResponse(
|
||||
sse_hub.subscribe(session_id, int(last)),
|
||||
media_type="text/event-stream",
|
||||
headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/sessions/{session_id}/messages")
|
||||
async def get_session_messages(session_id: str, limit: int = 50):
|
||||
return {"session_id": session_id, "messages": [m.model_dump(mode="json") for m in await memory.list(session_id, limit)]}
|
||||
|
||||
|
||||
@app.get("/sessions/{session_id}/checkpoint")
|
||||
async def get_session_checkpoint(session_id: str):
|
||||
return {"session_id": session_id, "checkpoint": await checkpoints.get_latest(session_id)}
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown():
|
||||
telemetry.shutdown()
|
||||
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from agent_framework.gateways import MCPGatewayClient
|
||||
|
||||
|
||||
def build_mcp_gateway_client() -> MCPGatewayClient | None:
|
||||
if os.getenv("MCP_GATEWAY_ENABLED", "true").lower() != "true":
|
||||
return None
|
||||
|
||||
return MCPGatewayClient(
|
||||
base_url=os.getenv("MCP_GATEWAY_URL", "http://localhost:8300"),
|
||||
token=os.getenv("MCP_GATEWAY_TOKEN") or None,
|
||||
timeout_seconds=int(os.getenv("MCP_GATEWAY_TIMEOUT_SECONDS", "60")),
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,84 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""Observer adapter that emits IC/NOC/GRL through framework Telemetry only.
|
||||
|
||||
This avoids a second Langfuse root trace created by AgentObserver ->
|
||||
AnalyticsPublisher while preserving the events inside the active request span.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _normalize_ic_code(code: str) -> str:
|
||||
code = str(code or "UNKNOWN").strip()
|
||||
return code if code.startswith(("IC.", "AGA.", "NOC.", "GRL.")) else f"IC.{code}"
|
||||
|
||||
|
||||
def _normalize_noc_code(code: str) -> str:
|
||||
code = str(code or "UNKNOWN").strip()
|
||||
return code if code.startswith("NOC.") else f"NOC.{code}"
|
||||
|
||||
|
||||
def _normalize_grl_code(code: str) -> str:
|
||||
code = str(code or "UNKNOWN").strip()
|
||||
return code if code.startswith("GRL.") else f"GRL.{code}"
|
||||
|
||||
|
||||
def _kind_for(event_type: str) -> str:
|
||||
if event_type.startswith(("IC.", "AGA.")):
|
||||
return "ic"
|
||||
if event_type.startswith("NOC."):
|
||||
return "noc"
|
||||
if event_type.startswith("GRL."):
|
||||
return "grl"
|
||||
return "event"
|
||||
|
||||
|
||||
class TelemetryBackedAgentObserver:
|
||||
"""Drop-in subset of AgentObserver backed by Telemetry.event.
|
||||
|
||||
Do not publish through AnalyticsPublisher here. Analytics publishing may be
|
||||
configured with a Langfuse provider, and that path creates an extra root
|
||||
trace for business events such as IC.AGENT_COMPLETED/NOC.006. Telemetry.event
|
||||
uses the active span/trace context, so these events appear inside the single
|
||||
request trace.
|
||||
"""
|
||||
|
||||
def __init__(self, telemetry: Any, *, source: str = "agent_framework") -> None:
|
||||
self.telemetry = telemetry
|
||||
self.source = source
|
||||
|
||||
async def emit(
|
||||
self,
|
||||
event_type: str,
|
||||
payload: dict[str, Any] | None = None,
|
||||
*,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
source: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
body = dict(payload or {})
|
||||
meta = dict(metadata or {})
|
||||
body.setdefault("tag", event_type)
|
||||
event = {
|
||||
"eventType": event_type,
|
||||
"source": source or self.source,
|
||||
"eventDate": datetime.now(timezone.utc).isoformat(),
|
||||
"body": body,
|
||||
"metadata": meta,
|
||||
}
|
||||
try:
|
||||
await self.telemetry.event(event_type, event, kind=_kind_for(event_type))
|
||||
except TypeError:
|
||||
# Compatibility with older Telemetry.event signatures.
|
||||
await self.telemetry.event(event_type, event)
|
||||
return event
|
||||
|
||||
async def emit_ic(self, code: str, payload: dict[str, Any] | None = None, **metadata: Any) -> dict[str, Any]:
|
||||
return await self.emit(_normalize_ic_code(code), payload, metadata={**metadata, "ic": True})
|
||||
|
||||
async def emit_noc(self, code: str, payload: dict[str, Any] | None = None, **metadata: Any) -> dict[str, Any]:
|
||||
return await self.emit(_normalize_noc_code(code), payload, metadata={**metadata, "noc": True})
|
||||
|
||||
async def emit_grl(self, code: str, payload: dict[str, Any] | None = None, **metadata: Any) -> dict[str, Any]:
|
||||
return await self.emit(_normalize_grl_code(code), payload, metadata={**metadata, "grl": True})
|
||||
@@ -0,0 +1,53 @@
|
||||
from typing import Any, TypedDict
|
||||
|
||||
|
||||
class AgentState(TypedDict, total=False):
|
||||
tenant_id: str
|
||||
agent_id: str
|
||||
session_id: str
|
||||
conversation_key: str
|
||||
workflow_id: str
|
||||
agent_profile: dict[str, Any]
|
||||
user_text: str
|
||||
sanitized_input: str
|
||||
route: str
|
||||
intent: str
|
||||
route_decision: dict[str, Any]
|
||||
answer: str
|
||||
final_answer: str
|
||||
history: list[dict[str, Any]]
|
||||
context: dict[str, Any]
|
||||
guardrail_decisions: list[dict[str, Any]]
|
||||
judge_results: list[dict[str, Any]]
|
||||
next_state: str
|
||||
domain: str
|
||||
mcp_tools: list[str]
|
||||
mcp_results: list[dict[str, Any]]
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
tool_policy_result: dict[str, Any]
|
||||
missing_parameters: list[str]
|
||||
supervisor_plan: dict[str, Any]
|
||||
supervisor_results: list[dict[str, Any]]
|
||||
active_agent: str
|
||||
route_bypassed: bool
|
||||
continuity_signal: dict[str, Any]
|
||||
session_control: str
|
||||
session_ended: bool
|
||||
human_handoff_requested: bool
|
||||
blocked: bool
|
||||
supervisor_action: str
|
||||
supervisor_guidance: str
|
||||
supervisor_attempt: int
|
||||
supervisor_handover_reason: str
|
||||
output_supervisor_results: list[dict[str, Any]]
|
||||
output_guardrails_already_applied: bool
|
||||
long_term_memories: list[dict[str, Any]]
|
||||
long_term_memory_context: str
|
||||
long_term_memory_write_result: dict[str, Any]
|
||||
long_term_memory_subject_key: str
|
||||
long_term_memory_load_error: str
|
||||
@@ -0,0 +1 @@
|
||||
from . import devolucao # noqa: F401
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user