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