Compare commits

...

3 Commits

55 changed files with 1770 additions and 378 deletions

View File

@@ -77,6 +77,7 @@ LANGFUSE_HOST=http://localhost:3005
ENABLE_OTEL=false ENABLE_OTEL=false
OTEL_EXPORTER_OTLP_ENDPOINT= OTEL_EXPORTER_OTLP_ENDPOINT=
OTEL_SERVICE_NAME=ai-agent-template OTEL_SERVICE_NAME=ai-agent-template
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
############################################################################### ###############################################################################
# Analytics / Observer corporativo # Analytics / Observer corporativo
@@ -165,3 +166,15 @@ MEMORY_MAX_SUMMARY_CHARS=6000
MEMORY_SUMMARY_USE_LLM=true MEMORY_SUMMARY_USE_LLM=true
MEMORY_INJECT_RECENT_MESSAGES=true MEMORY_INJECT_RECENT_MESSAGES=true
MEMORY_INJECT_SUMMARY=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

10
.idea/.gitignore generated vendored
View File

@@ -1,10 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Environment-dependent path to Maven home directory
/mavenHomeManager.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
</module>

View File

@@ -1,7 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<ScalaCodeStyleSettings>
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
</ScalaCodeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/agent_platform_oci.iml" filepath="$PROJECT_DIR$/.idea/agent_platform_oci.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

112
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="30a0e1d8-9d7d-469b-b241-f300911cee8a" name="Changes" comment="Ajustes na documentação e remanejamento dos folders">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/MANUAL_AGENT_PLATFORM_GATEWAYS.md" beforeDir="false" afterPath="$PROJECT_DIR$/MANUAL_AGENT_PLATFORM_GATEWAYS.md" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ComposerSettings">
<execution />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="GitHubPullRequestSearchHistory">{
&quot;lastFilter&quot;: {
&quot;state&quot;: &quot;OPEN&quot;,
&quot;assignee&quot;: &quot;hoshikawa2&quot;
}
}</component>
<component name="GithubPullRequestsUISettings">{
&quot;selectedUrlAndAccountId&quot;: {
&quot;url&quot;: &quot;https://github.com/hoshikawa2/agent_platform_oci.git&quot;,
&quot;accountId&quot;: &quot;bd799ac0-7624-42b2-999a-b96e6e11f98a&quot;
}
}</component>
<component name="KubernetesApiPersistence"><![CDATA[{}]]></component>
<component name="KubernetesApiProvider"><![CDATA[{
"isMigrated": true
}]]></component>
<component name="PhpWorkspaceProjectConfiguration" interpreter_name="PHP 8.1" />
<component name="ProjectCodeStyleSettingsMigration">
<option name="version" value="2" />
</component>
<component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 2
}</component>
<component name="ProjectId" id="3FQ8m21arbKaZKgqpTYDVjVGTBf" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;configurable.group.appearance&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-fbdcb00ec9e3-intellij.indexing.shared.core-IU-251.23774.435" />
<option value="bundled-js-predefined-d6986cc7102b-f27c65a3e318-JavaScript-IU-251.23774.435" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="30a0e1d8-9d7d-469b-b241-f300911cee8a" name="Changes" comment="" />
<created>1781996088468</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1781996088468</updated>
<workItem from="1781996091031" duration="1242000" />
<workItem from="1782038604623" duration="6486000" />
</task>
<task id="LOCAL-00001" summary="Ajustes na documentação e remanejamento dos folders">
<option name="closed" value="true" />
<created>1782045250014</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1782045250014</updated>
</task>
<task id="LOCAL-00002" summary="Ajustes na documentação e remanejamento dos folders">
<option name="closed" value="true" />
<created>1782045265085</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1782045265085</updated>
</task>
<option name="localTasksCounter" value="3" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="Ajustes na documentação e remanejamento dos folders" />
<option name="LAST_COMMIT_MESSAGE" value="Ajustes na documentação e remanejamento dos folders" />
</component>
</project>

View 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

157
README.md
View File

@@ -25,83 +25,83 @@ A documentação da Agent Platform OCI está organizada em SPECs/SDDs numeradas,
>**/agent_platform_oci/specs** >**/agent_platform_oci/specs**
### SPEC-001 — Architecture ### [SPEC-001 — Architecture](specs/SPEC-001-Architecture.md)
Define a arquitetura geral da plataforma, seus componentes principais, estrutura de repositório, arquitetura lógica e física, contratos centrais, fluxo principal, requisitos não funcionais e critérios de aceite. Define a arquitetura geral da plataforma, seus componentes principais, estrutura de repositório, arquitetura lógica e física, contratos centrais, fluxo principal, requisitos não funcionais e critérios de aceite.
### SPEC-002 — Agent Runtime ### [SPEC-002 — Agent Runtime](specs/SPEC-002-Agent-Runtime.md)
Descreve o runtime de execução conversacional dos agentes, incluindo LangGraph, estado, memória, checkpoints, roteamento, supervisor, BusinessContext, integração com MCP, RAG, eventos e tratamento de erros. Descreve o runtime de execução conversacional dos agentes, incluindo LangGraph, estado, memória, checkpoints, roteamento, supervisor, BusinessContext, integração com MCP, RAG, eventos e tratamento de erros.
### SPEC-003 — AI Gateway ### [SPEC-003 — AI Gateway](specs/SPEC-003-AI-Gateway.md)
Especifica o gateway responsável por centralizar chamadas de LLM e embeddings, incluindo contratos de request/response, profiles, providers, autenticação OCI, fallback, rate limit, métricas, segurança e observabilidade. Especifica o gateway responsável por centralizar chamadas de LLM e embeddings, incluindo contratos de request/response, profiles, providers, autenticação OCI, fallback, rate limit, métricas, segurança e observabilidade.
### SPEC-004 — MCP Gateway ### [SPEC-004 — MCP Gateway](specs/SPEC-004-MCP-Gateway.md)
Define o modelo de integração com MCP, incluindo catálogo de tools, roteamento, execução, autorização, cache, retry, timeout, mapeamento de parâmetros, eventos, métricas e resposta padronizada das ferramentas. Define o modelo de integração com MCP, incluindo catálogo de tools, roteamento, execução, autorização, cache, retry, timeout, mapeamento de parâmetros, eventos, métricas e resposta padronizada das ferramentas.
### SPEC-005 — Guardrails ### [SPEC-005 — Guardrails](specs/SPEC-005-Guardrails.md)
Descreve o modelo de guardrails da plataforma, cobrindo políticas de entrada, saída, tools, RAG e resposta final. Também define fases, modos de execução, tipos de guardrails, profiles LLM, códigos base, eventos, testes e critérios de aceite. Descreve o modelo de guardrails da plataforma, cobrindo políticas de entrada, saída, tools, RAG e resposta final. Também define fases, modos de execução, tipos de guardrails, profiles LLM, códigos base, eventos, testes e critérios de aceite.
### SPEC-006 — Evals ### [SPEC-006 — Evals](specs/SPEC-006-Evals.md)
Define a camada de avaliação da plataforma, incluindo avaliação online, avaliação offline, regressão, certificação, datasets, judges, métricas, CLI, API, persistência de resultados e publicação de evidências. Define a camada de avaliação da plataforma, incluindo avaliação online, avaliação offline, regressão, certificação, datasets, judges, métricas, CLI, API, persistência de resultados e publicação de evidências.
### SPEC-007 — Observability ### [SPEC-007 — Observability](specs/SPEC-007-Observability.md)
Especifica o modelo de observabilidade, incluindo logs, traces, métricas, Langfuse, OpenTelemetry, eventos IC/NOC/GRL, dashboards, alertas, mascaramento de dados e geração de evidências operacionais. Especifica o modelo de observabilidade, incluindo logs, traces, métricas, Langfuse, OpenTelemetry, eventos IC/NOC/GRL, dashboards, alertas, mascaramento de dados e geração de evidências operacionais.
### SPEC-008 — Deployment ### [SPEC-008 — Deployment](specs/SPEC-008-Deployment.md)
Descreve o processo de empacotamento e implantação da plataforma, incluindo componentes deployáveis, pipeline CI/CD, Kubernetes/OKE, Docker, secrets, autenticação OCI, health checks, rollback, smoke tests e etapa de certificação. Descreve o processo de empacotamento e implantação da plataforma, incluindo componentes deployáveis, pipeline CI/CD, Kubernetes/OKE, Docker, secrets, autenticação OCI, health checks, rollback, smoke tests e etapa de certificação.
### SPEC-009 — Channel Gateway ### [SPEC-009 — Channel Gateway](specs/SPEC-009-Channel-Gateway.md)
Define o gateway de canais, responsável por normalizar payloads externos para o contrato canônico da plataforma e traduzir respostas para cada canal. Cobre modos de operação, idempotência, versionamento, segurança, erros e anti-patterns. Define o gateway de canais, responsável por normalizar payloads externos para o contrato canônico da plataforma e traduzir respostas para cada canal. Cobre modos de operação, idempotência, versionamento, segurança, erros e anti-patterns.
### SPEC-010 — Agent Development ### [SPEC-010 — Agent Development](specs/SPEC-010-Agent-Development.md)
Descreve o padrão para desenvolvimento de agentes usando templates, configuração YAML, BusinessContext, MCP, guardrails, judges, RAG, memória, observabilidade e evals. Também diferencia responsabilidades do framework e do agente. Descreve o padrão para desenvolvimento de agentes usando templates, configuração YAML, BusinessContext, MCP, guardrails, judges, RAG, memória, observabilidade e evals. Também diferencia responsabilidades do framework e do agente.
### SPEC-011 — Governance Model ### [SPEC-011 — Governance Model](specs/SPEC-011-Governance-Model.md)
Define o modelo de governança da plataforma, incluindo ownership, papéis e responsabilidades, RACI, governança de agentes, prompts, guardrails, judges, modelos, MCP, datasets, processo de aprovação e evidências obrigatórias. Define o modelo de governança da plataforma, incluindo ownership, papéis e responsabilidades, RACI, governança de agentes, prompts, guardrails, judges, modelos, MCP, datasets, processo de aprovação e evidências obrigatórias.
### SPEC-012 — Canonical Contracts ### [SPEC-012 — Canonical Contracts](specs/SPEC-012-Canonical-Contracts.md)
Documenta os contratos canônicos da plataforma, como GatewayRequest, ChannelResponse, BusinessContext, AgentState, ToolInvocation, ToolResult, LLMRequest, LLMResponse, EvaluationRun e EventEnvelope. Também define regras de evolução desses contratos. Documenta os contratos canônicos da plataforma, como GatewayRequest, ChannelResponse, BusinessContext, AgentState, ToolInvocation, ToolResult, LLMRequest, LLMResponse, EvaluationRun e EventEnvelope. Também define regras de evolução desses contratos.
### SPEC-013 — Versioning and Compatibility Model ### [SPEC-013 — Versioning and Compatibility Model](specs/SPEC-013-Versioning-and-Compatibility-Model.md)
Define o modelo de versionamento e compatibilidade da plataforma, incluindo Semantic Versioning, artefatos versionados, versionamento de contratos, matriz de compatibilidade, política de depreciação, migração e rollback. Define o modelo de versionamento e compatibilidade da plataforma, incluindo Semantic Versioning, artefatos versionados, versionamento de contratos, matriz de compatibilidade, política de depreciação, migração e rollback.
### SPEC-014 — Templates and Agent Creation Model ### [SPEC-014 — Templates and Agent Creation Model](specs/SPEC-014-Templates-and-Agent-Creation-Model.md)
Descreve os templates oficiais e o modelo de criação de agentes do zero. Explica o que pertence ao framework, o que pertence ao agente, a estrutura padrão e o passo a passo para copiar template, definir escopo, registrar agente, configurar rotas, tools, BusinessContext, prompts, datasets e testes. Descreve os templates oficiais e o modelo de criação de agentes do zero. Explica o que pertence ao framework, o que pertence ao agente, a estrutura padrão e o passo a passo para copiar template, definir escopo, registrar agente, configurar rotas, tools, BusinessContext, prompts, datasets e testes.
### SPEC-015 — Adoption and Eligibility Criteria ### [SPEC-015 — Adoption and Eligibility Criteria](specs/SPEC-015-Adoption-and-Eligibility-Criteria.md)
Define os critérios claros para adoção da plataforma, incluindo casos indicados, casos não indicados, critérios de entrada de negócio, arquitetura, segurança, qualidade, operação, processo de exceção e checklist de adoção. Define os critérios claros para adoção da plataforma, incluindo casos indicados, casos não indicados, critérios de entrada de negócio, arquitetura, segurança, qualidade, operação, processo de exceção e checklist de adoção.
### SPEC-016 — Agent Development Lifecycle ### [SPEC-016 — Agent Development Lifecycle](specs/SPEC-016-Agent-Development-Lifecycle.md)
Descreve o ciclo de vida completo de desenvolvimento de agentes, desde discovery e definição de escopo até design, prompt, MCP, RAG, implementação, testes, avaliação, certificação, homologação e produção. Descreve o ciclo de vida completo de desenvolvimento de agentes, desde discovery e definição de escopo até design, prompt, MCP, RAG, implementação, testes, avaliação, certificação, homologação e produção.
### SPEC-017 — Release Management and CI/CD ### [SPEC-017 — Release Management and CI/CD](specs/SPEC-017-Release-Management-and-CICD.md)
Define o modelo de release e CI/CD, incluindo pipeline padrão, stages, artefatos de release, gates de qualidade, estratégia de rollback, erros comuns e critérios de aceite. Define o modelo de release e CI/CD, incluindo pipeline padrão, stages, artefatos de release, gates de qualidade, estratégia de rollback, erros comuns e critérios de aceite.
### SPEC-018 — Security and Identity Model ### [SPEC-018 — Security and Identity Model](specs/SPEC-018-Security-and-Identity-Model.md)
Especifica o modelo de segurança e identidade da plataforma, cobrindo autenticação, Workload Identity, autorização, secrets, proteção de dados, segurança em MCP, segurança em canais, auditoria e critérios de aceite. Especifica o modelo de segurança e identidade da plataforma, cobrindo autenticação, Workload Identity, autorização, secrets, proteção de dados, segurança em MCP, segurança em canais, auditoria e critérios de aceite.
### SPEC-019 — Evaluation and Certification Framework ### [SPEC-019 — Evaluation and Certification Framework](specs/SPEC-019-Evaluation-and-Certification-Framework.md)
Detalha o framework de avaliação e certificação, incluindo arquitetura de avaliação, métricas, datasets, EvaluationRun, CLI, processo de certificação, evidências obrigatórias e critérios de aceite. Detalha o framework de avaliação e certificação, incluindo arquitetura de avaliação, métricas, datasets, EvaluationRun, CLI, processo de certificação, evidências obrigatórias e critérios de aceite.
### SPEC-020 — Operational Readiness and SRE Model ### [SPEC-020 — Operational Readiness and SRE Model](specs/SPEC-020-Operational-Readiness-and-SRE-Model.md)
Define o modelo de readiness operacional e SRE da plataforma, incluindo componentes operados, health checks, readiness, SLOs, métricas, dashboards, alertas, runbooks, gestão de incidentes, capacidade e checklist de produção. Define o modelo de readiness operacional e SRE da plataforma, incluindo componentes operados, health checks, readiness, SLOs, métricas, dashboards, alertas, runbooks, gestão de incidentes, capacidade e checklist de produção.
@@ -7862,6 +7862,64 @@ workflow concluído
## 17. Build e execução local ## 17. Build e execução local
Vamos subir a estrutura completa de um agente:
```text
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
---
Na raiz do projeto: Na raiz do projeto:
```bash ```bash
@@ -7967,7 +8025,34 @@ servers:
> Você pode customizar para subir todos os seus mcp servers. > Você pode customizar para subir todos os seus mcp servers.
> Execute: **bash ./scripts/run_mcp_servers.sh** > Execute: **bash ./scripts/run_mcp_servers.sh**
### 18.3. Testar tool pelo backend ### 18.3. Subir o MCP Gateway
```bash
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:
```bash
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
### 18.4. Testar tool pelo backend
Teste pelo backend, não diretamente pelo MCP. Assim você valida o caminho completo: Teste pelo backend, não diretamente pelo MCP. Assim você valida o caminho completo:
@@ -7992,7 +8077,7 @@ curl -X POST http://localhost:8000/debug/mcp/call/consultar_titulo_financeiro \
>**Nota:** No projeto existe também uma interface visual para testar: >**Nota:** No projeto existe também uma interface visual para testar:
### 18.4. Subir Frontend para testes ### 18.5. Subir Frontend para testes
Install before the [npm](https://nodejs.org/) and: Install before the [npm](https://nodejs.org/) and:
@@ -8007,7 +8092,7 @@ Abra http://localhost:5173.
>**Nota:** O frontend está preparado para funcionar com o **Agent Gateway**. No projeto, consulte os capítulos 28 e 28.10 para experimentá-lo. Lembre-se apenas de trocar **Backend URL** para **http://localhost:8010** pois 8010 é a porta onde o **Agent Gateway** estará escutando. >**Nota:** O frontend está preparado para funcionar com o **Agent Gateway**. No projeto, consulte os capítulos 28 e 28.10 para experimentá-lo. Lembre-se apenas de trocar **Backend URL** para **http://localhost:8010** pois 8010 é a porta onde o **Agent Gateway** estará escutando.
### 18.5. Como interpretar erros MCP ### 18.6. Como interpretar erros MCP
```text ```text
Tool não encontrada → tools.yaml ou nome da tool errado. Tool não encontrada → tools.yaml ou nome da tool errado.
@@ -8017,6 +8102,32 @@ Parâmetro obrigatório ausente → identity.yaml ou mcp_parameter_mapping.yaml
Timeout → MCP lento, endpoint errado, VPN, DNS ou sistema real indisponível. Timeout → MCP lento, endpoint errado, VPN, DNS ou sistema real indisponível.
``` ```
### 18.7. Subir Agent Gateway
```bash
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:
```bash
curl http://localhost:9000/health
```
Teste:
curl -X POST http://localhost:9000/gateway/message
--- ---
## 19. Build com Docker ## 19. Build com Docker

View File

@@ -24,85 +24,86 @@ The Agent Platform OCI documentation is organized into numbered SPECs/SDDs, each
>**/agent_platform_oci/specs** >**/agent_platform_oci/specs**
### SPEC-001 — Architecture ### [SPEC-001 — Architecture](specs/SPEC-001-Architecture.md)
Defines the overall architecture of the platform, its main components, repository structure, logical and physical architecture, core contracts, main flow, non-functional requirements, and acceptance criteria. Defines the overall platform architecture, its main components, repository structure, logical and physical architecture, core contracts, primary flow, non-functional requirements, and acceptance criteria.
### SPEC-002 — Agent Runtime ### [SPEC-002 — Agent Runtime](specs/SPEC-002-Agent-Runtime.md)
Describes the conversational execution runtime of the agents, including LangGraph, state, memory, checkpoints, routing, supervisor, BusinessContext, MCP integration, RAG, events, and error handling. Describes the conversational execution runtime for agents, including LangGraph, state management, memory, checkpoints, routing, supervisor, BusinessContext, MCP integration, RAG, events, and error handling.
### SPEC-003 — AI Gateway ### [SPEC-003 — AI Gateway](specs/SPEC-003-AI-Gateway.md)
Specifies the gateway responsible for centralizing LLM and embedding calls, including request/response contracts, profiles, providers, OCI authentication, fallback, rate limiting, metrics, security, and observability. Specifies the gateway responsible for centralizing LLM and embedding calls, including request/response contracts, profiles, providers, OCI authentication, fallback, rate limiting, metrics, security, and observability.
### SPEC-004 — MCP Gateway ### [SPEC-004 — MCP Gateway](specs/SPEC-004-MCP-Gateway.md)
Defines the MCP integration model, including tool catalog, routing, execution, authorization, cache, retry, timeout, parameter mapping, events, metrics, and standardized tool responses. Defines the MCP integration model, including the tool catalog, routing, execution, authorization, caching, retries, timeouts, parameter mapping, events, metrics, and standardized tool responses.
### SPEC-005 — Guardrails ### [SPEC-005 — Guardrails](specs/SPEC-005-Guardrails.md)
Describes the platform guardrails model, covering input, output, tools, RAG, and final response policies. It also defines phases, execution modes, guardrail types, LLM profiles, base codes, events, tests, and acceptance criteria. Describes the platform guardrail model, covering input, output, tool, RAG, and final response policies. It also defines phases, execution modes, guardrail types, LLM profiles, base codes, events, tests, and acceptance criteria.
### SPEC-006 — Evals ### [SPEC-006 — Evals](specs/SPEC-006-Evals.md)
Defines the platform evaluation layer, including online evaluation, offline evaluation, regression, certification, datasets, judges, metrics, CLI, API, result persistence, and evidence publication. Defines the platform evaluation layer, including online evaluation, offline evaluation, regression testing, certification, datasets, judges, metrics, CLI, API, result persistence, and evidence publication.
### SPEC-007 — Observability ### [SPEC-007 — Observability](specs/SPEC-007-Observability.md)
Specifies the observability model, including logs, traces, metrics, Langfuse, OpenTelemetry, IC/NOC/GRL events, dashboards, alerts, data masking, and operational evidence generation. Specifies the observability model, including logs, traces, metrics, Langfuse, OpenTelemetry, IC/NOC/GRL events, dashboards, alerts, data masking, and operational evidence generation.
### SPEC-008 — Deployment ### [SPEC-008 — Deployment](specs/SPEC-008-Deployment.md)
Describes the platform packaging and deployment process, including deployable components, CI/CD pipeline, Kubernetes/OKE, Docker, secrets, OCI authentication, health checks, rollback, smoke tests, and certification stage. Describes the platform packaging and deployment process, including deployable components, CI/CD pipelines, Kubernetes/OKE, Docker, secrets, OCI authentication, health checks, rollback, smoke tests, and certification stages.
### SPEC-009 — Channel Gateway ### [SPEC-009 — Channel Gateway](specs/SPEC-009-Channel-Gateway.md)
Defines the channel gateway, responsible for normalizing external payloads into the platforms canonical contract and translating responses for each channel. It covers operation modes, idempotency, versioning, security, errors, and anti-patterns. Defines the channel gateway responsible for normalizing external payloads into the platform's canonical contract and translating responses for each channel. Covers operating modes, idempotency, versioning, security, errors, and anti-patterns.
### SPEC-010 — Agent Development ### [SPEC-010 — Agent Development](specs/SPEC-010-Agent-Development.md)
Describes the standard for developing agents using templates, YAML configuration, BusinessContext, MCP, guardrails, judges, RAG, memory, observability, and evals. It also differentiates the responsibilities of the framework and the agent. Describes the standard for agent development using templates, YAML configuration, BusinessContext, MCP, guardrails, judges, RAG, memory, observability, and evaluations. It also differentiates framework and agent responsibilities.
### SPEC-011 — Governance Model ### [SPEC-011 — Governance Model](specs/SPEC-011-Governance-Model.md)
Defines the platform governance model, including ownership, roles and responsibilities, RACI, governance of agents, prompts, guardrails, judges, models, MCP, datasets, approval process, and mandatory evidence. Defines the platform governance model, including ownership, roles and responsibilities, RACI, agent governance, prompts, guardrails, judges, models, MCP, datasets, approval processes, and mandatory evidence.
### SPEC-012 — Canonical Contracts ### [SPEC-012 — Canonical Contracts](specs/SPEC-012-Canonical-Contracts.md)
Documents the platforms canonical contracts, such as GatewayRequest, ChannelResponse, BusinessContext, AgentState, ToolInvocation, ToolResult, LLMRequest, LLMResponse, EvaluationRun, and EventEnvelope. It also defines rules for evolving these contracts. Documents the platform's canonical contracts, including GatewayRequest, ChannelResponse, BusinessContext, AgentState, ToolInvocation, ToolResult, LLMRequest, LLMResponse, EvaluationRun, and EventEnvelope. It also defines contract evolution rules.
### SPEC-013 — Versioning and Compatibility Model ### [SPEC-013 — Versioning and Compatibility Model](specs/SPEC-013-Versioning-and-Compatibility-Model.md)
Defines the platform versioning and compatibility model, including Semantic Versioning, versioned artifacts, contract versioning, compatibility matrix, deprecation policy, migration, and rollback. Defines the platform versioning and compatibility model, including Semantic Versioning, versioned artifacts, contract versioning, compatibility matrices, deprecation policies, migration, and rollback.
### SPEC-014 — Templates and Agent Creation Model ### [SPEC-014 — Templates and Agent Creation Model](specs/SPEC-014-Templates-and-Agent-Creation-Model.md)
Describes the official templates and the model for creating agents from scratch. It explains what belongs to the framework, what belongs to the agent, the standard structure, and the step-by-step process to copy a template, define scope, register the agent, configure routes, tools, BusinessContext, prompts, datasets, and tests. Describes the official templates and the agent creation model from scratch. Explains what belongs to the framework, what belongs to the agent, the standard structure, and the step-by-step process for copying templates, defining scope, registering agents, configuring routes, tools, BusinessContext, prompts, datasets, and tests.
### SPEC-015 — Adoption and Eligibility Criteria ### [SPEC-015 — Adoption and Eligibility Criteria](specs/SPEC-015-Adoption-and-Eligibility-Criteria.md)
Defines clear criteria for adopting the platform, including recommended use cases, non-recommended use cases, business entry criteria, architecture, security, quality, operation, exception process, and adoption checklist. Defines clear platform adoption criteria, including recommended and non-recommended use cases, business entry criteria, architecture, security, quality, operations, exception processes, and adoption checklists.
### SPEC-016 — Agent Development Lifecycle ### [SPEC-016 — Agent Development Lifecycle](specs/SPEC-016-Agent-Development-Lifecycle.md)
Describes the complete agent development lifecycle, from discovery and scope definition to design, prompt, MCP, RAG, implementation, testing, evaluation, certification, homologation, and production. Describes the complete agent development lifecycle, from discovery and scope definition through design, prompting, MCP, RAG, implementation, testing, evaluation, certification, validation, and production.
### SPEC-017 — Release Management and CI/CD ### [SPEC-017 — Release Management and CI/CD](specs/SPEC-017-Release-Management-and-CICD.md)
Defines the release and CI/CD model, including the standard pipeline, stages, release artifacts, quality gates, rollback strategy, common errors, and acceptance criteria. Defines the release and CI/CD model, including standard pipelines, stages, release artifacts, quality gates, rollback strategies, common errors, and acceptance criteria.
### SPEC-018 — Security and Identity Model ### [SPEC-018 — Security and Identity Model](specs/SPEC-018-Security-and-Identity-Model.md)
Specifies the platform security and identity model, covering authentication, Workload Identity, authorization, secrets, data protection, MCP security, channel security, auditing, and acceptance criteria. Specifies the platform security and identity model, covering authentication, Workload Identity, authorization, secrets, data protection, MCP security, channel security, auditing, and acceptance criteria.
### SPEC-019 — Evaluation and Certification Framework ### [SPEC-019 — Evaluation and Certification Framework](specs/SPEC-019-Evaluation-and-Certification-Framework.md)
Details the evaluation and certification framework, including evaluation architecture, metrics, datasets, EvaluationRun, CLI, certification process, mandatory evidence, and acceptance criteria. Details the evaluation and certification framework, including evaluation architecture, metrics, datasets, EvaluationRun, CLI, certification processes, mandatory evidence, and acceptance criteria.
### SPEC-020 — Operational Readiness and SRE Model ### [SPEC-020 — Operational Readiness and SRE Model](specs/SPEC-020-Operational-Readiness-and-SRE-Model.md)
Defines the platform operational readiness and SRE model, including managed components, health checks, readiness, SLOs, metrics, dashboards, alerts, runbooks, incident management, capacity planning, and production checklists.
Defines the platform operational readiness and SRE model, including operated components, health checks, readiness, SLOs, metrics, dashboards, alerts, runbooks, incident management, capacity, and production checklist.
--- ---
@@ -7766,22 +7767,80 @@ workflow completed
--- ---
## 17. Build and local execution ## 17. Local Build and Execution
At the root of the project: Let's bring up the complete agent stack:
```text
Frontend (5173)
Agent Gateway (9000)
Agent Template Backend / Runtime (8000)
MCP Gateway (8300)
Telecom MCP Server (8100)
Retail MCP Server (8200)
```
### Official Ports
| Component | Port |
|------------|--------|
| Frontend | 5173 |
| Agent Gateway | 9000 |
| Backend Runtime | 8000 |
| MCP Gateway | 8300 |
| Telecom MCP Server | 8100 |
| Retail MCP Server | 8200 |
### Official Variables
### 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
### Startup Order
1. Telecom MCP Server
2. Retail MCP Server
3. MCP Gateway
4. Agent Template Backend
5. Agent Gateway
6. Frontend
---
From the project root:
```bash ```bash
cd agent_platform_oci cd agent_platform_oci
python -m venv .venv python -m venv .venv
``` ```
### 17.1. Before the commands: what does it mean to upload the backend? ### 17.1. Before the commands: what does starting the backend mean?
Bringing up the backend means starting the API that receives messages, normalizes the channel, resolves identity, opens a session, executes the workflow, and returns a response. Starting the backend means launching the API that receives messages, normalizes the channel, resolves identity, opens a session, executes the workflow, and returns a response.
It can be uploaded even without a real MCP, as long as the configuration is in mock or the tools are not required for the test. It can be started even without a real MCP, as long as the configuration is in mock mode or the tools are not required for the test.
### 17.2. Run local backend ### 17.2. Run the Backend Locally
Inside `agent_template_backend`: Inside `agent_template_backend`:
@@ -7800,7 +7859,7 @@ Windows PowerShell:
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
``` ```
### 17.3. Immediate validations ### 17.3. Immediate Validations
Check health: Check health:
@@ -7820,11 +7879,11 @@ List known MCP tools:
curl http://localhost:8000/debug/mcp/tools curl http://localhost:8000/debug/mcp/tools
``` ```
### 17.4. How to interpret the result ### 17.4. How to Interpret the Result
```text ```text
/health ok → API is up. /health ok → API started successfully.
/agents lista → agents.yaml was loaded. /agents lists agents → agents.yaml was loaded.
/debug/mcp/tools → tools.yaml and mcp_servers.yaml were loaded. /debug/mcp/tools → tools.yaml and mcp_servers.yaml were loaded.
``` ```
@@ -7832,25 +7891,25 @@ If `/health` works but`/agents` does not list the agent, the problem is probably
--- ---
## 18. Uploading MCP Servers ## 18. Starting MCP Servers
### 18.1. Before the commands: when do I need to upload MCP? ### 18.1. Before the commands: when do I need to start MCP?
You need to upload MCP when the chosen intent uses `mcp_tools` and the agent depends on these tools to respond. You need to start MCP when the selected intent uses `mcp_tools` and the agent depends on those tools to respond.
You don't need to upload MCP just to test: You do not need MCP running to test only:
```text ```text
health check health check
agent registration agent registration
basic routing basic routing
mock LLM without tools mock LLM without tools
simple conversational flow without external query simple conversational flow without external lookup
``` ```
### 18.2. Upload local MCP Server ### 18.2. Start a Local MCP Server
If the MCP Servers are separate Python processes, upload each one on a different port. If the MCP Servers are separate Python processes, start each one on a different port.
Example: Example:
@@ -7868,14 +7927,41 @@ financeiro:
endpoint: http://localhost:8300/mcp endpoint: http://localhost:8300/mcp
``` ```
> **Note:** The**/scripts/ folder** has automated mcp server startup scripts for educational purposes. > **Note:** The **/scripts/** folder contains automated MCP Server startup scripts for demonstration and educational purposes.
> The **/agent_template_backend folder** has 2 mcp servers configured, one on port 8100 and the other on 8200. These services are ready and configured to run if you want to test the circuit. > The **/agent_template_backend** folder includes two MCP Servers already configured, one running on port **8100** and the other on port **8200**. These services are ready to run if you want to test the full flow.
> You can customize it to upload all your mcp servers. > You can customize the setup to start all of your MCP Servers.
> Run: **bash ./scripts/run_mcp_servers.sh** > Run: **bash ./scripts/run_mcp_servers.sh**
### 18.3. Test tool through the backend ### 18.3. Start the MCP Gateway
Test through the backend, not directly through the MCP. This way, you validate the complete path: ```bash
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
```
Validations:
```bash
curl http://localhost:8300/health
curl http://localhost:8300/ready
curl http://localhost:8300/v1/tools
```
Test:
curl -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke
### 18.4. Test a Tool Through the Backend
Test through the backend, not directly through MCP. This validates the full path:
```text ```text
backend → MCP Tool Router → MCP Server → response backend → MCP Tool Router → MCP Server → response
@@ -7896,9 +7982,11 @@ curl -X POST http://localhost:8000/debug/mcp/call/consultar_titulo_financeiro \
}' }'
``` ```
> **Note:** In the project, there is also a visual interface for testing: > **Note:** The project also includes a visual interface for testing:
### 18.4. Upload Frontend for testing ### 18.5. Start the Frontend for Testing
Install before the [npm](https://nodejs.org/) and:
```bash ```bash
cd agent_platform_oci cd agent_platform_oci
@@ -7909,18 +7997,45 @@ python -m http.server 5173
Open http://localhost:5173. Open http://localhost:5173.
> **Note:** The frontend is ready to work with the **Agent Gateway**. In the project, see chapters 28 and 28.10 to try it out. Just remember to change the **Backend URL** to **http://localhost:8010** because 8010 is the port where the **Agent Gateway** will be listening. > **Note:** The frontend is designed to work with the **Agent Gateway**. In the project documentation, see chapters 28 and 28.10 for usage examples. Just remember to change **Backend URL** to **http://localhost:8010**, as 8010 is the port where the **Agent Gateway** will be listening.
### 18.5. How to interpret MCP errors ### 18.6. How to Interpret MCP Errors
```text ```text
Tool not found → tools.yaml or wrong tool name. Tool not found → tools.yaml or tool name is incorrect.
Server not found → mcp_servers.yaml does not have the mcp_server indicated by the tool. Server not found → mcp_servers.yaml does not contain the MCP server referenced by the tool.
Connection refused → MCP Server is not running or wrong port. Connection refused → MCP Server is not running or the port is incorrect.
Required parameter missing → identity.yaml or mcp_parameter_mapping.yaml incorrect. Missing required parameter → identity.yaml or mcp_parameter_mapping.yaml is incorrect.
Timeout → Slow MCP, wrong endpoint, VPN, DNS or real system unavailable. Timeout → Slow MCP, wrong endpoint, VPN, DNS, or target system unavailable.
``` ```
### 18.7. Start the Agent Gateway
```bash
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
```
Validations:
```bash
curl http://localhost:9000/health
```
Test:
curl -X POST http://localhost:9000/gateway/message
--- ---
## 19. Build with Docker ## 19. Build with Docker

View File

@@ -0,0 +1,14 @@
from __future__ import annotations
import os
from pathlib import Path
from typing import Any
import yaml
def load_gateway_governance_config(path: str | None = None) -> dict[str, Any]:
config_path = Path(path or os.getenv("AGENT_GATEWAY_GOVERNANCE_CONFIG", "config/gateway_governance.yaml"))
if not config_path.exists():
return {}
return yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,14 @@
from __future__ import annotations
import json
import logging
from typing import Any
logger = logging.getLogger("agent_gateway.governance")
def audit_event(name: str, payload: dict[str, Any]) -> None:
safe = dict(payload)
if "message" in safe:
safe["message_len"] = len(str(safe.pop("message") or ""))
logger.info("%s %s", name, json.dumps(safe, ensure_ascii=False, default=str))

View File

@@ -0,0 +1,11 @@
from __future__ import annotations
from typing import Any
class EvaluationHooks:
def before_backend_call(self, request_payload: dict[str, Any]) -> dict[str, Any]:
return request_payload
def after_backend_call(self, response_payload: dict[str, Any]) -> dict[str, Any]:
return response_payload

View File

@@ -0,0 +1,66 @@
from __future__ import annotations
from typing import Any
class ModelPolicyError(RuntimeError):
pass
class ModelPolicyResolver:
def __init__(self, config: dict[str, Any]):
self.config = config or {}
def resolve_profile(
self,
*,
tenant_id: str,
agent_id: str | None,
operation: str,
requested_profile: str | None = None,
) -> dict[str, Any]:
profiles = self.config.get("profiles", {}) or {}
operation_profiles = self.config.get("operation_profiles", {}) or {}
profile_name = requested_profile or operation_profiles.get(operation) or "default"
profile = profiles.get(profile_name)
if not profile:
raise ModelPolicyError(f"Model profile not found: {profile_name}")
self._validate_policy(
tenant_id=tenant_id,
agent_id=agent_id,
profile_name=profile_name,
profile=profile,
)
return {
"profile": profile_name,
"provider": profile.get("provider"),
"model": profile.get("model"),
"parameters": {
k: v for k, v in profile.items()
if k not in {"provider", "model"}
},
}
def _validate_policy(
self,
*,
tenant_id: str,
agent_id: str | None,
profile_name: str,
profile: dict[str, Any],
) -> None:
policies = self.config.get("policies", {}) or {}
tenant_policies = policies.get("tenants", {}) or {}
tenant_policy = tenant_policies.get(tenant_id) or tenant_policies.get("default") or {}
allowed_profiles = tenant_policy.get("allowed_profiles")
if allowed_profiles and profile_name not in allowed_profiles:
raise ModelPolicyError(f"Profile not allowed for tenant={tenant_id}: {profile_name}")
allowed_providers = tenant_policy.get("allowed_providers")
provider = profile.get("provider")
if allowed_providers and provider not in allowed_providers:
raise ModelPolicyError(f"Provider not allowed for tenant={tenant_id}: {provider}")

View File

@@ -0,0 +1,35 @@
from __future__ import annotations
import time
from collections import defaultdict, deque
from typing import Any
class RateLimitExceeded(RuntimeError):
pass
class InMemoryRateLimiter:
def __init__(self, config: dict[str, Any]):
self.config = config or {}
self.events: dict[str, deque[float]] = defaultdict(deque)
def check(self, *, tenant_id: str, agent_id: str | None, channel: str | None) -> None:
default_limit = ((self.config.get("default") or {}).get("requests_per_minute")) or 600
agent_limits = self.config.get("agents") or {}
channel_limits = self.config.get("channels") or {}
limit = default_limit
if agent_id and agent_id in agent_limits:
limit = agent_limits[agent_id].get("requests_per_minute", limit)
if channel and channel in channel_limits:
limit = min(limit, channel_limits[channel].get("requests_per_minute", limit))
key = f"{tenant_id}:{agent_id or '*'}:{channel or '*'}"
now = time.time()
bucket = self.events[key]
while bucket and bucket[0] < now - 60:
bucket.popleft()
if len(bucket) >= int(limit):
raise RateLimitExceeded(f"Gateway rate limit exceeded for {key}: {limit}/min")
bucket.append(now)

View File

@@ -0,0 +1,14 @@
from __future__ import annotations
from typing import Any
class UsageRecorder:
def record_gateway_request(self, payload: dict[str, Any]) -> None:
return None
def record_model_policy(self, payload: dict[str, Any]) -> None:
return None
def record_backend_response(self, payload: dict[str, Any]) -> None:
return None

View File

@@ -0,0 +1,78 @@
from __future__ import annotations
from typing import Any
from fastapi import HTTPException
from app.config.governance_loader import load_gateway_governance_config
from app.governance.audit import audit_event
from app.governance.evaluation_hooks import EvaluationHooks
from app.governance.model_policies import ModelPolicyError, ModelPolicyResolver
from app.governance.rate_limit import InMemoryRateLimiter, RateLimitExceeded
from app.governance.usage import UsageRecorder
class AgentGatewayGovernance:
def __init__(self, config: dict[str, Any] | None = None):
self.config = config if config is not None else load_gateway_governance_config()
self.model_resolver = ModelPolicyResolver((self.config.get("model_governance") or {}))
self.rate_limiter = InMemoryRateLimiter((self.config.get("rate_limits") or {}))
self.usage = UsageRecorder()
self.eval_hooks = EvaluationHooks()
def prepare_backend_request(self, gateway_request: dict[str, Any]) -> tuple[dict[str, Any], dict[str, str]]:
tenant_id = gateway_request.get("tenant_id") or "default"
agent_id = gateway_request.get("agent_id")
channel = gateway_request.get("channel")
payload = gateway_request.get("payload") or {}
metadata = payload.setdefault("metadata", {})
try:
self.rate_limiter.check(tenant_id=tenant_id, agent_id=agent_id, channel=channel)
model_policy = self.model_resolver.resolve_profile(
tenant_id=tenant_id,
agent_id=agent_id,
operation=metadata.get("operation") or "agent.final_answer",
requested_profile=metadata.get("llm_profile"),
)
metadata["model_policy"] = model_policy
headers = {
"X-Agent-Gateway-Governance": "enabled",
"X-Model-Profile": str(model_policy.get("profile") or ""),
"X-Model-Provider": str(model_policy.get("provider") or ""),
"X-Model-Name": str(model_policy.get("model") or ""),
}
audit_event("agent_gateway.request.governed", {
"tenant_id": tenant_id,
"agent_id": agent_id,
"channel": channel,
"model_policy": model_policy,
"request_id": metadata.get("request_id"),
"message": payload.get("message"),
})
self.usage.record_gateway_request({
"tenant_id": tenant_id,
"agent_id": agent_id,
"channel": channel,
"metadata": metadata,
})
governed = self.eval_hooks.before_backend_call(gateway_request)
return governed, headers
except RateLimitExceeded as exc:
raise HTTPException(status_code=429, detail=str(exc)) from exc
except ModelPolicyError as exc:
raise HTTPException(status_code=403, detail=str(exc)) from exc
def process_backend_response(self, response_payload: dict[str, Any]) -> dict[str, Any]:
response_payload = self.eval_hooks.after_backend_call(response_payload)
self.usage.record_backend_response(response_payload)
audit_event("agent_gateway.response.completed", {
"metadata": response_payload.get("metadata") if isinstance(response_payload, dict) else {},
})
return response_payload

View File

@@ -0,0 +1,40 @@
from __future__ import annotations
import os
from typing import Any
import httpx
from fastapi import APIRouter, HTTPException, Request
from app.governance_middleware import AgentGatewayGovernance
router = APIRouter()
governance = AgentGatewayGovernance()
@router.post("/gateway/message/governed")
async def governed_gateway_message(request: Request):
"""Example governed proxy route.
Use as reference to patch the existing /gateway/message handler.
"""
body: dict[str, Any] = await request.json()
backend_url = os.getenv("DEFAULT_AGENT_BACKEND_URL", "http://localhost:8000")
governed_body, headers = governance.prepare_backend_request(body)
try:
async with httpx.AsyncClient(timeout=90) as client:
resp = await client.post(
f"{backend_url.rstrip('/')}/gateway/message",
json=governed_body,
headers=headers,
)
resp.raise_for_status()
data = resp.json()
return governance.process_backend_response(data)
except httpx.HTTPStatusError as exc:
raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text) from exc
except Exception as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc

View File

@@ -2,7 +2,7 @@ default_backend: contas
backends: backends:
contas: contas:
url: http://localhost:8001 url: http://localhost:8000
description: Backend responsável por faturas, contas, pagamentos, consumo, segunda via e contestação. description: Backend responsável por faturas, contas, pagamentos, consumo, segunda via e contestação.
domains: [contas, fatura, pagamento, consumo, contestacao] domains: [contas, fatura, pagamento, consumo, contestacao]
keywords: [fatura, conta, boleto, pagamento, consumo, segunda via, contestar, contestação, valor, cobrança] keywords: [fatura, conta, boleto, pagamento, consumo, segunda via, contestar, contestação, valor, cobrança]
@@ -14,7 +14,7 @@ backends:
default_agent_id: telecom_contas default_agent_id: telecom_contas
ofertas: ofertas:
url: http://localhost:8002 url: http://localhost:8001
description: Backend responsável por ofertas, planos, upgrades, retenção e contratação. description: Backend responsável por ofertas, planos, upgrades, retenção e contratação.
domains: [ofertas, planos, retenção, contratação] domains: [ofertas, planos, retenção, contratação]
keywords: [oferta, plano, contratar, upgrade, desconto, promoção, pacote, retenção, cancelar serviço] keywords: [oferta, plano, contratar, upgrade, desconto, promoção, pacote, retenção, cancelar serviço]
@@ -26,7 +26,7 @@ backends:
default_agent_id: telecom_ofertas default_agent_id: telecom_ofertas
suporte: suporte:
url: http://localhost:8003 url: http://localhost:8002
description: Backend responsável por suporte técnico, falhas, rede, internet e atendimento operacional. description: Backend responsável por suporte técnico, falhas, rede, internet e atendimento operacional.
domains: [suporte, técnico, rede, internet] domains: [suporte, técnico, rede, internet]
keywords: [internet, sinal, rede, suporte, técnico, problema, falha, sem conexão, modem] keywords: [internet, sinal, rede, suporte, técnico, problema, falha, sem conexão, modem]

View File

@@ -0,0 +1,56 @@
model_governance:
profiles:
default:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.2
max_tokens: 2048
router:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 500
judge:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 800
operation_profiles:
router.intent: router
agent.final_answer: default
judge.response_quality: judge
policies:
tenants:
default:
allowed_providers:
- oci_openai
- oci_sdk
- mock
allowed_profiles:
- default
- router
- judge
rate_limits:
default:
requests_per_minute: 600
channels:
whatsapp:
requests_per_minute: 120
web:
requests_per_minute: 300
agents:
telecom_contas:
requests_per_minute: 180
backend_headers:
propagate_model_policy: true
propagate_trace_context: true
evaluation:
enabled: true
sample_rate: 1.0

View File

@@ -1,7 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
COPY apps/ai_gateway/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt
COPY apps/ai_gateway /app
EXPOSE 9100
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "9100"]

View File

@@ -1,18 +0,0 @@
# AI Gateway
Camada desacoplada para abstração, roteamento, controle e governança de chamadas LLM.
Este componente não substitui o Agent Runtime. Ele centraliza políticas de modelo, seleção de provider, fallback, telemetria e controles corporativos.
## Rotas
- `GET /health`
- `GET /models`
- `POST /v1/chat/completions`
## Execução local
```bash
cd apps/ai_gateway
uvicorn app.main:app --host 0.0.0.0 --port 9100 --reload
```

View File

@@ -1,81 +0,0 @@
from __future__ import annotations
import os
from typing import Any, Literal
import httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
class Message(BaseModel):
role: Literal["system", "user", "assistant", "tool"]
content: str | list[Any] | None = None
class ChatCompletionRequest(BaseModel):
model: str | None = None
messages: list[Message]
temperature: float | None = None
max_tokens: int | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
class ModelRoute(BaseModel):
provider: str
model: str
base_url: str | None = None
app = FastAPI(title="Agent Framework OCI - AI Gateway", version="0.1.0")
def resolve_route(requested_model: str | None, metadata: dict[str, Any]) -> ModelRoute:
provider = metadata.get("provider") or os.getenv("AI_GATEWAY_DEFAULT_PROVIDER", "oci_openai")
model = requested_model or metadata.get("profile_model") or os.getenv("AI_GATEWAY_DEFAULT_MODEL", "openai.gpt-4.1")
base_url = os.getenv("AI_GATEWAY_OPENAI_COMPAT_BASE_URL")
return ModelRoute(provider=provider, model=model, base_url=base_url)
@app.get("/health")
def health() -> dict[str, Any]:
return {"status": "ok", "component": "ai_gateway"}
@app.get("/models")
def models() -> dict[str, Any]:
return {
"default_provider": os.getenv("AI_GATEWAY_DEFAULT_PROVIDER", "oci_openai"),
"default_model": os.getenv("AI_GATEWAY_DEFAULT_MODEL", "openai.gpt-4.1"),
"purpose": "model routing, policy control and LLM abstraction",
}
@app.post("/v1/chat/completions")
async def chat_completions(payload: ChatCompletionRequest) -> dict[str, Any]:
route = resolve_route(payload.model, payload.metadata)
# Safe default: when no upstream is configured, return route decision only.
# Production deployments should configure AI_GATEWAY_OPENAI_COMPAT_BASE_URL and credentials.
if not route.base_url:
return {
"gateway": "ai_gateway",
"mode": "dry_run",
"route": route.model_dump(),
"message": "No upstream base URL configured. Set AI_GATEWAY_OPENAI_COMPAT_BASE_URL to proxy requests.",
}
api_key = os.getenv("AI_GATEWAY_API_KEY") or os.getenv("OCI_GENAI_API_KEY") or os.getenv("OPENAI_API_KEY")
headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
body = payload.model_dump(exclude_none=True)
body["model"] = route.model
try:
async with httpx.AsyncClient(timeout=float(os.getenv("AI_GATEWAY_TIMEOUT_SECONDS", "60"))) as client:
resp = await client.post(f"{route.base_url.rstrip('/')}/chat/completions", json=body, headers=headers)
resp.raise_for_status()
data = resp.json()
if isinstance(data, dict):
data.setdefault("gateway", "ai_gateway")
data.setdefault("route", route.model_dump())
return data
except httpx.HTTPError as exc:
raise HTTPException(status_code=502, detail=f"AI upstream request failed: {exc}") from exc

View File

@@ -1,5 +0,0 @@
fastapi>=0.111
uvicorn[standard]>=0.30
pydantic>=2
httpx>=0.27
PyYAML>=6

View File

@@ -0,0 +1,3 @@
MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
MCP_GATEWAY_HOST=0.0.0.0
MCP_GATEWAY_PORT=8300

View File

@@ -1,7 +1,15 @@
FROM python:3.11-slim FROM python:3.12-slim
WORKDIR /app WORKDIR /app
COPY apps/mcp_gateway/requirements.txt /app/requirements.txt COPY apps/mcp_gateway/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt RUN pip install --no-cache-dir -r /app/requirements.txt
COPY apps/mcp_gateway /app
EXPOSE 9200 COPY apps/mcp_gateway/app /app/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "9200"] COPY apps/mcp_gateway/config /app/config
ENV MCP_GATEWAY_CONFIG_PATH=/app/config/mcp_gateway.yaml
EXPOSE 8300
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8300"]

View File

@@ -0,0 +1 @@

View File

@@ -1,74 +1,232 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import hashlib
import json
import os import os
import time
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
import httpx import httpx
import yaml import yaml
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
class ToolInvokeRequest(BaseModel): class BusinessContext(BaseModel):
customer_key: str | None = None
contract_key: str | None = None
interaction_key: str | None = None
account_key: str | None = None
resource_key: str | None = None
session_key: str | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
class ToolInvocation(BaseModel):
tenant_id: str = "default"
agent_id: str
channel: str | None = None
tool_name: str
arguments: dict[str, Any] = Field(default_factory=dict) arguments: dict[str, Any] = Field(default_factory=dict)
context: dict[str, Any] = Field(default_factory=dict) business_context: BusinessContext = Field(default_factory=BusinessContext)
metadata: dict[str, Any] = Field(default_factory=dict)
app = FastAPI(title="Agent Framework OCI - MCP Gateway", version="0.1.0") class ToolResult(BaseModel):
tool_name: str
version: str | None = None
ok: bool
data: Any = None
error: str | None = None
cache: dict[str, Any] = Field(default_factory=dict)
latency_ms: int = 0
metadata: dict[str, Any] = Field(default_factory=dict)
def load_config() -> dict[str, Any]: def load_config() -> dict[str, Any]:
config_path = Path(os.getenv("MCP_GATEWAY_CONFIG", "config/mcp_servers.yaml")) path = Path(os.getenv("MCP_GATEWAY_CONFIG_PATH", "config/mcp_gateway.yaml"))
if not config_path.exists(): return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
return {"servers": {}}
return yaml.safe_load(config_path.read_text()) or {"servers": {}}
def find_tool(tool_name: str) -> tuple[str, dict[str, Any]] | None: config = load_config()
cfg = load_config() cache: dict[str, tuple[float, Any]] = {}
for server_name, server in (cfg.get("servers") or {}).items(): app = FastAPI(title="Agent Platform OCI - MCP Gateway", version="1.0.0")
if tool_name in (server.get("tools") or []):
return server_name, server
return None def audit(name: str, payload: dict[str, Any]) -> None:
print(json.dumps({"event": name, **payload}, ensure_ascii=False, default=str))
def auth_check(authorization: str | None) -> None:
auth = config.get("auth") or {}
if not auth.get("enabled", False):
return
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="Missing MCP Gateway bearer token")
token = authorization.split(" ", 1)[1]
if token not in (auth.get("static_tokens") or {}):
raise HTTPException(status_code=403, detail="Invalid MCP Gateway token")
def map_arguments(tool_name: str, args: dict[str, Any], bc: dict[str, Any]) -> dict[str, Any]:
result = dict(args or {})
for source, target in ((config.get("parameter_mapping") or {}).get(tool_name) or {}).items():
if bc.get(source) is not None and target not in result:
result[target] = bc[source]
return result
def cache_key(tenant_id: str, agent_id: str, tool_name: str, version: str, args: dict[str, Any]) -> str:
digest = hashlib.sha256(json.dumps(args, sort_keys=True, ensure_ascii=False, default=str).encode()).hexdigest()
return f"mcp:{tenant_id}:{agent_id}:{tool_name}:{version}:{digest}"
async def post_with_retry(url: str, payload: dict[str, Any], timeout: int, retry: dict[str, Any]) -> Any:
attempts = int(retry.get("max_attempts", 1)) if retry.get("enabled", False) else 1
backoff_ms = int(retry.get("backoff_ms", 250))
last_exc: Exception | None = None
for attempt in range(attempts):
try:
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.post(url, json=payload)
resp.raise_for_status()
return resp.json()
except Exception as exc:
last_exc = exc
if attempt < attempts - 1:
await asyncio.sleep(backoff_ms / 1000)
raise RuntimeError(str(last_exc))
def build_server_payload(tool_name: str, args: dict[str, Any], server: dict[str, Any], tool: dict[str, Any]) -> dict[str, Any]:
"""Builds the payload expected by the downstream MCP server.
The example servers under mcp/servers expose the framework legacy contract:
POST /mcp/tools/call {tool_name, arguments}. Some mocks expose direct
tool endpoints that accept only the argument object. The gateway supports
both shapes through the server/tool config.
"""
protocol = str(tool.get("protocol") or server.get("protocol") or "legacy_http")
if protocol in {"legacy_http", "framework_http"}:
return {"tool_name": tool_name, "arguments": args or {}}
return args or {}
def normalize_server_response(data: Any) -> tuple[bool, Any, str | None, dict[str, Any]]:
if isinstance(data, dict) and ("ok" in data or "result" in data or "error" in data):
ok = bool(data.get("ok", not data.get("error")))
return ok, data.get("result", data.get("data")), data.get("error"), data.get("metadata") or {}
return True, data, None, {}
@app.get("/health") @app.get("/health")
def health() -> dict[str, Any]: async def health():
return {"status": "ok", "component": "mcp_gateway"} return {"status": "ok", "service": "mcp_gateway"}
@app.get("/tools") @app.get("/ready")
def tools() -> dict[str, Any]: async def ready():
cfg = load_config() enabled_tools = [k for k, v in (config.get("tools") or {}).items() if v.get("enabled", True)]
result = [] return {"status": "ready", "tools": enabled_tools}
for server_name, server in (cfg.get("servers") or {}).items():
for tool in server.get("tools") or []:
result.append({"name": tool, "server": server_name})
return {"tools": result}
@app.post("/tools/{tool_name}/invoke") @app.get("/v1/tools")
async def invoke_tool(tool_name: str, payload: ToolInvokeRequest) -> dict[str, Any]: async def tools():
found = find_tool(tool_name) return {"tools": [{"name": name, **cfg} for name, cfg in (config.get("tools") or {}).items()]}
if not found:
raise HTTPException(status_code=404, detail=f"Tool not registered in MCP Gateway: {tool_name}")
server_name, server = found @app.get("/v1/tools/{tool_name}")
base_url = server.get("base_url") async def tool_detail(tool_name: str):
if not base_url: tool = (config.get("tools") or {}).get(tool_name)
raise HTTPException(status_code=500, detail=f"Server {server_name} has no base_url") if not tool:
raise HTTPException(status_code=404, detail=f"Tool not found: {tool_name}")
return {"name": tool_name, **tool}
@app.post("/v1/tools/{tool_name}/invoke", response_model=ToolResult)
async def invoke(tool_name: str, invocation: ToolInvocation, authorization: str | None = Header(default=None)):
started = time.perf_counter()
auth_check(authorization)
tool = (config.get("tools") or {}).get(tool_name)
if not tool or not tool.get("enabled", True):
raise HTTPException(status_code=404, detail=f"Tool not found or disabled: {tool_name}")
if invocation.tool_name != tool_name:
raise HTTPException(status_code=422, detail="Path tool_name and body tool_name differ")
allowed_agents = tool.get("allowed_agents") or []
if allowed_agents and invocation.agent_id not in allowed_agents:
raise HTTPException(status_code=403, detail=f"Agent not allowed: {invocation.agent_id}")
allowed_channels = tool.get("allowed_channels") or []
if invocation.channel and allowed_channels and invocation.channel not in allowed_channels:
raise HTTPException(status_code=403, detail=f"Channel not allowed: {invocation.channel}")
bc = invocation.business_context.model_dump()
missing = [k for k in tool.get("required_business_keys", []) if not bc.get(k)]
if missing:
raise HTTPException(status_code=422, detail={"missing_business_keys": missing})
version = str(tool.get("version", "1.0.0"))
args = map_arguments(tool_name, invocation.arguments, bc)
ttl = int(tool.get("cache_ttl_seconds", 0) or 0)
ck = None
if tool.get("idempotent", False) and ttl > 0:
ck = cache_key(invocation.tenant_id, invocation.agent_id, tool_name, version, args)
cached = cache.get(ck)
if cached and cached[0] > time.time():
audit("mcp.cache.hit", {"tool": tool_name, "agent_id": invocation.agent_id})
return ToolResult(
tool_name=tool_name,
version=version,
ok=True,
data=cached[1],
cache={"hit": True, "key": ck, "ttl_seconds": ttl},
latency_ms=int((time.perf_counter() - started) * 1000),
)
server = (config.get("servers") or {}).get(tool.get("server"))
if not server or not server.get("enabled", True):
raise HTTPException(status_code=503, detail=f"MCP server unavailable: {tool.get('server')}")
url = f"{server['url'].rstrip('/')}{tool.get('endpoint')}"
audit("mcp.tool.started", {"tool": tool_name, "version": version, "agent_id": invocation.agent_id, "server": tool.get("server")})
# Conventional endpoint. Concrete MCP servers may adapt this via an adapter later.
url = f"{base_url.rstrip('/')}/tools/{tool_name}/invoke"
try: try:
async with httpx.AsyncClient(timeout=float(os.getenv("MCP_GATEWAY_TIMEOUT_SECONDS", "30"))) as client: raw_data = await post_with_retry(
resp = await client.post(url, json=payload.model_dump()) url=url,
resp.raise_for_status() payload=build_server_payload(tool_name, args, server, tool),
data = resp.json() timeout=int(tool.get("timeout_seconds") or server.get("timeout_seconds") or 30),
if isinstance(data, dict): retry=tool.get("retry") or {},
data.setdefault("gateway", "mcp_gateway") )
data.setdefault("server", server_name) ok, data, error, server_metadata = normalize_server_response(raw_data)
data.setdefault("tool", tool_name) if ck and ttl > 0 and ok:
return data cache[ck] = (time.time() + ttl, data)
except httpx.HTTPError as exc:
raise HTTPException(status_code=502, detail=f"MCP upstream request failed: {exc}") from exc latency_ms = int((time.perf_counter() - started) * 1000)
audit("mcp.tool.completed", {"tool": tool_name, "latency_ms": latency_ms, "ok": ok})
return ToolResult(
tool_name=tool_name,
version=version,
ok=ok,
data=data,
error=error,
cache={"hit": False, "key": ck, "ttl_seconds": ttl},
latency_ms=latency_ms,
metadata={"server": tool.get("server"), **server_metadata},
)
except Exception as exc:
latency_ms = int((time.perf_counter() - started) * 1000)
audit("mcp.tool.failed", {"tool": tool_name, "latency_ms": latency_ms, "error": str(exc)})
return ToolResult(
tool_name=tool_name,
version=version,
ok=False,
error=str(exc),
latency_ms=latency_ms,
metadata={"server": tool.get("server")},
)

View File

@@ -0,0 +1,172 @@
# Dedicated MCP Gateway configuration.
# The agent backend/framework calls this gateway; this gateway calls the final MCP servers.
servers:
telecom:
enabled: true
protocol: legacy_http
transport: http
# Local run: uvicorn mcp.servers.telecom_mcp_server.main:app --port 8100
url: http://localhost:8100/mcp
timeout_seconds: 30
retail:
enabled: true
protocol: legacy_http
transport: http
# Local run: uvicorn mcp.servers.retail_mcp_server.main:app --port 8200
url: http://localhost:8200/mcp
timeout_seconds: 30
tools:
consultar_fatura:
version: 1.0.0
server: telecom
endpoint: /tools/call
protocol: legacy_http
enabled: true
idempotent: true
cache_ttl_seconds: 300
timeout_seconds: 30
retry: {enabled: true, max_attempts: 2, backoff_ms: 250}
allowed_agents: []
allowed_channels: []
required_business_keys: []
consultar_pagamentos:
version: 1.0.0
server: telecom
endpoint: /tools/call
protocol: legacy_http
enabled: true
idempotent: true
cache_ttl_seconds: 300
timeout_seconds: 30
retry: {enabled: true, max_attempts: 2, backoff_ms: 250}
allowed_agents: []
allowed_channels: []
required_business_keys: []
consultar_plano:
version: 1.0.0
server: telecom
endpoint: /tools/call
protocol: legacy_http
enabled: true
idempotent: true
cache_ttl_seconds: 300
timeout_seconds: 30
retry: {enabled: true, max_attempts: 2, backoff_ms: 250}
allowed_agents: []
allowed_channels: []
required_business_keys: []
listar_servicos:
version: 1.0.0
server: telecom
endpoint: /tools/call
protocol: legacy_http
enabled: true
idempotent: true
cache_ttl_seconds: 300
timeout_seconds: 30
retry: {enabled: true, max_attempts: 2, backoff_ms: 250}
allowed_agents: []
allowed_channels: []
required_business_keys: []
consultar_pedido:
version: 1.0.0
server: retail
endpoint: /tools/call
protocol: legacy_http
enabled: true
idempotent: true
cache_ttl_seconds: 300
timeout_seconds: 30
retry: {enabled: true, max_attempts: 2, backoff_ms: 250}
allowed_agents: []
allowed_channels: []
required_business_keys: []
consultar_entrega:
version: 1.0.0
server: retail
endpoint: /tools/call
protocol: legacy_http
enabled: true
idempotent: true
cache_ttl_seconds: 300
timeout_seconds: 30
retry: {enabled: true, max_attempts: 2, backoff_ms: 250}
allowed_agents: []
allowed_channels: []
required_business_keys: []
solicitar_troca:
version: 1.0.0
server: retail
endpoint: /tools/call
protocol: legacy_http
enabled: true
idempotent: false
cache_ttl_seconds: 0
timeout_seconds: 30
retry: {enabled: false}
allowed_agents: []
allowed_channels: []
required_business_keys: []
solicitar_devolucao:
version: 1.0.0
server: retail
endpoint: /tools/call
protocol: legacy_http
enabled: true
idempotent: false
cache_ttl_seconds: 0
timeout_seconds: 30
retry: {enabled: false}
allowed_agents: []
allowed_channels: []
required_business_keys: []
# Optional mapping if the backend sends canonical BusinessContext directly to the gateway.
# In the normal framework path, the framework already maps before calling the gateway.
parameter_mapping:
consultar_fatura:
customer_key: msisdn
contract_key: invoice_id
interaction_key: ura_call_id
session_key: session_id
consultar_pagamentos:
customer_key: msisdn
interaction_key: ura_call_id
session_key: session_id
consultar_plano:
customer_key: msisdn
resource_key: asset_id
contract_key: asset_id
session_key: session_id
listar_servicos:
customer_key: msisdn
session_key: session_id
consultar_pedido:
customer_key: customer_id
contract_key: order_id
session_key: session_id
consultar_entrega:
contract_key: order_id
session_key: session_id
solicitar_troca:
contract_key: order_id
session_key: session_id
solicitar_devolucao:
contract_key: order_id
session_key: session_id
auth:
enabled: false
static_tokens:
runtime-local-token:
agents: []

View File

@@ -1,5 +1,5 @@
fastapi>=0.111 fastapi>=0.110
uvicorn[standard]>=0.30 uvicorn[standard]>=0.27
pydantic>=2 pydantic>=2.6
PyYAML>=6.0
httpx>=0.27 httpx>=0.27
PyYAML>=6

View File

@@ -0,0 +1,20 @@
services:
mcp_gateway:
build:
context: ../..
dockerfile: apps/mcp_gateway/Dockerfile
ports:
- "8300:8300"
depends_on:
- mock_telecom_mcp
mock_telecom_mcp:
image: python:3.12-slim
working_dir: /app
command: >
sh -c "pip install -r requirements.txt &&
uvicorn app:app --host 0.0.0.0 --port 8001"
volumes:
- ../../mcp/servers/mock_telecom_mcp:/app
ports:
- "8001:8001"

View File

@@ -3,34 +3,34 @@ kind: Deployment
metadata: metadata:
name: mcp-gateway name: mcp-gateway
labels: labels:
app.kubernetes.io/name: mcp-gateway app: mcp-gateway
app.kubernetes.io/part-of: agent-framework-oci
spec: spec:
replicas: 1 replicas: 2
selector: selector:
matchLabels: matchLabels:
app.kubernetes.io/name: mcp-gateway app: mcp-gateway
template: template:
metadata: metadata:
labels: labels:
app.kubernetes.io/name: mcp-gateway app: mcp-gateway
app.kubernetes.io/part-of: agent-framework-oci
spec: spec:
serviceAccountName: agent-framework-oci serviceAccountName: mcp-gateway-sa
containers: containers:
- name: mcp-gateway - name: mcp-gateway
image: mcp-gateway:latest image: registry.example.com/agent-platform/mcp-gateway:1.0.0
imagePullPolicy: IfNotPresent
ports: ports:
- containerPort: 9200 - containerPort: 8300
env:
- name: MCP_GATEWAY_CONFIG_PATH
value: /app/config/mcp_gateway.yaml
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /health path: /ready
port: 9200 port: 8300
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /health path: /health
port: 9200 port: 8300
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
@@ -38,8 +38,7 @@ metadata:
name: mcp-gateway name: mcp-gateway
spec: spec:
selector: selector:
app.kubernetes.io/name: mcp-gateway app: mcp-gateway
ports: ports:
- name: http - port: 8300
port: 9200 targetPort: 8300
targetPort: 9200

View File

@@ -10,8 +10,8 @@ Esta entrega reorganiza o projeto para uma arquitetura corporativa modular, pres
| `agent_gateway/` | `apps/agent_gateway/` | | `agent_gateway/` | `apps/agent_gateway/` |
| `channel_gateway/` | `apps/channel_gateway/` | | `channel_gateway/` | `apps/channel_gateway/` |
| `agent_frontend/` | `apps/agent_frontend/` | | `agent_frontend/` | `apps/agent_frontend/` |
| `agent_template_backend/` | `templates/backend/` | | `agent_template_backend/` | `templates/agent_template_backend/` |
| `agent_template_backend_day_zero/` | `templates/backend_day_zero/` | | `agent_template_backend_day_zero/` | `templates/agent_template_backend_day_zero/` |
| `mcp_servers/` | `mcp/servers/` | | `mcp_servers/` | `mcp/servers/` |
| `agent_certification_tests/` | `evals/certification/` | | `agent_certification_tests/` | `evals/certification/` |
| `agent_framework_oci_evaluator/evaluator/` | `evals/offline/evaluator/` | | `agent_framework_oci_evaluator/evaluator/` | `evals/offline/evaluator/` |

View File

@@ -1,30 +1,16 @@
# MCP servers registry. # Logical MCP server registry used by the framework for tool ownership.
# transport=http keeps the legacy framework mock contract: # With MCP_GATEWAY_ENABLED=true, the framework does NOT call these endpoints directly;
# GET <endpoint>/tools/list # it calls MCP_GATEWAY_URL and the dedicated gateway routes to the final servers.
# POST <endpoint>/tools/call # Keep these logical names aligned with config/tools.yaml mcp_server values.
# transport=fastmcp uses official MCP Streamable HTTP, typically endpoint http://host:port/mcp
# transport=sse uses official MCP SSE, typically endpoint http://host:port/sse
servers: servers:
# telecom:
# enabled: true
# transport: fastmcp
# endpoint: http://localhost:8001/mcp
# description: Telecom FastMCP server using official MCP protocol
#
# retail:
# enabled: true
# transport: fastmcp
# endpoint: http://localhost:8002/mcp
# description: Retail FastMCP server using official MCP protocol
telecom: telecom:
enabled: true enabled: true
transport: http transport: http
endpoint: http://localhost:8100/mcp endpoint: http://localhost:8100/mcp
description: Telecom legacy HTTP mock MCP server description: Logical Telecom MCP server. Direct endpoint is kept only for fallback when MCP_GATEWAY_ENABLED=false.
retail: retail:
enabled: true enabled: true
transport: http transport: http
endpoint: http://localhost:8200/mcp endpoint: http://localhost:8200/mcp
description: Retail legacy HTTP mock MCP server description: Logical Retail MCP server. Direct endpoint is kept only for fallback when MCP_GATEWAY_ENABLED=false.

View File

@@ -170,6 +170,15 @@ class Settings(BaseSettings):
IDENTITY_CONFIG_PATH: str = './config/identity.yaml' IDENTITY_CONFIG_PATH: str = './config/identity.yaml'
MCP_PARAMETER_MAPPING_PATH: str = './config/mcp_parameter_mapping.yaml' MCP_PARAMETER_MAPPING_PATH: str = './config/mcp_parameter_mapping.yaml'
MCP_TOOL_TIMEOUT_SECONDS: int = 30 MCP_TOOL_TIMEOUT_SECONDS: int = 30
# When enabled, the framework routes tool calls to the dedicated MCP Gateway
# instead of calling individual MCP servers directly. The gateway then owns
# server selection, retry, cache and policy enforcement.
MCP_GATEWAY_ENABLED: bool = False
MCP_GATEWAY_URL: str = 'http://localhost:8300'
MCP_GATEWAY_TIMEOUT_SECONDS: int = 60
MCP_GATEWAY_TOKEN: str | None = None
MCP_GATEWAY_AGENT_ID: str = 'telecom_contas'
MCP_GATEWAY_TENANT_ID: str = 'default'
DEFAULT_CHANNEL: str = 'web' DEFAULT_CHANNEL: str = 'web'
# Agent Framework channel input mode. # Agent Framework channel input mode.

View File

@@ -0,0 +1,27 @@
from __future__ import annotations
from typing import Any
def get_gateway_model_policy(state: dict[str, Any]) -> dict[str, Any] | None:
metadata = state.get("metadata") or {}
policy = metadata.get("model_policy")
return policy if isinstance(policy, dict) else None
def apply_gateway_model_policy_to_llm_kwargs(
state: dict[str, Any],
fallback_profile: dict[str, Any] | None = None,
) -> dict[str, Any]:
policy = get_gateway_model_policy(state)
if not policy:
return fallback_profile or {}
params = dict(policy.get("parameters") or {})
if policy.get("model"):
params["model"] = policy["model"]
if policy.get("provider"):
params["provider"] = policy["provider"]
if policy.get("profile"):
params["profile"] = policy["profile"]
return params

View File

@@ -0,0 +1,3 @@
from .mcp_gateway_client import MCPGatewayClient
__all__ = ["MCPGatewayClient"]

View File

@@ -0,0 +1,50 @@
from __future__ import annotations
from typing import Any
import httpx
class MCPGatewayClient:
def __init__(self, base_url: str, token: str | None = None, timeout_seconds: int = 60):
self.base_url = base_url.rstrip("/")
self.token = token
self.timeout_seconds = timeout_seconds
def _headers(self) -> dict[str, str]:
return {"Authorization": f"Bearer {self.token}"} if self.token else {}
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()

View File

@@ -8,6 +8,7 @@ from agent_framework.identity import MCPParameterMapper
from .registry import MCPRegistry from .registry import MCPRegistry
from .client import MCPHttpClient from .client import MCPHttpClient
from .models import MCPToolResult from .models import MCPToolResult
from agent_framework.gateways import MCPGatewayClient
logger = logging.getLogger("agent_framework.mcp.tool_router") logger = logging.getLogger("agent_framework.mcp.tool_router")
@@ -30,12 +31,26 @@ class MCPToolRouter:
settings.TOOLS_CONFIG_PATH, settings.TOOLS_CONFIG_PATH,
) )
self.client = MCPHttpClient(timeout_seconds=settings.MCP_TOOL_TIMEOUT_SECONDS) self.client = MCPHttpClient(timeout_seconds=settings.MCP_TOOL_TIMEOUT_SECONDS)
self.gateway_enabled = bool(getattr(settings, "MCP_GATEWAY_ENABLED", False))
self.gateway_agent_id = getattr(settings, "MCP_GATEWAY_AGENT_ID", "telecom_contas")
self.gateway_tenant_id = getattr(settings, "MCP_GATEWAY_TENANT_ID", "default")
self.gateway_client = (
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),
)
if self.gateway_enabled
else None
)
self.parameter_mapper = MCPParameterMapper.from_yaml( self.parameter_mapper = MCPParameterMapper.from_yaml(
getattr(settings, "MCP_PARAMETER_MAPPING_PATH", "./config/mcp_parameter_mapping.yaml") getattr(settings, "MCP_PARAMETER_MAPPING_PATH", "./config/mcp_parameter_mapping.yaml")
) )
logger.info( logger.info(
"MCPToolRouter carregado enabled=%s servers=%s tools=%s mapper=%s", "MCPToolRouter carregado enabled=%s gateway_enabled=%s gateway_url=%s servers=%s tools=%s mapper=%s",
self.enabled, self.enabled,
self.gateway_enabled,
getattr(settings, "MCP_GATEWAY_URL", None),
list(self.registry.servers.keys()), list(self.registry.servers.keys()),
list(self.registry.tools.keys()), list(self.registry.tools.keys()),
getattr(settings, "MCP_PARAMETER_MAPPING_PATH", None), getattr(settings, "MCP_PARAMETER_MAPPING_PATH", None),
@@ -116,27 +131,55 @@ class MCPToolRouter:
bool(mapped_arguments.get("invoice_id") or mapped_arguments.get("current_invoice_number")), bool(mapped_arguments.get("invoice_id") or mapped_arguments.get("current_invoice_number")),
) )
async def _execute() -> MCPToolResult:
if self.gateway_enabled and self.gateway_client:
response = await self.gateway_client.invoke_tool(
tenant_id=self.gateway_tenant_id,
agent_id=self.gateway_agent_id,
channel=getattr(self.settings, "DEFAULT_CHANNEL", "web"),
tool_name=tool_name,
arguments=mapped_arguments,
business_context={},
metadata={"routed_by": "agent_framework.mcp.tool_router", "logical_server": server.name},
)
return MCPToolResult(
tool_name=tool_name,
server_name="mcp_gateway",
ok=bool(response.get("ok", False)),
result=response.get("data"),
error=response.get("error"),
metadata={
"transport": "mcp_gateway",
"logical_server": server.name,
**(response.get("metadata") or {}),
"cache": response.get("cache") or {},
"latency_ms": response.get("latency_ms"),
},
)
return await self.client.call_tool(server, tool_name, mapped_arguments)
if self.telemetry: if self.telemetry:
async with self.telemetry.span( async with self.telemetry.span(
"mcp.tool_call", "mcp.tool_call",
tool_name=tool_name, tool_name=tool_name,
mcp_server=server.name, mcp_server=("mcp_gateway" if self.gateway_enabled else server.name),
input=mapped_arguments, input=mapped_arguments,
tags=["mcp", "tool"], tags=["mcp", "tool", "mcp_gateway" if self.gateway_enabled else "mcp_server"],
): ):
result = await self.client.call_tool(server, tool_name, mapped_arguments) result = await _execute()
await self.telemetry.event( await self.telemetry.event(
"mcp.tool_call.completed", "mcp.tool_call.completed",
{ {
"tool_name": tool_name, "tool_name": tool_name,
"server": server.name, "server": "mcp_gateway" if self.gateway_enabled else server.name,
"logical_server": server.name,
"ok": result.ok, "ok": result.ok,
"error": result.error, "error": result.error,
}, },
) )
return result return result
return await self.client.call_tool(server, tool_name, mapped_arguments) return await _execute()
async def call( async def call(
self, self,

View File

@@ -0,0 +1,35 @@
from __future__ import annotations
from typing import Any
from agent_framework.gateways import MCPGatewayClient
class MCPGatewayRuntimeMixin:
mcp_gateway_client: MCPGatewayClient | None = None
async def _invoke_mcp_gateway_tool(
self,
state: dict[str, Any],
tool_name: str,
arguments: dict[str, Any] | None = None,
) -> dict[str, Any]:
if not self.mcp_gateway_client:
raise RuntimeError("MCP Gateway client not configured")
result = await self.mcp_gateway_client.invoke_tool(
tenant_id=state.get("tenant_id", "default"),
agent_id=state.get("agent_id") or state.get("route") or "unknown",
channel=state.get("channel"),
tool_name=tool_name,
arguments=arguments or {},
business_context=state.get("business_context") or {},
metadata={
"session_id": state.get("session_id"),
"conversation_key": state.get("conversation_key"),
"trace_id": (state.get("metadata") or {}).get("trace_id"),
},
)
state.setdefault("mcp_results", []).append(result)
return result

View File

@@ -0,0 +1,29 @@
from fastapi import FastAPI
app = FastAPI(title="Mock Telecom MCP Server")
@app.get("/health")
async def health():
return {"status": "ok", "service": "mock_telecom_mcp"}
@app.post("/tools/consultar_fatura")
async def consultar_fatura(payload: dict):
return {
"invoice_id": payload.get("invoice_id") or "INV-001",
"msisdn": payload.get("msisdn"),
"valor_total": 249.90,
"vencimento": "2026-06-10",
"status": "ABERTA",
}
@app.post("/tools/consultar_pagamentos")
async def consultar_pagamentos(payload: dict):
return {
"msisdn": payload.get("msisdn"),
"pagamentos": [
{"data": "2026-06-05", "valor": 249.90, "status": "CONFIRMADO"}
],
}

View File

@@ -0,0 +1,2 @@
fastapi>=0.110
uvicorn[standard]>=0.27

View File

@@ -7,5 +7,5 @@ requires-python = ">=3.10"
[tool.agent_framework_oci.layout] [tool.agent_framework_oci.layout]
libs = ["libs/agent_framework"] libs = ["libs/agent_framework"]
apps = ["apps/agent_gateway", "apps/channel_gateway", "apps/ai_gateway", "apps/mcp_gateway"] apps = ["apps/agent_gateway", "apps/channel_gateway", "apps/ai_gateway", "apps/mcp_gateway"]
templates = ["templates/backend", "templates/backend_day_zero"] templates = ["templates/agent_template_backend", "templates/agent_template_backend_day_zero"]
evals = ["evals/offline", "evals/certification"] evals = ["evals/offline", "evals/certification"]

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
cd "$(dirname "$0")/../apps/mcp_gateway" cd "$(dirname "$0")/../apps/mcp_gateway"
uvicorn app.main:app --host 0.0.0.0 --port 9200 --reload uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload

View File

@@ -17,7 +17,7 @@ A Agent Platform OCI é composta por componentes reutilizáveis, aplicações de
| `mcp/servers` | Apps | MCP servers de domínio. | | `mcp/servers` | Apps | MCP servers de domínio. |
| `evals/offline` | App/Lib | Avaliação offline/batch. | | `evals/offline` | App/Lib | Avaliação offline/batch. |
| `evals/certification` | Suite | Certificação técnica e funcional. | | `evals/certification` | Suite | Certificação técnica e funcional. |
| `templates/backend` | Template | Scaffold para novos agentes. | | `templates/agent_template_backend` | Template | Scaffold para novos agentes. |
| `specs` | Documentação | Contratos SDD versionados. | | `specs` | Documentação | Contratos SDD versionados. |
| `deploy` | Operação | Docker, Kubernetes e Helm. | | `deploy` | Operação | Docker, Kubernetes e Helm. |
@@ -40,8 +40,8 @@ agent_platform_oci/
│ ├── offline/ │ ├── offline/
│ └── certification/ │ └── certification/
├── templates/ ├── templates/
│ ├── backend/ │ ├── agent_template_backend/
│ └── backend_day_zero/ │ └── agent_template_backend_day_zero/
├── specs/ ├── specs/
├── deploy/ ├── deploy/
│ ├── docker/ │ ├── docker/
@@ -76,7 +76,7 @@ flowchart LR
| Agent Backend / Runtime | 8000 | Kubernetes Deployment | Horizontal com storage externo | | Agent Backend / Runtime | 8000 | Kubernetes Deployment | Horizontal com storage externo |
| Channel Gateway | 7000 | Kubernetes Deployment | Horizontal | | Channel Gateway | 7000 | Kubernetes Deployment | Horizontal |
| AI Gateway | 9100 | Kubernetes Deployment | Horizontal | | AI Gateway | 9100 | Kubernetes Deployment | Horizontal |
| MCP Gateway | 9200 | Kubernetes Deployment | Horizontal | | MCP Gateway | 8300 | Kubernetes Deployment | Horizontal |
| MCP Servers | 8001+ | Kubernetes Deployment | Por domínio | | MCP Servers | 8001+ | Kubernetes Deployment | Por domínio |
| Evaluator API | 9300 | Deployment/CronJob | Por carga batch | | Evaluator API | 9300 | Deployment/CronJob | Por carga batch |
| Frontend Demo | 5173 | Opcional | Não crítico | | Frontend Demo | 5173 | Opcional | Não crítico |

View File

@@ -172,7 +172,7 @@ Itens considerados:
```bash ```bash
curl -f http://agent-runtime:8000/health curl -f http://agent-runtime:8000/health
curl -f http://agent-gateway:9000/health curl -f http://agent-gateway:9000/health
curl -f http://mcp-gateway:9200/health curl -f http://mcp-gateway:8300/health
curl -f http://ai-gateway:9100/health curl -f http://ai-gateway:9100/health
``` ```

View File

@@ -7,7 +7,7 @@ Esta SPEC define o padrão para criação de agentes usando templates, configura
## Estrutura do Template ## Estrutura do Template
```text ```text
templates/backend/ templates/agent_template_backend/
├── app/ ├── app/
│ ├── main.py │ ├── main.py
│ ├── state.py │ ├── state.py

View File

@@ -49,7 +49,7 @@ Sem template:
| Template | Uso | | Template | Uso |
| --- | --- | | --- | --- |
| backend | Criação de agentes com runtime. | | backend | Criação de agentes com runtime. |
| backend_day_zero | Bootstrap acelerado com exemplos. | | agent_template_backend_day_zero | Bootstrap acelerado com exemplos. |
| mcp_server | Criação de MCP server. | | mcp_server | Criação de MCP server. |
| channel_adapter | Adapter de canal quando aplicável. | | channel_adapter | Adapter de canal quando aplicável. |
@@ -111,7 +111,7 @@ my_agent/
## Passo 1 — Copiar template ## Passo 1 — Copiar template
```bash ```bash
cp -R templates/backend financeiro_agent cp -R templates/agent_template_backend financeiro_agent
cd financeiro_agent cd financeiro_agent
``` ```

View File

@@ -77,6 +77,7 @@ LANGFUSE_HOST=http://localhost:3005
ENABLE_OTEL=false ENABLE_OTEL=false
OTEL_EXPORTER_OTLP_ENDPOINT= OTEL_EXPORTER_OTLP_ENDPOINT=
OTEL_SERVICE_NAME=ai-agent-template OTEL_SERVICE_NAME=ai-agent-template
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
############################################################################### ###############################################################################
# Analytics / Observer corporativo # Analytics / Observer corporativo
@@ -165,3 +166,15 @@ MEMORY_MAX_SUMMARY_CHARS=6000
MEMORY_SUMMARY_USE_LLM=true MEMORY_SUMMARY_USE_LLM=true
MEMORY_INJECT_RECENT_MESSAGES=true MEMORY_INJECT_RECENT_MESSAGES=true
MEMORY_INJECT_SUMMARY=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

View File

@@ -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")),
)

View File

@@ -77,6 +77,7 @@ LANGFUSE_HOST=http://localhost:3005
ENABLE_OTEL=false ENABLE_OTEL=false
OTEL_EXPORTER_OTLP_ENDPOINT= OTEL_EXPORTER_OTLP_ENDPOINT=
OTEL_SERVICE_NAME=ai-agent-template OTEL_SERVICE_NAME=ai-agent-template
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
############################################################################### ###############################################################################
# Analytics / Observer corporativo # Analytics / Observer corporativo
@@ -165,3 +166,15 @@ MEMORY_MAX_SUMMARY_CHARS=6000
MEMORY_SUMMARY_USE_LLM=true MEMORY_SUMMARY_USE_LLM=true
MEMORY_INJECT_RECENT_MESSAGES=true MEMORY_INJECT_RECENT_MESSAGES=true
MEMORY_INJECT_SUMMARY=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

View File

@@ -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")),
)