mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-11-06 03:14:12 +00:00
Compare commits
19 Commits
main
...
a472daa1e4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a472daa1e4 | ||
|
|
d93efd8972 | ||
| 727997aa41 | |||
| 2496e831a1 | |||
| b5d2a12953 | |||
| bdc44b15d6 | |||
| 05077a91bc | |||
| 19c4024693 | |||
| 22e8f56146 | |||
| 0a72afb94e | |||
| 57df723a24 | |||
| 6e43cc0158 | |||
| d271031198 | |||
| 34d33350ae | |||
| ad778f6889 | |||
| 762f6fb024 | |||
| f9c66b4792 | |||
| 560e79d21b | |||
| 23d32bbcfc |
64
Documentacao/IMPLEMENTACAO_WORKFLOWS_TRANSACIONAIS.md
Normal file
64
Documentacao/IMPLEMENTACAO_WORKFLOWS_TRANSACIONAIS.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Implementação — workflows transacionais determinísticos
|
||||
|
||||
## Entrega
|
||||
|
||||
Foi adicionada ao `agent_framework_oci` uma capacidade opcional para executar transações multi-etapas como workflows determinísticos compilados em LangGraph.
|
||||
|
||||
### Módulo novo
|
||||
|
||||
`libs/agent_framework/src/agent_framework/workflows/`
|
||||
|
||||
- `models.py`: contratos Pydantic e validação estrutural;
|
||||
- `repository.py`: resolução de versão ativa e leitura de YAML imutável;
|
||||
- `registry.py`: registro desacoplado de actions sync/async;
|
||||
- `runtime.py`: compilação, cache e execução do StateGraph;
|
||||
- `tool_executor.py`: integração com a política da tool;
|
||||
- `__init__.py`: API pública.
|
||||
|
||||
### Política expandida
|
||||
|
||||
`ToolPolicy` agora aceita:
|
||||
|
||||
```yaml
|
||||
execution:
|
||||
mode: direct_tool | workflow | agent
|
||||
workflow: nome_do_workflow
|
||||
version: active | 1
|
||||
```
|
||||
|
||||
O default permanece `direct_tool`, preservando compatibilidade.
|
||||
|
||||
### Configuração
|
||||
|
||||
Foram adicionados:
|
||||
|
||||
- `ENABLE_TRANSACTIONAL_WORKFLOWS=false`;
|
||||
- `WORKFLOWS_PATH=./workflows`.
|
||||
|
||||
### Template
|
||||
|
||||
Inclui um exemplo completo de devolução de pedido com:
|
||||
|
||||
- confirmação e campos obrigatórios pela política;
|
||||
- workflow YAML versionado;
|
||||
- actions de domínio no backend;
|
||||
- bifurcação determinística baseada no resultado da validação.
|
||||
|
||||
## Validação realizada
|
||||
|
||||
- `tests/unit/test_tool_policies.py`: 4 testes aprovados;
|
||||
- compilação Python de framework, template e novos testes: aprovada;
|
||||
- o teste funcional novo do LangGraph foi criado, mas não pôde ser executado neste container porque `langgraph` não está instalado no ambiente. A dependência já está declarada no `pyproject.toml` do framework.
|
||||
|
||||
## Escopo e segurança
|
||||
|
||||
Esta entrega cria o motor e a integração de política. Para operações críticas em produção ainda é necessário conectar:
|
||||
|
||||
- execution store persistente;
|
||||
- idempotência de negócio nas actions/APIs;
|
||||
- autorização por escopo;
|
||||
- telemetria IC/NOC específica de workflow;
|
||||
- compensação/Saga quando aplicável;
|
||||
- estratégia corporativa de timeout e retry.
|
||||
|
||||
Esses itens foram explicitamente documentados para evitar a falsa impressão de que retry por si só garante segurança transacional.
|
||||
987
Documentacao/Implementando_Basic_Auth.md
Normal file
987
Documentacao/Implementando_Basic_Auth.md
Normal file
@@ -0,0 +1,987 @@
|
||||
# Implementando Basic Auth
|
||||
|
||||
Para validar **todo o circuito com Basic Auth**, você precisa configurar três relações distintas:
|
||||
|
||||
```text
|
||||
Cliente de teste
|
||||
└─ Basic Auth A ─► Agent Gateway :8010
|
||||
└─ Basic Auth B ─► Agent Backend :8000
|
||||
└─ Basic Auth C ─► MCP Gateway :8300
|
||||
```
|
||||
|
||||
Há um detalhe importante: no pacote atual, a autenticação Basic já funciona para chamadas **de entrada**, mas os clientes internos ainda não enviam Basic Auth:
|
||||
|
||||
* `Agent Gateway → Agent Backend` não envia credencial;
|
||||
* `Agent Backend → MCP Gateway` envia apenas Bearer Token.
|
||||
|
||||
Portanto, para testar o circuito inteiro com Basic Auth, faça os dois pequenos ajustes de código descritos abaixo.
|
||||
|
||||
---
|
||||
|
||||
# 1. Preparar o ambiente
|
||||
|
||||
Considere que o ZIP foi extraído em:
|
||||
|
||||
```bash
|
||||
cd agent_framework_oci_authentication_v2_1
|
||||
```
|
||||
|
||||
Crie um único ambiente virtual para facilitar o teste:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
No Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
Instale o framework e as dependências dos três componentes:
|
||||
|
||||
```bash
|
||||
pip install -U pip
|
||||
|
||||
pip install -e ./libs/agent_framework
|
||||
|
||||
pip install \
|
||||
-r ./Tuning-Performance/Authentication/agent_template_backend_authentication/requirements.txt \
|
||||
-r ./apps/agent_gateway/requirements.txt \
|
||||
-r ./apps/mcp_gateway/requirements.txt
|
||||
```
|
||||
|
||||
Confirme a importação:
|
||||
|
||||
```bash
|
||||
python -c "from agent_framework.security import install_authentication; print('framework ok')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 2. Criar três pares de Client ID e Secret
|
||||
|
||||
Use credenciais diferentes para cada trecho. Para teste local:
|
||||
|
||||
| Fluxo | Client ID | Secret de teste |
|
||||
| ----------------------- | -------------------- | --------------------------- |
|
||||
| Cliente → Agent Gateway | `tia-test` | `TiaGateway-Test-2026!` |
|
||||
| Agent Gateway → Backend | `agent-gateway-test` | `GatewayBackend-Test-2026!` |
|
||||
| Backend → MCP Gateway | `agent-backend-test` | `BackendMcp-Test-2026!` |
|
||||
|
||||
Esses valores são apenas para ambiente local. Não os reutilize em produção.
|
||||
|
||||
## Gerar os hashes
|
||||
|
||||
O script está em:
|
||||
|
||||
```text
|
||||
Tuning-Performance/Authentication/
|
||||
agent_template_backend_authentication/
|
||||
scripts/generate_secret_hash.py
|
||||
```
|
||||
|
||||
Execute:
|
||||
|
||||
```bash
|
||||
python Tuning-Performance/Authentication/agent_template_backend_authentication/scripts/generate_secret_hash.py \
|
||||
--secret 'TiaGateway-Test-2026!'
|
||||
```
|
||||
|
||||
Depois:
|
||||
|
||||
```bash
|
||||
python Tuning-Performance/Authentication/agent_template_backend_authentication/scripts/generate_secret_hash.py \
|
||||
--secret 'GatewayBackend-Test-2026!'
|
||||
```
|
||||
|
||||
E:
|
||||
|
||||
```bash
|
||||
python Tuning-Performance/Authentication/agent_template_backend_authentication/scripts/generate_secret_hash.py \
|
||||
--secret 'BackendMcp-Test-2026!'
|
||||
```
|
||||
|
||||
Você receberá três valores semelhantes a:
|
||||
|
||||
```text
|
||||
pbkdf2_sha256:310000:<salt>:<digest>
|
||||
```
|
||||
|
||||
Guarde-os temporariamente:
|
||||
|
||||
```bash
|
||||
HASH_CLIENT_GATEWAY='pbkdf2_sha256:310000:...'
|
||||
HASH_GATEWAY_BACKEND='pbkdf2_sha256:310000:...'
|
||||
HASH_BACKEND_MCP='pbkdf2_sha256:310000:...'
|
||||
```
|
||||
|
||||
O hash muda a cada execução porque o salt é aleatório. Isso é esperado.
|
||||
|
||||
---
|
||||
|
||||
# 3. Configurar o Agent Gateway
|
||||
|
||||
Entre no diretório:
|
||||
|
||||
```bash
|
||||
cd apps/agent_gateway
|
||||
```
|
||||
|
||||
Copie o exemplo:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Adicione ao final do `.env`:
|
||||
|
||||
```env
|
||||
# Entrada: cliente/TIA -> Agent Gateway
|
||||
AGENT_GATEWAY_AUTH_ENABLED=true
|
||||
AGENT_GATEWAY_AUTH_MODE=basic
|
||||
AGENT_GATEWAY_AUTH_BASIC_CLIENT_ID=tia-test
|
||||
AGENT_GATEWAY_AUTH_BASIC_SECRET_HASH=COLE_AQUI_HASH_CLIENT_GATEWAY
|
||||
AGENT_GATEWAY_AUTH_BASIC_REALM=agent-gateway
|
||||
|
||||
AGENT_GATEWAY_AUTH_PUBLIC_PATHS=/health,/docs,/openapi.json,/redoc
|
||||
AGENT_GATEWAY_AUTH_PUBLIC_PREFIXES=
|
||||
|
||||
# Saída: Agent Gateway -> Agent Backend
|
||||
BACKEND_AUTH_MODE=basic
|
||||
BACKEND_AUTH_CLIENT_ID=agent-gateway-test
|
||||
BACKEND_AUTH_SECRET=GatewayBackend-Test-2026!
|
||||
```
|
||||
|
||||
Não coloque aspas no `.env`:
|
||||
|
||||
```env
|
||||
BACKEND_AUTH_SECRET=GatewayBackend-Test-2026!
|
||||
```
|
||||
|
||||
O arquivo de backends já aponta o backend Contas para:
|
||||
|
||||
```yaml
|
||||
contas:
|
||||
url: http://localhost:8000
|
||||
```
|
||||
|
||||
Arquivo:
|
||||
|
||||
```text
|
||||
apps/agent_gateway/config/backends.yaml
|
||||
```
|
||||
|
||||
Para este teste, mantenha apenas o backend `contas` ou force o backend no payload. Caso contrário, pedidos sobre ofertas e suporte podem ser roteados para portas em que nenhum backend está rodando.
|
||||
|
||||
---
|
||||
|
||||
# 4. Fazer o Agent Gateway enviar Basic Auth ao backend
|
||||
|
||||
Abra:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/global_supervisor/client.py
|
||||
```
|
||||
|
||||
Substitua a classe `BackendClient` por uma versão que aceite autenticação Basic.
|
||||
|
||||
No início do arquivo, adicione:
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
Altere o construtor:
|
||||
|
||||
```python
|
||||
class BackendClient:
|
||||
def __init__(
|
||||
self,
|
||||
timeout_seconds: float = 120.0,
|
||||
basic_client_id: str | None = None,
|
||||
basic_secret: str | None = None,
|
||||
):
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.basic_client_id = basic_client_id
|
||||
self.basic_secret = basic_secret
|
||||
|
||||
def _auth(self) -> httpx.BasicAuth | None:
|
||||
if self.basic_client_id and self.basic_secret:
|
||||
return httpx.BasicAuth(
|
||||
username=self.basic_client_id,
|
||||
password=self.basic_secret,
|
||||
)
|
||||
return None
|
||||
```
|
||||
|
||||
No método `call_message`, troque:
|
||||
|
||||
```python
|
||||
resp = await client.post(url, json=payload)
|
||||
```
|
||||
|
||||
por:
|
||||
|
||||
```python
|
||||
resp = await client.post(
|
||||
url,
|
||||
json=payload,
|
||||
auth=self._auth(),
|
||||
)
|
||||
```
|
||||
|
||||
No método `health`, você pode manter `/health` público. Caso queira enviar autenticação também, use:
|
||||
|
||||
```python
|
||||
resp = await client.get(url, auth=self._auth())
|
||||
```
|
||||
|
||||
Agora abra:
|
||||
|
||||
```text
|
||||
apps/agent_gateway/app/main.py
|
||||
```
|
||||
|
||||
Adicione:
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
Troque:
|
||||
|
||||
```python
|
||||
backend_client = BackendClient(
|
||||
timeout_seconds=settings.BACKEND_TIMEOUT_SECONDS
|
||||
)
|
||||
```
|
||||
|
||||
por:
|
||||
|
||||
```python
|
||||
backend_client = BackendClient(
|
||||
timeout_seconds=settings.BACKEND_TIMEOUT_SECONDS,
|
||||
basic_client_id=os.getenv("BACKEND_AUTH_CLIENT_ID"),
|
||||
basic_secret=os.getenv("BACKEND_AUTH_SECRET"),
|
||||
)
|
||||
```
|
||||
|
||||
Isso implementa:
|
||||
|
||||
```text
|
||||
Agent Gateway → Agent Backend
|
||||
Authorization: Basic base64(agent-gateway-test:GatewayBackend-Test-2026!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 5. Configurar o Agent Backend autenticado
|
||||
|
||||
Entre no diretório:
|
||||
|
||||
```bash
|
||||
cd Tuning-Performance/Authentication/agent_template_backend_authentication
|
||||
```
|
||||
|
||||
Copie o exemplo:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Ajuste a seção de autenticação:
|
||||
|
||||
```env
|
||||
# Entrada: Agent Gateway -> Agent Backend
|
||||
AGENT_AUTH_ENABLED=true
|
||||
AGENT_AUTH_MODE=basic
|
||||
AGENT_AUTH_BASIC_CLIENT_ID=agent-gateway-test
|
||||
AGENT_AUTH_BASIC_SECRET_HASH=COLE_AQUI_HASH_GATEWAY_BACKEND
|
||||
AGENT_AUTH_BASIC_REALM=agent-contas
|
||||
|
||||
AGENT_AUTH_PUBLIC_PATHS=/health,/docs,/openapi.json,/redoc
|
||||
AGENT_AUTH_PUBLIC_PREFIXES=
|
||||
```
|
||||
|
||||
Para usar o MCP Gateway:
|
||||
|
||||
```env
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
|
||||
# Saída: Agent Backend -> MCP Gateway
|
||||
MCP_GATEWAY_AUTH_MODE=basic
|
||||
MCP_GATEWAY_BASIC_CLIENT_ID=agent-backend-test
|
||||
MCP_GATEWAY_BASIC_SECRET=BackendMcp-Test-2026!
|
||||
```
|
||||
|
||||
Para evitar dependências externas durante o primeiro teste, configure também:
|
||||
|
||||
```env
|
||||
LLM_PROVIDER=mock
|
||||
ENABLE_LANGFUSE=false
|
||||
ENABLE_ANALYTICS=false
|
||||
|
||||
SESSION_REPOSITORY_PROVIDER=memory
|
||||
MEMORY_REPOSITORY_PROVIDER=memory
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=memory
|
||||
CACHE_PROVIDER=memory
|
||||
USAGE_REPOSITORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
Os nomes exatos de alguns providers podem depender do arquivo de configuração atual do framework. Caso o `.env.example` já contenha valores locais ou mock, preserve-os.
|
||||
|
||||
---
|
||||
|
||||
# 6. Fazer o Backend enviar Basic Auth ao MCP Gateway
|
||||
|
||||
Abra:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/gateways/mcp_gateway_client.py
|
||||
```
|
||||
|
||||
Substitua a implementação por:
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class MCPGatewayClient:
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
token: str | None = None,
|
||||
timeout_seconds: int = 60,
|
||||
auth_mode: str | None = None,
|
||||
basic_client_id: str | None = None,
|
||||
basic_secret: str | None = None,
|
||||
):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.token = token
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.auth_mode = (auth_mode or "").strip().lower()
|
||||
self.basic_client_id = basic_client_id
|
||||
self.basic_secret = basic_secret
|
||||
|
||||
def _headers(self) -> dict[str, str]:
|
||||
if (
|
||||
self.auth_mode == "basic"
|
||||
and self.basic_client_id
|
||||
and self.basic_secret
|
||||
):
|
||||
raw = f"{self.basic_client_id}:{self.basic_secret}".encode("utf-8")
|
||||
encoded = base64.b64encode(raw).decode("ascii")
|
||||
return {"Authorization": f"Basic {encoded}"}
|
||||
|
||||
if self.token:
|
||||
return {"Authorization": f"Bearer {self.token}"}
|
||||
|
||||
return {}
|
||||
|
||||
async def list_tools(self) -> dict[str, Any]:
|
||||
async with httpx.AsyncClient(
|
||||
timeout=self.timeout_seconds
|
||||
) as client:
|
||||
response = await client.get(
|
||||
f"{self.base_url}/v1/tools",
|
||||
headers=self._headers(),
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def invoke_tool(
|
||||
self,
|
||||
*,
|
||||
tenant_id: str,
|
||||
agent_id: str,
|
||||
channel: str | None,
|
||||
tool_name: str,
|
||||
arguments: dict[str, Any] | None = None,
|
||||
business_context: dict[str, Any] | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
payload = {
|
||||
"tenant_id": tenant_id,
|
||||
"agent_id": agent_id,
|
||||
"channel": channel,
|
||||
"tool_name": tool_name,
|
||||
"arguments": arguments or {},
|
||||
"business_context": business_context or {},
|
||||
"metadata": metadata or {},
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
timeout=self.timeout_seconds
|
||||
) as client:
|
||||
response = await client.post(
|
||||
f"{self.base_url}/v1/tools/{tool_name}/invoke",
|
||||
json=payload,
|
||||
headers=self._headers(),
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
```
|
||||
|
||||
Agora abra:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/mcp/tool_router.py
|
||||
```
|
||||
|
||||
Localize:
|
||||
|
||||
```python
|
||||
MCPGatewayClient(
|
||||
base_url=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_URL",
|
||||
"http://localhost:8300",
|
||||
),
|
||||
token=getattr(settings, "MCP_GATEWAY_TOKEN", None),
|
||||
timeout_seconds=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_TIMEOUT_SECONDS",
|
||||
settings.MCP_TOOL_TIMEOUT_SECONDS,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Altere para:
|
||||
|
||||
```python
|
||||
MCPGatewayClient(
|
||||
base_url=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_URL",
|
||||
"http://localhost:8300",
|
||||
),
|
||||
token=getattr(settings, "MCP_GATEWAY_TOKEN", None),
|
||||
timeout_seconds=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_TIMEOUT_SECONDS",
|
||||
settings.MCP_TOOL_TIMEOUT_SECONDS,
|
||||
),
|
||||
auth_mode=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_AUTH_MODE",
|
||||
None,
|
||||
),
|
||||
basic_client_id=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_BASIC_CLIENT_ID",
|
||||
None,
|
||||
),
|
||||
basic_secret=getattr(
|
||||
settings,
|
||||
"MCP_GATEWAY_BASIC_SECRET",
|
||||
None,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Adicione estes campos em:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/config/settings.py
|
||||
```
|
||||
|
||||
Próximo das configurações existentes de MCP Gateway:
|
||||
|
||||
```python
|
||||
MCP_GATEWAY_AUTH_MODE: str | None = None
|
||||
MCP_GATEWAY_BASIC_CLIENT_ID: str | None = None
|
||||
MCP_GATEWAY_BASIC_SECRET: str | None = None
|
||||
```
|
||||
|
||||
Há também uma factory local em:
|
||||
|
||||
```text
|
||||
Tuning-Performance/Authentication/
|
||||
agent_template_backend_authentication/
|
||||
app/mcp_gateway_client_factory.py
|
||||
```
|
||||
|
||||
Ajuste para:
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from agent_framework.gateways import MCPGatewayClient
|
||||
|
||||
|
||||
def build_mcp_gateway_client() -> MCPGatewayClient | None:
|
||||
if os.getenv("MCP_GATEWAY_ENABLED", "true").lower() != "true":
|
||||
return None
|
||||
|
||||
return MCPGatewayClient(
|
||||
base_url=os.getenv(
|
||||
"MCP_GATEWAY_URL",
|
||||
"http://localhost:8300",
|
||||
),
|
||||
token=os.getenv("MCP_GATEWAY_TOKEN") or None,
|
||||
timeout_seconds=int(
|
||||
os.getenv("MCP_GATEWAY_TIMEOUT_SECONDS", "60")
|
||||
),
|
||||
auth_mode=os.getenv("MCP_GATEWAY_AUTH_MODE"),
|
||||
basic_client_id=os.getenv(
|
||||
"MCP_GATEWAY_BASIC_CLIENT_ID"
|
||||
),
|
||||
basic_secret=os.getenv(
|
||||
"MCP_GATEWAY_BASIC_SECRET"
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 7. Configurar o MCP Gateway
|
||||
|
||||
Entre no diretório:
|
||||
|
||||
```bash
|
||||
cd apps/mcp_gateway
|
||||
```
|
||||
|
||||
Crie `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Adicione:
|
||||
|
||||
```env
|
||||
# Entrada: Agent Backend -> MCP Gateway
|
||||
MCP_GATEWAY_AUTH_ENABLED=true
|
||||
MCP_GATEWAY_AUTH_MODE=basic
|
||||
MCP_GATEWAY_AUTH_BASIC_CLIENT_ID=agent-backend-test
|
||||
MCP_GATEWAY_AUTH_BASIC_SECRET_HASH=COLE_AQUI_HASH_BACKEND_MCP
|
||||
MCP_GATEWAY_AUTH_BASIC_REALM=mcp-gateway
|
||||
|
||||
MCP_GATEWAY_AUTH_PUBLIC_PATHS=/health,/ready,/docs,/openapi.json,/redoc
|
||||
MCP_GATEWAY_AUTH_PUBLIC_PREFIXES=
|
||||
|
||||
MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
|
||||
```
|
||||
|
||||
## Desabilitar o mecanismo Bearer legado
|
||||
|
||||
O MCP Gateway ainda possui um segundo mecanismo antigo, configurado dentro de:
|
||||
|
||||
```text
|
||||
apps/mcp_gateway/config/mcp_gateway.yaml
|
||||
```
|
||||
|
||||
Localize a seção:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
Altere para:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
Isso é necessário porque o novo middleware já faz a autenticação Basic. Caso o `auth_check()` legado continue habilitado, a requisição passará pelo Basic e depois será rejeitada por não possuir Bearer Token.
|
||||
|
||||
---
|
||||
|
||||
# 8. Subir os componentes
|
||||
|
||||
Use quatro terminais.
|
||||
|
||||
## Terminal 1 — MCP Servers
|
||||
|
||||
O MCP Gateway precisa ter pelo menos um servidor MCP disponível para demonstrar uma chamada real.
|
||||
|
||||
Na raiz do projeto:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Suba o servidor telecom:
|
||||
|
||||
```bash
|
||||
uvicorn mcp.servers.telecom_mcp_server.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8100 \
|
||||
--reload
|
||||
```
|
||||
|
||||
Em outro terminal, caso queira também o retail:
|
||||
|
||||
```bash
|
||||
uvicorn mcp.servers.retail_mcp_server.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8200 \
|
||||
--reload
|
||||
```
|
||||
|
||||
Confira as URLs configuradas em:
|
||||
|
||||
```text
|
||||
apps/mcp_gateway/config/mcp_gateway.yaml
|
||||
```
|
||||
|
||||
Para execução local, devem apontar para:
|
||||
|
||||
```yaml
|
||||
url: http://localhost:8100
|
||||
```
|
||||
|
||||
e:
|
||||
|
||||
```yaml
|
||||
url: http://localhost:8200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Terminal 2 — MCP Gateway
|
||||
|
||||
```bash
|
||||
cd apps/mcp_gateway
|
||||
source ../../.venv/bin/activate
|
||||
```
|
||||
|
||||
Suba usando `--env-file`. Isso é importante porque o middleware lê variáveis com `os.getenv()`:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8300 \
|
||||
--reload \
|
||||
--env-file .env
|
||||
```
|
||||
|
||||
Teste a saúde pública:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8300/health
|
||||
```
|
||||
|
||||
Teste um endpoint protegido sem credencial:
|
||||
|
||||
```bash
|
||||
curl -i http://localhost:8300/v1/tools
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 401 Unauthorized
|
||||
```
|
||||
|
||||
Teste com Basic Auth:
|
||||
|
||||
```bash
|
||||
curl -i \
|
||||
-u 'agent-backend-test:BackendMcp-Test-2026!' \
|
||||
http://localhost:8300/v1/tools
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Terminal 3 — Agent Backend
|
||||
|
||||
```bash
|
||||
cd Tuning-Performance/Authentication/agent_template_backend_authentication
|
||||
source ../../../.venv/bin/activate
|
||||
```
|
||||
|
||||
Suba:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8000 \
|
||||
--reload \
|
||||
--env-file .env
|
||||
```
|
||||
|
||||
Teste saúde:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
```
|
||||
|
||||
Teste endpoint protegido sem credencial:
|
||||
|
||||
```bash
|
||||
curl -i http://localhost:8000/agents
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 401 Unauthorized
|
||||
```
|
||||
|
||||
Teste com a credencial usada pelo Agent Gateway:
|
||||
|
||||
```bash
|
||||
curl -i \
|
||||
-u 'agent-gateway-test:GatewayBackend-Test-2026!' \
|
||||
http://localhost:8000/agents
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
Teste mensagem diretamente:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-u 'agent-gateway-test:GatewayBackend-Test-2026!' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"agent_id": "telecom_contas",
|
||||
"tenant_id": "default",
|
||||
"payload": {
|
||||
"text": "Quero consultar minha fatura",
|
||||
"session_id": "teste-backend-001",
|
||||
"user_id": "user-001",
|
||||
"customer_id": "12345",
|
||||
"message_id": "msg-001"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Terminal 4 — Agent Gateway
|
||||
|
||||
```bash
|
||||
cd apps/agent_gateway
|
||||
source ../../.venv/bin/activate
|
||||
```
|
||||
|
||||
Suba:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8010 \
|
||||
--reload \
|
||||
--env-file .env
|
||||
```
|
||||
|
||||
Teste saúde:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8010/health
|
||||
```
|
||||
|
||||
Teste endpoint protegido sem credencial:
|
||||
|
||||
```bash
|
||||
curl -i http://localhost:8010/backends
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 401 Unauthorized
|
||||
```
|
||||
|
||||
Teste com a credencial externa:
|
||||
|
||||
```bash
|
||||
curl -i \
|
||||
-u 'tia-test:TiaGateway-Test-2026!' \
|
||||
http://localhost:8010/backends
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 9. Validar o circuito completo
|
||||
|
||||
Force o backend `contas` para evitar que o roteador selecione um backend não iniciado:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8010/gateway/message \
|
||||
-u 'tia-test:TiaGateway-Test-2026!' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"backend_id": "contas",
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"session_id": "circuito-basic-001",
|
||||
"payload": {
|
||||
"text": "Quero consultar minha fatura",
|
||||
"session_id": "circuito-basic-001",
|
||||
"user_id": "user-001",
|
||||
"customer_id": "12345",
|
||||
"message_id": "msg-circuito-001"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
O circuito esperado é:
|
||||
|
||||
```text
|
||||
curl
|
||||
│ Basic tia-test
|
||||
▼
|
||||
Agent Gateway :8010
|
||||
│ Basic agent-gateway-test
|
||||
▼
|
||||
Agent Backend :8000
|
||||
│ Basic agent-backend-test
|
||||
▼
|
||||
MCP Gateway :8300
|
||||
▼
|
||||
MCP Server :8100 ou :8200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 10. Como comprovar cada autenticação
|
||||
|
||||
Faça testes negativos em cada trecho.
|
||||
|
||||
## Secret externo incorreto
|
||||
|
||||
```bash
|
||||
curl -i \
|
||||
-u 'tia-test:senha-errada' \
|
||||
http://localhost:8010/backends
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```text
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
## Secret do gateway para backend incorreto
|
||||
|
||||
Altere temporariamente no `apps/agent_gateway/.env`:
|
||||
|
||||
```env
|
||||
BACKEND_AUTH_SECRET=senha-errada
|
||||
```
|
||||
|
||||
Reinicie o Agent Gateway e envie uma mensagem.
|
||||
|
||||
O gateway deverá retornar erro de backend, normalmente:
|
||||
|
||||
```text
|
||||
502 Bad Gateway
|
||||
```
|
||||
|
||||
O erro interno será originado por um:
|
||||
|
||||
```text
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
do Agent Backend.
|
||||
|
||||
## Secret do backend para MCP incorreto
|
||||
|
||||
Altere temporariamente:
|
||||
|
||||
```env
|
||||
MCP_GATEWAY_BASIC_SECRET=senha-errada
|
||||
```
|
||||
|
||||
Reinicie o backend e execute uma frase que acione uma ferramenta MCP.
|
||||
|
||||
O backend deverá registrar falha na chamada ao MCP Gateway com:
|
||||
|
||||
```text
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 11. Verificação rápida de portas
|
||||
|
||||
No Linux ou WSL:
|
||||
|
||||
```bash
|
||||
ss -lntp | grep -E ':8000|:8010|:8100|:8200|:8300'
|
||||
```
|
||||
|
||||
No Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
Get-NetTCPConnection -State Listen |
|
||||
Where-Object LocalPort -in 8000,8010,8100,8200,8300 |
|
||||
Sort-Object LocalPort
|
||||
```
|
||||
|
||||
Você deverá ver:
|
||||
|
||||
```text
|
||||
8000 Agent Backend
|
||||
8010 Agent Gateway
|
||||
8100 Telecom MCP Server
|
||||
8200 Retail MCP Server
|
||||
8300 MCP Gateway
|
||||
```
|
||||
|
||||
## Observação importante
|
||||
|
||||
O segredo original precisa existir no componente cliente:
|
||||
|
||||
```text
|
||||
TIA ou curl:
|
||||
TiaGateway-Test-2026!
|
||||
|
||||
Agent Gateway:
|
||||
GatewayBackend-Test-2026!
|
||||
|
||||
Agent Backend:
|
||||
BackendMcp-Test-2026!
|
||||
```
|
||||
|
||||
Os componentes servidores armazenam apenas os hashes:
|
||||
|
||||
```text
|
||||
Agent Gateway:
|
||||
hash de TiaGateway-Test-2026!
|
||||
|
||||
Agent Backend:
|
||||
hash de GatewayBackend-Test-2026!
|
||||
|
||||
MCP Gateway:
|
||||
hash de BackendMcp-Test-2026!
|
||||
```
|
||||
|
||||
Em produção, os segredos originais e hashes devem vir de Vault ou Kubernetes Secret, não de arquivos `.env`.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Release Notes — Generic Deterministic Intent Shift v15
|
||||
|
||||
## Problema corrigido
|
||||
|
||||
A Route Stickiness podia preservar a intent anterior quando a nova mensagem correspondia a uma intent configurada no `routing.yaml`, mas a frase do usuário omitia conectores curtos presentes na keyword configurada.
|
||||
|
||||
Exemplo real de configuração:
|
||||
|
||||
- keyword: `qual é o meu plano`
|
||||
- mensagem: `qual o meu plano`
|
||||
|
||||
A classificação determinística não reconhecia a nova intent e a continuity acabava mantendo a intent anterior.
|
||||
|
||||
## Correção
|
||||
|
||||
O `EnterpriseRouter` continua usando, nesta ordem:
|
||||
|
||||
1. match exato;
|
||||
2. sequência completa de tokens com palavras inseridas (`ordered_tokens`);
|
||||
3. sequência de tokens informativos tolerando a omissão de conectores curtos presentes na keyword (`ordered_content_tokens`).
|
||||
|
||||
A terceira estratégia ignora, somente no lado da keyword, tokens de até dois caracteres e exige pelo menos dois tokens informativos. Não há nomes de intents, agentes, domínios ou verbos de negócio hardcoded.
|
||||
|
||||
Assim, a solução é dirigida integralmente pelas intents carregadas do `routing.yaml` da aplicação.
|
||||
|
||||
## Precedência sobre Route Stickiness
|
||||
|
||||
Quando o candidato determinístico encontrado é diferente da intent ativa, ele preempta a stickiness e retorna:
|
||||
|
||||
- `route_stickiness_preempted: true`
|
||||
- `previous_agent`
|
||||
- `previous_intent`
|
||||
- `keyword_match_strategy`
|
||||
|
||||
A continuity LLM não é chamada nesse caminho.
|
||||
|
||||
## Casos cobertos
|
||||
|
||||
### Mesmo agente, nova intent
|
||||
|
||||
`retail_order_tracking` -> `quero cancelar meu pedido` -> `retail_order_cancel`
|
||||
|
||||
### Mesmo agente, tools diferentes
|
||||
|
||||
`contas_invoice_query` -> `qual o meu plano` -> `contas_plan_information`
|
||||
|
||||
Mesmo que ambas as intents usem `faturas_agent`, as tools mudam de `consultar_faturas` para `consultar_plano`.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Route Stickiness — deterministic intent shift (v14)
|
||||
|
||||
## Problema corrigido
|
||||
|
||||
Uma keyword multi-token como `cancelar pedido` não era reconhecida em frases como `quero cancelar meu pedido`. O match legado usava substring literal; assim, a keyword genérica `pedido` podia manter `retail_order_tracking` e a continuidade reutilizava a intent anterior.
|
||||
|
||||
## Correção
|
||||
|
||||
O `EnterpriseRouter` agora possui um segundo estágio determinístico para keywords multi-token: ordered-token matching com até três tokens intermediários. Não há chamada adicional de LLM.
|
||||
|
||||
Exemplos reconhecidos pela keyword configurada `cancelar pedido`:
|
||||
|
||||
- `quero cancelar meu pedido`
|
||||
- `quero cancelar o meu pedido`
|
||||
- `pode cancelar esse pedido`
|
||||
- `gostaria de cancelar meu pedido`
|
||||
|
||||
Quando esse match identifica uma intent diferente da ativa, ele preempta a route stickiness antes do LLM de continuidade.
|
||||
|
||||
Metadados de auditoria esperados:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "keyword",
|
||||
"intent": "retail_order_cancel",
|
||||
"metadata": {
|
||||
"matched_keyword": "cancelar pedido",
|
||||
"keyword_match_strategy": "ordered_tokens",
|
||||
"route_stickiness_preempted": true,
|
||||
"previous_intent": "retail_order_tracking"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custo de LLM
|
||||
|
||||
Para mudança explícita reconhecida deterministicamente, o classificador LLM de continuity não é chamado. Para mensagens sem sinal explícito, a Route Stickiness continua com o comportamento configurado.
|
||||
|
||||
## Regressão
|
||||
|
||||
Testes cobrem mudança `retail_order_tracking -> retail_order_cancel` no mesmo `orders_agent`, inclusive com palavras intermediárias. A suíte relacionada passou com 18 testes.
|
||||
30
FIX_TRANSACTION_INTENT_LOOP.md
Normal file
30
FIX_TRANSACTION_INTENT_LOOP.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Correção: escape de estado transacional / mudança de intenção
|
||||
|
||||
Correção aplicada em 2026-08-20 para impedir que uma sessão fique presa em `COLLECTING_PARAMETERS` ou `AWAITING_CONFIRMATION` quando o usuário muda explicitamente de assunto.
|
||||
|
||||
## Comportamento corrigido
|
||||
|
||||
Antes:
|
||||
|
||||
1. uma transação entrava em `COLLECTING_PARAMETERS`;
|
||||
2. `next_state` forçava o mesmo agente via `state_policies`;
|
||||
3. toda mensagem seguinte era tratada como tentativa de preencher o parâmetro faltante;
|
||||
4. uma nova intenção como `quais sao meus servicos` permanecia presa no fluxo anterior.
|
||||
|
||||
Agora:
|
||||
|
||||
- o `EnterpriseRouter` verifica mudança explícita de intenção antes de aplicar o lock de estado;
|
||||
- keyword explícita tem prioridade;
|
||||
- quando necessário, o LLM router pode detectar mudança com confiança >= `router.confidence_threshold`;
|
||||
- a decisão recebe `metadata.transaction_interruption=intent_shift`;
|
||||
- o runtime encerra a transação pendente como `CANCELLED`, limpa `next_state`, parâmetros e latches, e prossegue com a nova intent;
|
||||
- cancelamentos explícitos como `cancele essa operação anterior` funcionam também durante `COLLECTING_PARAMETERS`.
|
||||
|
||||
## Testes adicionados
|
||||
|
||||
- mudança de intent durante `COLLECTING_PARAMETERS`;
|
||||
- resposta curta/baixa confiança permanece na transação;
|
||||
- cancelamento explícito durante coleta de parâmetros;
|
||||
- limpeza do estado transacional antes de executar a nova intent.
|
||||
|
||||
Testes focados: 19 passed.
|
||||
102
FIX_TRANSACTION_PARAMETER_PRECEDENCE.md
Normal file
102
FIX_TRANSACTION_PARAMETER_PRECEDENCE.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Precedência transacional + extração LLM de parâmetros
|
||||
|
||||
Esta correção remove a extração textual hardcoded de parâmetros transacionais e faz a coleta de `policy.requires` por um extrator LLM genérico.
|
||||
|
||||
## Regra de precedência
|
||||
|
||||
Enquanto existir uma transação ativa, o framework trata o turno nesta ordem:
|
||||
|
||||
```text
|
||||
ACTIVE_TRANSACTION
|
||||
|
|
||||
+-- COLLECTING_PARAMETERS
|
||||
| |
|
||||
| +-- LLM tenta extrair SOMENTE os parâmetros ainda pendentes
|
||||
| |
|
||||
| +-- extraiu >= 1 ?
|
||||
| |
|
||||
| +-- SIM -> continua a transação; NÃO avalia intent_shift
|
||||
| |
|
||||
| +-- NÃO -> libera EnterpriseRouter para avaliar intent_shift
|
||||
|
|
||||
+-- AWAITING_CONFIRMATION
|
||||
|
|
||||
+-- reconhece confirmação/rejeição explícita
|
||||
|
|
||||
+-- reconheceu ?
|
||||
|
|
||||
+-- SIM -> continua/cancela a transação; NÃO avalia intent_shift
|
||||
|
|
||||
+-- NÃO -> libera EnterpriseRouter para avaliar intent_shift
|
||||
```
|
||||
|
||||
## TransactionParameterExtractor
|
||||
|
||||
Novo componente:
|
||||
|
||||
`libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py`
|
||||
|
||||
A extração textual dos parâmetros de negócio é feita exclusivamente por LLM. O componente recebe:
|
||||
|
||||
- nome da tool/transação ativa;
|
||||
- parâmetros atualmente pendentes;
|
||||
- argumentos já conhecidos;
|
||||
- schema/tipos declarados em `tools.yaml` quando disponíveis;
|
||||
- descrição da tool;
|
||||
- mensagem atual do usuário.
|
||||
|
||||
Ele não conhece nomes de domínio como `order_id`, `reason`, `subject`, `valor`, TIM ou retail. Não há regex de entidades de negócio.
|
||||
|
||||
A LLM pode interpretar, por exemplo:
|
||||
|
||||
- `PED-1001` quando só há um parâmetro compatível pendente;
|
||||
- `o pedido é PED-1001`;
|
||||
- `PED-1001, desisti da compra` preenchendo dois parâmetros no mesmo turno;
|
||||
- respostas com o nome do parâmetro seguido do valor;
|
||||
- respostas apenas com o valor, quando semanticamente inequívocas.
|
||||
|
||||
Em caso de dúvida, o prompt manda retornar `null`. Uma nova solicitação não deve ser transformada em valor de parâmetro.
|
||||
|
||||
## Separação de responsabilidades
|
||||
|
||||
`tool_policies.yaml` continua sendo a fonte de verdade para `requires`.
|
||||
|
||||
`tools.yaml` pode fornecer tipos via `args_schema` e descrição da tool para melhorar a interpretação sem introduzir código específico de domínio.
|
||||
|
||||
`mcp_parameter_mapping.yaml` continua responsável pelos parâmetros auxiliares/contrato MCP. As strategies do mapper são explicitamente excluídas dos campos presentes em `policy.requires`, para não misturar extração MCP com coleta transacional.
|
||||
|
||||
O `EnterpriseRouter` usa o mesmo extrator LLM apenas como *probe* de precedência. Se pelo menos um parâmetro pendente for encontrado, o turno permanece no estado transacional. Os valores extraídos são colocados no metadata da decisão e reutilizados pelo runtime, evitando uma segunda chamada LLM no mesmo turno.
|
||||
|
||||
## Profile LLM
|
||||
|
||||
Foi adicionado aos templates:
|
||||
|
||||
```yaml
|
||||
transaction_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
timeout_seconds: 8
|
||||
```
|
||||
|
||||
Generation/component:
|
||||
|
||||
- `llm.transaction_parameter_extraction`
|
||||
- `transaction_parameter_extraction`
|
||||
|
||||
## Limpeza de estado
|
||||
|
||||
Em `intent_shift`, `transaction_pre_validation` da transação abandonada é removido para não contaminar a nova transação. O resultado de pre-validation continua preservado enquanto pertence à própria transação para auditoria.
|
||||
|
||||
## Testes adicionados
|
||||
|
||||
`tests/test_transaction_parameter_llm_precedence.py`
|
||||
|
||||
Cobertura:
|
||||
|
||||
1. dois parâmetros extraídos no mesmo turno;
|
||||
2. um parâmetro preenchido ganha precedência sobre keyword que indicaria outra intent;
|
||||
3. nenhum parâmetro encontrado libera `intent_shift`;
|
||||
4. ausência do antigo `_extract_action_arguments()` hardcoded;
|
||||
5. confirmação `sim` ganha precedência sobre intent shift.
|
||||
@@ -3,5 +3,8 @@ from __future__ import annotations
|
||||
# Compatibilidade local do template/backend.
|
||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||
from app.presentation import register_tool_renderers
|
||||
|
||||
register_tool_renderers()
|
||||
|
||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .tool_renderers import register_tool_renderers
|
||||
|
||||
__all__ = ["register_tool_renderers"]
|
||||
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.presentation import register_tool_response_renderer
|
||||
|
||||
|
||||
def _money_brl(value: Any) -> str:
|
||||
try:
|
||||
return f"{float(value):.2f}".replace(".", ",")
|
||||
except (TypeError, ValueError):
|
||||
return str(value)
|
||||
|
||||
|
||||
def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
return f"[{agent_label}] Fatura consultada: {result}."
|
||||
|
||||
|
||||
def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
plano = result.get("plano")
|
||||
if plano is None:
|
||||
return None
|
||||
parts = [f"[{agent_label}] Seu plano é {plano}"]
|
||||
internet_gb = result.get("internet_gb")
|
||||
status = result.get("status")
|
||||
if internet_gb is not None:
|
||||
parts.append(f"com {internet_gb} GB")
|
||||
if status is not None:
|
||||
parts.append(f"status {status}")
|
||||
return ", ".join(parts) + "."
|
||||
|
||||
|
||||
def render_retail_order(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
status = result.get("status")
|
||||
if order_id is None or status is None:
|
||||
return None
|
||||
lines = [f"[{agent_label}] Pedido {order_id}: status {status}."]
|
||||
total = result.get("valor_total")
|
||||
if total is not None:
|
||||
lines.append(f"Valor total: R$ {_money_brl(total)}.")
|
||||
items = result.get("itens") or []
|
||||
rendered_items: list[str] = []
|
||||
if isinstance(items, list):
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
value = item.get("descricao") or item.get("nome") or item.get("sku")
|
||||
else:
|
||||
value = item
|
||||
if value not in (None, ""):
|
||||
rendered_items.append(str(value))
|
||||
if rendered_items:
|
||||
lines.append("Itens: " + "; ".join(rendered_items) + ".")
|
||||
return " ".join(lines)
|
||||
|
||||
|
||||
def render_retail_delivery(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
transportadora = result.get("transportadora")
|
||||
codigo = result.get("codigo_rastreio")
|
||||
previsao = result.get("previsao_entrega")
|
||||
if any(v is None for v in (order_id, transportadora, codigo, previsao)):
|
||||
return None
|
||||
return (
|
||||
f"[{agent_label}] Entrega do pedido {order_id}: transportadora {transportadora}, "
|
||||
f"rastreio {codigo}, previsão {previsao}."
|
||||
)
|
||||
|
||||
|
||||
def register_tool_renderers() -> None:
|
||||
register_tool_response_renderer("telecom.invoice", render_telecom_invoice)
|
||||
register_tool_response_renderer("telecom.plan", render_telecom_plan)
|
||||
register_tool_response_renderer("retail.order", render_retail_order)
|
||||
register_tool_response_renderer("retail.delivery", render_retail_delivery)
|
||||
@@ -26,6 +26,8 @@ class AgentState(TypedDict, total=False):
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
active_transaction: dict[str, Any]
|
||||
last_transaction: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
|
||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
cancelar_pedido:
|
||||
map:
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente pelo usuário. Retorne null quando não houver identificador de pedido na mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
|
||||
solicitar_troca:
|
||||
map:
|
||||
session_key: session_id
|
||||
|
||||
@@ -79,6 +79,25 @@ intents:
|
||||
- Quero saber sobre meu pacote de internet.
|
||||
- Tenho roaming internacional?
|
||||
|
||||
|
||||
- name: retail_order_cancel
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
description: Cancelamento explícito de pedido ou compra.
|
||||
priority: 20
|
||||
mcp_tools:
|
||||
- consultar_pedido
|
||||
- cancelar_pedido
|
||||
keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar a compra
|
||||
- cancelar compra
|
||||
examples:
|
||||
- Quero cancelar meu pedido.
|
||||
- Cancele o pedido.
|
||||
- Quero cancelar a compra.
|
||||
|
||||
- name: retail_order_tracking
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
|
||||
@@ -7,6 +7,12 @@ defaults:
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_pedido:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id]
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
@@ -10,6 +10,9 @@ tools:
|
||||
- fatura
|
||||
- conta
|
||||
- boleto
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.invoice
|
||||
consultar_pagamentos:
|
||||
description: Consulta histórico de pagamentos do cliente.
|
||||
mcp_server: telecom
|
||||
@@ -28,6 +31,9 @@ tools:
|
||||
asset_id: string
|
||||
selection_keywords:
|
||||
- plano
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.plan
|
||||
listar_servicos:
|
||||
description: Lista serviços ativos e adicionais VAS.
|
||||
mcp_server: telecom
|
||||
@@ -49,6 +55,9 @@ tools:
|
||||
- consultar pedido
|
||||
- status do pedido
|
||||
- pedido
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.order
|
||||
consultar_entrega:
|
||||
description: Consulta entrega e rastreamento do pedido.
|
||||
mcp_server: retail
|
||||
@@ -61,6 +70,26 @@ tools:
|
||||
- rastreamento
|
||||
- transportadora
|
||||
- previsão
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.delivery
|
||||
cancelar_pedido:
|
||||
description: Simula o cancelamento de um pedido de varejo.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires:
|
||||
- order_id
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
selection_keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar compra
|
||||
- cancelar a compra
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
|
||||
@@ -78,3 +78,10 @@ profiles:
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
|
||||
transaction_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
timeout_seconds: 8
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
###############################################################################
|
||||
# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA
|
||||
# Este arquivo é lido por Pydantic Settings no framework e no backend template.
|
||||
###############################################################################
|
||||
|
||||
APP_NAME=ai-agent-template
|
||||
APP_ENV=local
|
||||
LOG_LEVEL=INFO
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
|
||||
###############################################################################
|
||||
# LLM - OCI Generative AI como provider principal
|
||||
###############################################################################
|
||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
||||
LLM_PROVIDER=oci_openai
|
||||
LLM_TEMPERATURE=0.2
|
||||
LLM_MAX_TOKENS=2048
|
||||
LLM_TIMEOUT_SECONDS=120
|
||||
|
||||
# OCI OpenAI-compatible endpoint
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6
|
||||
OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
# OCI_AUTH_MODE=config_file|instance_principal|resource_principal
|
||||
OCI_AUTH_MODE=config_file
|
||||
# OCI SDK / signer / profiles
|
||||
OCI_CONFIG_FILE=~/.oci/config
|
||||
OCI_PROFILE=DEFAULT
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
OCI_REGION=us-chicago-1
|
||||
|
||||
###############################################################################
|
||||
# Persistência
|
||||
###############################################################################
|
||||
# Opções: memory, autonomous, mongodb
|
||||
SESSION_REPOSITORY_PROVIDER=autonomous
|
||||
MEMORY_REPOSITORY_PROVIDER=autonomous
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
|
||||
|
||||
# Autonomous Database
|
||||
ADB_USER=admin
|
||||
ADB_PASSWORD=fjhsdf04954hf
|
||||
ADB_DSN=oradb23aidev_high
|
||||
ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev
|
||||
ADB_WALLET_PASSWORD=fjhsdf04954hf
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
|
||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
||||
MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
|
||||
MONGODB_DATABASE=agent_platform
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
ENABLE_REDIS_CACHE=false
|
||||
|
||||
###############################################################################
|
||||
# RAG / Vector / Graph
|
||||
###############################################################################
|
||||
VECTOR_STORE_PROVIDER=memory
|
||||
GRAPH_STORE_PROVIDER=memory
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=mock
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
|
||||
###############################################################################
|
||||
# Observabilidade
|
||||
###############################################################################
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_TRACE_MODE=verbose # Opcional: verbose, compact
|
||||
LANGFUSE_ROOT_SPAN_NAME=agent.gateway_message
|
||||
LANGFUSE_LEGACY_IO_FALLBACK=true
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-bd9b0c7e-2b8b-4e5b-a382-284a9b4413b3
|
||||
LANGFUSE_SECRET_KEY=sk-lf-5f5cc18d-0bb5-424e-b5d0-cb3664d58c20
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
|
||||
###############################################################################
|
||||
# Analytics / Observer corporativo
|
||||
###############################################################################
|
||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
||||
ENABLE_ANALYTICS=false
|
||||
# Providers aceitos: oci_streaming,pubsub,noop
|
||||
ANALYTICS_PROVIDERS=pubsub
|
||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
||||
AGENT_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TOPIC_PATH=
|
||||
GCP_PROJECT_ID=
|
||||
GCP_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TIMEOUT_SECONDS=30
|
||||
# Credencial GCP segue padrão Google:
|
||||
# GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-service-account.json
|
||||
|
||||
###############################################################################
|
||||
# OCI Streaming
|
||||
###############################################################################
|
||||
ENABLE_OCI_STREAMING=false
|
||||
OCI_STREAM_ENDPOINT=
|
||||
OCI_STREAM_OCID=
|
||||
OCI_STREAM_PARTITION_KEY=agent-events
|
||||
|
||||
###############################################################################
|
||||
# Guardrails, Judges, Supervisor
|
||||
###############################################################################
|
||||
ENABLE_INPUT_GUARDRAILS=true
|
||||
ENABLE_OUTPUT_GUARDRAILS=true
|
||||
ENABLE_JUDGES=true
|
||||
ENABLE_SUPERVISOR=true
|
||||
ENABLE_OUTPUT_SUPERVISOR=true
|
||||
ENABLE_PARALLEL_GUARDRAILS=true
|
||||
GUARDRAILS_FAIL_FAST=true
|
||||
OUTPUT_SUPERVISOR_MAX_RETRIES=3
|
||||
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
|
||||
JUDGES_CONFIG_PATH=./config/judges.yaml
|
||||
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
|
||||
|
||||
###############################################################################
|
||||
# Gateway de canais
|
||||
###############################################################################
|
||||
DEFAULT_CHANNEL=web
|
||||
# embedded = backend may parse simple/native channel payloads.
|
||||
# external = backend only accepts GatewayRequest normalized by an external Channel Gateway.
|
||||
FRAMEWORK_CHANNEL_INPUT_MODE=embedded
|
||||
ENABLE_VOICE_ADAPTER=true
|
||||
ENABLE_WHATSAPP_ADAPTER=true
|
||||
ENABLE_TEXT_ADAPTER=true
|
||||
|
||||
#################################################
|
||||
# ENTERPRISE ROUTING
|
||||
#################################################
|
||||
# Arquivo YAML com intents, keywords, políticas de estado e fallback.
|
||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
# true = usa LLM para classificar quando keywords/estado não resolverem.
|
||||
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
|
||||
ENABLE_LLM_ROUTER=true
|
||||
|
||||
# Semantic route stickiness (optional).
|
||||
# Uses a lightweight LLM profile to decide only CONTINUE vs ROUTE.
|
||||
# There are no regexes or deterministic language rules.
|
||||
ENABLE_ROUTE_STICKINESS=true
|
||||
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
|
||||
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
|
||||
ROUTE_STICKINESS_HISTORY_TURNS=2
|
||||
ROUTE_STICKINESS_MAX_TOKENS=80
|
||||
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
|
||||
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
|
||||
ENABLE_TRANSACTIONAL_WORKFLOWS=true
|
||||
WORKFLOWS_PATH=./workflows
|
||||
|
||||
###############################################################################
|
||||
# MCP / Tools
|
||||
###############################################################################
|
||||
ENABLE_MCP_TOOLS=true
|
||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||
TOOL_POLICIES_PATH=./config/tool_policies.yaml
|
||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
||||
|
||||
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
||||
ROUTING_MODE=router
|
||||
|
||||
# Usage/cost accounting
|
||||
USAGE_REPOSITORY_PROVIDER=autonomous
|
||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ConversationSummaryMemory / compressão de contexto conversacional
|
||||
# -----------------------------------------------------------------------------
|
||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
||||
MEMORY_CONTEXT_STRATEGY=summary
|
||||
MEMORY_HISTORY_LIMIT=80
|
||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
||||
MEMORY_SUMMARY_USE_LLM=true
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
|
||||
###############################################################################
|
||||
# MCP Gateway
|
||||
###############################################################################
|
||||
# true = framework routes tool calls to the dedicated MCP Gateway.
|
||||
# false = framework calls MCP servers directly from mcp_servers.yaml.
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
# MCP_GATEWAY_TOKEN=
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
|
||||
###############################################################################
|
||||
# LONG-TERM MEMORY
|
||||
###############################################################################
|
||||
ENABLE_LONG_TERM_MEMORY=true
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
||||
# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY
|
||||
# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
||||
@@ -0,0 +1,3 @@
|
||||
from .tool_renderers import register_tool_renderers
|
||||
|
||||
__all__ = ["register_tool_renderers"]
|
||||
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.presentation import register_tool_response_renderer
|
||||
|
||||
|
||||
def _money_brl(value: Any) -> str:
|
||||
try:
|
||||
return f"{float(value):.2f}".replace(".", ",")
|
||||
except (TypeError, ValueError):
|
||||
return str(value)
|
||||
|
||||
|
||||
def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
return f"[{agent_label}] Fatura consultada: {result}."
|
||||
|
||||
|
||||
def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
plano = result.get("plano")
|
||||
if plano is None:
|
||||
return None
|
||||
parts = [f"[{agent_label}] Seu plano é {plano}"]
|
||||
internet_gb = result.get("internet_gb")
|
||||
status = result.get("status")
|
||||
if internet_gb is not None:
|
||||
parts.append(f"com {internet_gb} GB")
|
||||
if status is not None:
|
||||
parts.append(f"status {status}")
|
||||
return ", ".join(parts) + "."
|
||||
|
||||
|
||||
def render_retail_order(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
status = result.get("status")
|
||||
if order_id is None or status is None:
|
||||
return None
|
||||
lines = [f"[{agent_label}] Pedido {order_id}: status {status}."]
|
||||
total = result.get("valor_total")
|
||||
if total is not None:
|
||||
lines.append(f"Valor total: R$ {_money_brl(total)}.")
|
||||
items = result.get("itens") or []
|
||||
rendered_items: list[str] = []
|
||||
if isinstance(items, list):
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
value = item.get("descricao") or item.get("nome") or item.get("sku")
|
||||
else:
|
||||
value = item
|
||||
if value not in (None, ""):
|
||||
rendered_items.append(str(value))
|
||||
if rendered_items:
|
||||
lines.append("Itens: " + "; ".join(rendered_items) + ".")
|
||||
return " ".join(lines)
|
||||
|
||||
|
||||
def render_retail_delivery(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
transportadora = result.get("transportadora")
|
||||
codigo = result.get("codigo_rastreio")
|
||||
previsao = result.get("previsao_entrega")
|
||||
if any(v is None for v in (order_id, transportadora, codigo, previsao)):
|
||||
return None
|
||||
return (
|
||||
f"[{agent_label}] Entrega do pedido {order_id}: transportadora {transportadora}, "
|
||||
f"rastreio {codigo}, previsão {previsao}."
|
||||
)
|
||||
|
||||
|
||||
def register_tool_renderers() -> None:
|
||||
register_tool_response_renderer("telecom.invoice", render_telecom_invoice)
|
||||
register_tool_response_renderer("telecom.plan", render_telecom_plan)
|
||||
register_tool_response_renderer("retail.order", render_retail_order)
|
||||
register_tool_response_renderer("retail.delivery", render_retail_delivery)
|
||||
@@ -26,6 +26,8 @@ class AgentState(TypedDict, total=False):
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
active_transaction: dict[str, Any]
|
||||
last_transaction: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
|
||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
cancelar_pedido:
|
||||
map:
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente pelo usuário. Retorne null quando não houver identificador de pedido na mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
|
||||
solicitar_troca:
|
||||
map:
|
||||
session_key: session_id
|
||||
|
||||
@@ -79,6 +79,25 @@ intents:
|
||||
- Quero saber sobre meu pacote de internet.
|
||||
- Tenho roaming internacional?
|
||||
|
||||
|
||||
- name: retail_order_cancel
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
description: Cancelamento explícito de pedido ou compra.
|
||||
priority: 20
|
||||
mcp_tools:
|
||||
- consultar_pedido
|
||||
- cancelar_pedido
|
||||
keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar a compra
|
||||
- cancelar compra
|
||||
examples:
|
||||
- Quero cancelar meu pedido.
|
||||
- Cancele o pedido.
|
||||
- Quero cancelar a compra.
|
||||
|
||||
- name: retail_order_tracking
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
|
||||
@@ -7,6 +7,12 @@ defaults:
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_pedido:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id]
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
@@ -10,6 +10,9 @@ tools:
|
||||
- fatura
|
||||
- conta
|
||||
- boleto
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.invoice
|
||||
consultar_pagamentos:
|
||||
description: Consulta histórico de pagamentos do cliente.
|
||||
mcp_server: telecom
|
||||
@@ -28,6 +31,9 @@ tools:
|
||||
asset_id: string
|
||||
selection_keywords:
|
||||
- plano
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.plan
|
||||
listar_servicos:
|
||||
description: Lista serviços ativos e adicionais VAS.
|
||||
mcp_server: telecom
|
||||
@@ -49,6 +55,9 @@ tools:
|
||||
- consultar pedido
|
||||
- status do pedido
|
||||
- pedido
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.order
|
||||
consultar_entrega:
|
||||
description: Consulta entrega e rastreamento do pedido.
|
||||
mcp_server: retail
|
||||
@@ -61,6 +70,26 @@ tools:
|
||||
- rastreamento
|
||||
- transportadora
|
||||
- previsão
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.delivery
|
||||
cancelar_pedido:
|
||||
description: Simula o cancelamento de um pedido de varejo.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires:
|
||||
- order_id
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
selection_keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar compra
|
||||
- cancelar a compra
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
|
||||
@@ -78,3 +78,10 @@ profiles:
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
|
||||
transaction_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
timeout_seconds: 8
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Domain Requested LLM Composition
|
||||
|
||||
## Objetivo
|
||||
|
||||
Permitir que uma tool/workflow de domínio informe que o resultado operacional não deve ser devolvido diretamente ao usuário e precisa ser redigido pelo LLM oficial do agente, sem criar um gateway LLM dentro do domínio.
|
||||
|
||||
## Contrato
|
||||
|
||||
A tool pode devolver, em qualquer nível do resultado:
|
||||
|
||||
```json
|
||||
{
|
||||
"requires_llm_composition": true,
|
||||
"response_instruction": "Explique ao cliente a forma de devolução usando apenas os dados do workflow."
|
||||
}
|
||||
```
|
||||
|
||||
Também é aceito `response_instructions` como lista.
|
||||
|
||||
O `AgentRuntimeMixin` percorre recursivamente o resultado MCP. Quando a flag está ativa, `build_direct_mcp_answer()` retorna `None`; a resposta segue pelo LLM configurado no framework e recebe as evidências MCP no contexto normal do agente.
|
||||
|
||||
## Por que isso existe
|
||||
|
||||
Algumas operações, como pró-rata, possuem resultado determinístico e efeitos já concluídos, mas precisam de linguagem natural adequada ao canal. Antes, o domínio Contas possuía um gateway LLM próprio. Agora o domínio só declara a necessidade e a instrução; execução, credenciais, profiles, tracing e custos continuam no `agent_framework_oci`.
|
||||
|
||||
## Regras
|
||||
|
||||
- Não usar para decidir se uma transação deve ocorrer.
|
||||
- Não usar para inventar valores ou protocolos.
|
||||
- A instrução deve exigir que o LLM use somente as evidências retornadas pela tool/workflow.
|
||||
- Pode coexistir com `requires_rag`; nesse caso o framework também executa RAG antes da composição.
|
||||
51
Tuning-Performance/Domain_Requested_RAG/README.md
Normal file
51
Tuning-Performance/Domain_Requested_RAG/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Domain-Requested RAG
|
||||
|
||||
## Objetivo
|
||||
|
||||
Permitir que uma tool/workflow de domínio declare que o resultado MCP **não é suficiente** para produzir a resposta final e que o agente deve recuperar conhecimento usando o `RagService` oficial do `agent_framework_oci`.
|
||||
|
||||
O domínio não instancia banco vetorial, embeddings, LLM ou cliente RAG. Ele apenas devolve no resultado:
|
||||
|
||||
```json
|
||||
{
|
||||
"requires_rag": true,
|
||||
"rag_queries": [
|
||||
"Como cancelar o serviço Paramount+ no parceiro? Procedimento oficial de cancelamento."
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Fluxo
|
||||
|
||||
```text
|
||||
Workflow/tool de domínio
|
||||
|
|
||||
| requires_rag + rag_queries
|
||||
v
|
||||
AgentRuntimeMixin
|
||||
|
|
||||
+-- impede direct MCP answer
|
||||
+-- ignora SKIP_RAG_WHEN_MCP_SUFFICIENT para este resultado
|
||||
+-- usa rag_query/rag_queries como query override
|
||||
v
|
||||
RagService
|
||||
v
|
||||
Vector/Graph store configurado no framework
|
||||
v
|
||||
LLM do agente com MCP evidence + RAG evidence
|
||||
```
|
||||
|
||||
## Regras
|
||||
|
||||
1. `requires_rag=false` ou ausente mantém o comportamento padrão.
|
||||
2. `SKIP_RAG_WHEN_MCP_SUFFICIENT=true` continua válido para tools normais.
|
||||
3. Quando `requires_rag=true`, o framework não usa `build_direct_mcp_answer()`.
|
||||
4. `rag_query` aceita uma query; `rag_queries` aceita várias queries, preservadas e deduplicadas em ordem.
|
||||
5. O domínio nunca executa `RagService` diretamente.
|
||||
6. Guardrails de retrieval (`RAGSEC`, `RET_REL` etc.) continuam executando normalmente sobre o contexto recuperado.
|
||||
|
||||
## Exemplo Contas
|
||||
|
||||
No fluxo VAS Estratégico, após o cliente rejeitar a explicação e pedir cancelamento, a action devolve queries específicas por parceiro. O `VasAgent` usa o `RagService` do framework para recuperar o procedimento oficial e compor a resposta.
|
||||
|
||||
Isso substitui o comportamento legado em que o backend Contas possuía uma busca RAG própria dentro de `vas_strategic()`.
|
||||
@@ -3,5 +3,8 @@ from __future__ import annotations
|
||||
# Compatibilidade local do template/backend.
|
||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||
from app.presentation import register_tool_renderers
|
||||
|
||||
register_tool_renderers()
|
||||
|
||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .tool_renderers import register_tool_renderers
|
||||
|
||||
__all__ = ["register_tool_renderers"]
|
||||
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.presentation import register_tool_response_renderer
|
||||
|
||||
|
||||
def _money_brl(value: Any) -> str:
|
||||
try:
|
||||
return f"{float(value):.2f}".replace(".", ",")
|
||||
except (TypeError, ValueError):
|
||||
return str(value)
|
||||
|
||||
|
||||
def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
return f"[{agent_label}] Fatura consultada: {result}."
|
||||
|
||||
|
||||
def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
plano = result.get("plano")
|
||||
if plano is None:
|
||||
return None
|
||||
parts = [f"[{agent_label}] Seu plano é {plano}"]
|
||||
internet_gb = result.get("internet_gb")
|
||||
status = result.get("status")
|
||||
if internet_gb is not None:
|
||||
parts.append(f"com {internet_gb} GB")
|
||||
if status is not None:
|
||||
parts.append(f"status {status}")
|
||||
return ", ".join(parts) + "."
|
||||
|
||||
|
||||
def render_retail_order(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
status = result.get("status")
|
||||
if order_id is None or status is None:
|
||||
return None
|
||||
lines = [f"[{agent_label}] Pedido {order_id}: status {status}."]
|
||||
total = result.get("valor_total")
|
||||
if total is not None:
|
||||
lines.append(f"Valor total: R$ {_money_brl(total)}.")
|
||||
items = result.get("itens") or []
|
||||
rendered_items: list[str] = []
|
||||
if isinstance(items, list):
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
value = item.get("descricao") or item.get("nome") or item.get("sku")
|
||||
else:
|
||||
value = item
|
||||
if value not in (None, ""):
|
||||
rendered_items.append(str(value))
|
||||
if rendered_items:
|
||||
lines.append("Itens: " + "; ".join(rendered_items) + ".")
|
||||
return " ".join(lines)
|
||||
|
||||
|
||||
def render_retail_delivery(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
transportadora = result.get("transportadora")
|
||||
codigo = result.get("codigo_rastreio")
|
||||
previsao = result.get("previsao_entrega")
|
||||
if any(v is None for v in (order_id, transportadora, codigo, previsao)):
|
||||
return None
|
||||
return (
|
||||
f"[{agent_label}] Entrega do pedido {order_id}: transportadora {transportadora}, "
|
||||
f"rastreio {codigo}, previsão {previsao}."
|
||||
)
|
||||
|
||||
|
||||
def register_tool_renderers() -> None:
|
||||
register_tool_response_renderer("telecom.invoice", render_telecom_invoice)
|
||||
register_tool_response_renderer("telecom.plan", render_telecom_plan)
|
||||
register_tool_response_renderer("retail.order", render_retail_order)
|
||||
register_tool_response_renderer("retail.delivery", render_retail_delivery)
|
||||
@@ -26,6 +26,8 @@ class AgentState(TypedDict, total=False):
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
active_transaction: dict[str, Any]
|
||||
last_transaction: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
|
||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
cancelar_pedido:
|
||||
map:
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente pelo usuário. Retorne null quando não houver identificador de pedido na mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
|
||||
solicitar_troca:
|
||||
map:
|
||||
session_key: session_id
|
||||
|
||||
@@ -79,6 +79,25 @@ intents:
|
||||
- Quero saber sobre meu pacote de internet.
|
||||
- Tenho roaming internacional?
|
||||
|
||||
|
||||
- name: retail_order_cancel
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
description: Cancelamento explícito de pedido ou compra.
|
||||
priority: 20
|
||||
mcp_tools:
|
||||
- consultar_pedido
|
||||
- cancelar_pedido
|
||||
keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar a compra
|
||||
- cancelar compra
|
||||
examples:
|
||||
- Quero cancelar meu pedido.
|
||||
- Cancele o pedido.
|
||||
- Quero cancelar a compra.
|
||||
|
||||
- name: retail_order_tracking
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
|
||||
@@ -7,6 +7,12 @@ defaults:
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_pedido:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id]
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
@@ -10,6 +10,9 @@ tools:
|
||||
- fatura
|
||||
- conta
|
||||
- boleto
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.invoice
|
||||
consultar_pagamentos:
|
||||
description: Consulta histórico de pagamentos do cliente.
|
||||
mcp_server: telecom
|
||||
@@ -28,6 +31,9 @@ tools:
|
||||
asset_id: string
|
||||
selection_keywords:
|
||||
- plano
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.plan
|
||||
listar_servicos:
|
||||
description: Lista serviços ativos e adicionais VAS.
|
||||
mcp_server: telecom
|
||||
@@ -49,6 +55,9 @@ tools:
|
||||
- consultar pedido
|
||||
- status do pedido
|
||||
- pedido
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.order
|
||||
consultar_entrega:
|
||||
description: Consulta entrega e rastreamento do pedido.
|
||||
mcp_server: retail
|
||||
@@ -61,6 +70,26 @@ tools:
|
||||
- rastreamento
|
||||
- transportadora
|
||||
- previsão
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.delivery
|
||||
cancelar_pedido:
|
||||
description: Simula o cancelamento de um pedido de varejo.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires:
|
||||
- order_id
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
selection_keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar compra
|
||||
- cancelar a compra
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
|
||||
@@ -78,3 +78,10 @@ profiles:
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
|
||||
transaction_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
timeout_seconds: 8
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
###############################################################################
|
||||
# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA
|
||||
# Este arquivo é lido por Pydantic Settings no framework e no backend template.
|
||||
###############################################################################
|
||||
|
||||
APP_NAME=ai-agent-template
|
||||
APP_ENV=local
|
||||
LOG_LEVEL=INFO
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
|
||||
###############################################################################
|
||||
# LLM - OCI Generative AI como provider principal
|
||||
###############################################################################
|
||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
||||
LLM_PROVIDER=oci_openai
|
||||
LLM_TEMPERATURE=0.2
|
||||
LLM_MAX_TOKENS=2048
|
||||
LLM_TIMEOUT_SECONDS=120
|
||||
|
||||
# OCI OpenAI-compatible endpoint
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6
|
||||
OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
# OCI SDK / signer / profiles
|
||||
OCI_CONFIG_FILE=~/.oci/config
|
||||
OCI_PROFILE=DEFAULT
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
OCI_REGION=us-chicago-1
|
||||
|
||||
###############################################################################
|
||||
# Persistência
|
||||
###############################################################################
|
||||
# Opções: memory, autonomous, mongodb
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
SQLITE_DB_PATH=./data/agent_framework.db
|
||||
|
||||
# Autonomous Database
|
||||
ADB_USER=admin
|
||||
ADB_PASSWORD=fjhsdf04954hf
|
||||
ADB_DSN=oradb23aidev_high
|
||||
ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev
|
||||
ADB_WALLET_PASSWORD=fjhsdf04954hf
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
|
||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
||||
MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
|
||||
MONGODB_DATABASE=agent_platform
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
ENABLE_REDIS_CACHE=false
|
||||
|
||||
###############################################################################
|
||||
# RAG / Vector / Graph
|
||||
###############################################################################
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
GRAPH_STORE_PROVIDER=sqlite
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=mock
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
|
||||
###############################################################################
|
||||
# Observabilidade
|
||||
###############################################################################
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_TRACE_MODE=compact # Opcional: verbose, compact
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-2f9da109-5b0f-4c78-b61d-9598ed787eba
|
||||
LANGFUSE_SECRET_KEY=sk-lf-a4cb0cdd-f2ea-4468-9911-cebeb91ba944
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
|
||||
###############################################################################
|
||||
# Analytics / Observer corporativo
|
||||
###############################################################################
|
||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
||||
ENABLE_ANALYTICS=false
|
||||
# Providers aceitos: oci_streaming,pubsub,noop
|
||||
ANALYTICS_PROVIDERS=pubsub
|
||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
||||
AGENT_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TOPIC_PATH=
|
||||
GCP_PROJECT_ID=
|
||||
GCP_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TIMEOUT_SECONDS=30
|
||||
# Credencial GCP segue padrão Google:
|
||||
# GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-service-account.json
|
||||
|
||||
###############################################################################
|
||||
# OCI Streaming
|
||||
###############################################################################
|
||||
ENABLE_OCI_STREAMING=false
|
||||
OCI_STREAM_ENDPOINT=
|
||||
OCI_STREAM_OCID=
|
||||
OCI_STREAM_PARTITION_KEY=agent-events
|
||||
|
||||
###############################################################################
|
||||
# Guardrails, Judges, Supervisor
|
||||
###############################################################################
|
||||
ENABLE_INPUT_GUARDRAILS=true
|
||||
ENABLE_OUTPUT_GUARDRAILS=true
|
||||
ENABLE_JUDGES=true
|
||||
ENABLE_SUPERVISOR=true
|
||||
ENABLE_OUTPUT_SUPERVISOR=true
|
||||
ENABLE_PARALLEL_GUARDRAILS=true
|
||||
GUARDRAILS_FAIL_FAST=true
|
||||
OUTPUT_SUPERVISOR_MAX_RETRIES=3
|
||||
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
|
||||
JUDGES_CONFIG_PATH=./config/judges.yaml
|
||||
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
|
||||
|
||||
###############################################################################
|
||||
# Gateway de canais
|
||||
###############################################################################
|
||||
DEFAULT_CHANNEL=web
|
||||
# embedded = backend may parse simple/native channel payloads.
|
||||
# external = backend only accepts GatewayRequest normalized by an external Channel Gateway.
|
||||
FRAMEWORK_CHANNEL_INPUT_MODE=embedded
|
||||
ENABLE_VOICE_ADAPTER=true
|
||||
ENABLE_WHATSAPP_ADAPTER=true
|
||||
ENABLE_TEXT_ADAPTER=true
|
||||
|
||||
#################################################
|
||||
# ENTERPRISE ROUTING
|
||||
#################################################
|
||||
# Arquivo YAML com intents, keywords, políticas de estado e fallback.
|
||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
# true = usa LLM para classificar quando keywords/estado não resolverem.
|
||||
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
|
||||
ENABLE_LLM_ROUTER=true
|
||||
|
||||
###############################################################################
|
||||
# MCP / Tools
|
||||
###############################################################################
|
||||
ENABLE_MCP_TOOLS=true
|
||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||
TOOL_POLICIES_PATH=./config/tool_policies.yaml
|
||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
||||
|
||||
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
||||
ROUTING_MODE=router
|
||||
|
||||
# Usage/cost accounting
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ConversationSummaryMemory / compressão de contexto conversacional
|
||||
# -----------------------------------------------------------------------------
|
||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
||||
MEMORY_CONTEXT_STRATEGY=summary
|
||||
MEMORY_HISTORY_LIMIT=80
|
||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
||||
MEMORY_SUMMARY_USE_LLM=true
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
|
||||
###############################################################################
|
||||
# MCP Gateway
|
||||
###############################################################################
|
||||
# true = framework routes tool calls to the dedicated MCP Gateway.
|
||||
# false = framework calls MCP servers directly from mcp_servers.yaml.
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
# MCP_GATEWAY_TOKEN=
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
|
||||
###############################################################################
|
||||
# LONG-TERM MEMORY
|
||||
###############################################################################
|
||||
ENABLE_LONG_TERM_MEMORY=true
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
||||
# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY
|
||||
# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
||||
@@ -3,5 +3,8 @@ from __future__ import annotations
|
||||
# Compatibilidade local do template/backend.
|
||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||
from app.presentation import register_tool_renderers
|
||||
|
||||
register_tool_renderers()
|
||||
|
||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .tool_renderers import register_tool_renderers
|
||||
|
||||
__all__ = ["register_tool_renderers"]
|
||||
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.presentation import register_tool_response_renderer
|
||||
|
||||
|
||||
def _money_brl(value: Any) -> str:
|
||||
try:
|
||||
return f"{float(value):.2f}".replace(".", ",")
|
||||
except (TypeError, ValueError):
|
||||
return str(value)
|
||||
|
||||
|
||||
def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
return f"[{agent_label}] Fatura consultada: {result}."
|
||||
|
||||
|
||||
def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
plano = result.get("plano")
|
||||
if plano is None:
|
||||
return None
|
||||
parts = [f"[{agent_label}] Seu plano é {plano}"]
|
||||
internet_gb = result.get("internet_gb")
|
||||
status = result.get("status")
|
||||
if internet_gb is not None:
|
||||
parts.append(f"com {internet_gb} GB")
|
||||
if status is not None:
|
||||
parts.append(f"status {status}")
|
||||
return ", ".join(parts) + "."
|
||||
|
||||
|
||||
def render_retail_order(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
status = result.get("status")
|
||||
if order_id is None or status is None:
|
||||
return None
|
||||
lines = [f"[{agent_label}] Pedido {order_id}: status {status}."]
|
||||
total = result.get("valor_total")
|
||||
if total is not None:
|
||||
lines.append(f"Valor total: R$ {_money_brl(total)}.")
|
||||
items = result.get("itens") or []
|
||||
rendered_items: list[str] = []
|
||||
if isinstance(items, list):
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
value = item.get("descricao") or item.get("nome") or item.get("sku")
|
||||
else:
|
||||
value = item
|
||||
if value not in (None, ""):
|
||||
rendered_items.append(str(value))
|
||||
if rendered_items:
|
||||
lines.append("Itens: " + "; ".join(rendered_items) + ".")
|
||||
return " ".join(lines)
|
||||
|
||||
|
||||
def render_retail_delivery(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
transportadora = result.get("transportadora")
|
||||
codigo = result.get("codigo_rastreio")
|
||||
previsao = result.get("previsao_entrega")
|
||||
if any(v is None for v in (order_id, transportadora, codigo, previsao)):
|
||||
return None
|
||||
return (
|
||||
f"[{agent_label}] Entrega do pedido {order_id}: transportadora {transportadora}, "
|
||||
f"rastreio {codigo}, previsão {previsao}."
|
||||
)
|
||||
|
||||
|
||||
def register_tool_renderers() -> None:
|
||||
register_tool_response_renderer("telecom.invoice", render_telecom_invoice)
|
||||
register_tool_response_renderer("telecom.plan", render_telecom_plan)
|
||||
register_tool_response_renderer("retail.order", render_retail_order)
|
||||
register_tool_response_renderer("retail.delivery", render_retail_delivery)
|
||||
@@ -26,6 +26,8 @@ class AgentState(TypedDict, total=False):
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
active_transaction: dict[str, Any]
|
||||
last_transaction: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
|
||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
cancelar_pedido:
|
||||
map:
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente pelo usuário. Retorne null quando não houver identificador de pedido na mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
|
||||
solicitar_troca:
|
||||
map:
|
||||
session_key: session_id
|
||||
|
||||
@@ -79,6 +79,25 @@ intents:
|
||||
- Quero saber sobre meu pacote de internet.
|
||||
- Tenho roaming internacional?
|
||||
|
||||
|
||||
- name: retail_order_cancel
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
description: Cancelamento explícito de pedido ou compra.
|
||||
priority: 20
|
||||
mcp_tools:
|
||||
- consultar_pedido
|
||||
- cancelar_pedido
|
||||
keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar a compra
|
||||
- cancelar compra
|
||||
examples:
|
||||
- Quero cancelar meu pedido.
|
||||
- Cancele o pedido.
|
||||
- Quero cancelar a compra.
|
||||
|
||||
- name: retail_order_tracking
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
|
||||
@@ -7,6 +7,12 @@ defaults:
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_pedido:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id]
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
@@ -10,6 +10,9 @@ tools:
|
||||
- fatura
|
||||
- conta
|
||||
- boleto
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.invoice
|
||||
consultar_pagamentos:
|
||||
description: Consulta histórico de pagamentos do cliente.
|
||||
mcp_server: telecom
|
||||
@@ -28,6 +31,9 @@ tools:
|
||||
asset_id: string
|
||||
selection_keywords:
|
||||
- plano
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.plan
|
||||
listar_servicos:
|
||||
description: Lista serviços ativos e adicionais VAS.
|
||||
mcp_server: telecom
|
||||
@@ -49,6 +55,9 @@ tools:
|
||||
- consultar pedido
|
||||
- status do pedido
|
||||
- pedido
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.order
|
||||
consultar_entrega:
|
||||
description: Consulta entrega e rastreamento do pedido.
|
||||
mcp_server: retail
|
||||
@@ -61,6 +70,26 @@ tools:
|
||||
- rastreamento
|
||||
- transportadora
|
||||
- previsão
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.delivery
|
||||
cancelar_pedido:
|
||||
description: Simula o cancelamento de um pedido de varejo.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires:
|
||||
- order_id
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
selection_keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar compra
|
||||
- cancelar a compra
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
|
||||
@@ -72,3 +72,10 @@ profiles:
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
|
||||
transaction_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
timeout_seconds: 8
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
###############################################################################
|
||||
# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA
|
||||
# Este arquivo é lido por Pydantic Settings no framework e no backend template.
|
||||
###############################################################################
|
||||
|
||||
APP_NAME=ai-agent-template
|
||||
APP_ENV=local
|
||||
LOG_LEVEL=INFO
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
|
||||
###############################################################################
|
||||
# LLM - OCI Generative AI como provider principal
|
||||
###############################################################################
|
||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
||||
LLM_PROVIDER=oci_openai
|
||||
LLM_TEMPERATURE=0.2
|
||||
LLM_MAX_TOKENS=2048
|
||||
LLM_TIMEOUT_SECONDS=120
|
||||
|
||||
# OCI OpenAI-compatible endpoint
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6
|
||||
OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
# OCI SDK / signer / profiles
|
||||
OCI_CONFIG_FILE=~/.oci/config
|
||||
OCI_PROFILE=DEFAULT
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
OCI_REGION=us-chicago-1
|
||||
|
||||
###############################################################################
|
||||
# Persistência
|
||||
###############################################################################
|
||||
# Opções: memory, autonomous, mongodb
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
SQLITE_DB_PATH=./data/agent_framework.db
|
||||
|
||||
# Autonomous Database
|
||||
ADB_USER=admin
|
||||
ADB_PASSWORD=fjhsdf04954hf
|
||||
ADB_DSN=oradb23aidev_high
|
||||
ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev
|
||||
ADB_WALLET_PASSWORD=fjhsdf04954hf
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
|
||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
||||
MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
|
||||
MONGODB_DATABASE=agent_platform
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
ENABLE_REDIS_CACHE=false
|
||||
|
||||
###############################################################################
|
||||
# RAG / Vector / Graph
|
||||
###############################################################################
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
GRAPH_STORE_PROVIDER=sqlite
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=mock
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
|
||||
###############################################################################
|
||||
# Observabilidade
|
||||
###############################################################################
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_TRACE_MODE=compact # Opcional: verbose, compact
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-2f9da109-5b0f-4c78-b61d-9598ed787eba
|
||||
LANGFUSE_SECRET_KEY=sk-lf-a4cb0cdd-f2ea-4468-9911-cebeb91ba944
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
|
||||
###############################################################################
|
||||
# Analytics / Observer corporativo
|
||||
###############################################################################
|
||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
||||
ENABLE_ANALYTICS=false
|
||||
# Providers aceitos: oci_streaming,pubsub,noop
|
||||
ANALYTICS_PROVIDERS=pubsub
|
||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
||||
AGENT_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TOPIC_PATH=
|
||||
GCP_PROJECT_ID=
|
||||
GCP_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TIMEOUT_SECONDS=30
|
||||
# Credencial GCP segue padrão Google:
|
||||
# GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-service-account.json
|
||||
|
||||
###############################################################################
|
||||
# OCI Streaming
|
||||
###############################################################################
|
||||
ENABLE_OCI_STREAMING=false
|
||||
OCI_STREAM_ENDPOINT=
|
||||
OCI_STREAM_OCID=
|
||||
OCI_STREAM_PARTITION_KEY=agent-events
|
||||
|
||||
###############################################################################
|
||||
# Guardrails, Judges, Supervisor
|
||||
###############################################################################
|
||||
ENABLE_INPUT_GUARDRAILS=true
|
||||
ENABLE_OUTPUT_GUARDRAILS=true
|
||||
ENABLE_JUDGES=true
|
||||
ENABLE_SUPERVISOR=true
|
||||
ENABLE_OUTPUT_SUPERVISOR=true
|
||||
ENABLE_PARALLEL_GUARDRAILS=true
|
||||
GUARDRAILS_FAIL_FAST=true
|
||||
OUTPUT_SUPERVISOR_MAX_RETRIES=3
|
||||
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
|
||||
JUDGES_CONFIG_PATH=./config/judges.yaml
|
||||
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
|
||||
|
||||
###############################################################################
|
||||
# Gateway de canais
|
||||
###############################################################################
|
||||
DEFAULT_CHANNEL=web
|
||||
ENABLE_VOICE_ADAPTER=true
|
||||
ENABLE_WHATSAPP_ADAPTER=true
|
||||
ENABLE_TEXT_ADAPTER=true
|
||||
|
||||
#################################################
|
||||
# ENTERPRISE ROUTING
|
||||
#################################################
|
||||
# Arquivo YAML com intents, keywords, políticas de estado e fallback.
|
||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
# true = usa LLM para classificar quando keywords/estado não resolverem.
|
||||
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
|
||||
ENABLE_LLM_ROUTER=true
|
||||
|
||||
###############################################################################
|
||||
# MCP / Tools
|
||||
###############################################################################
|
||||
ENABLE_MCP_TOOLS=true
|
||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||
TOOL_POLICIES_PATH=./config/tool_policies.yaml
|
||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
||||
|
||||
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
||||
ROUTING_MODE=router
|
||||
|
||||
# Usage/cost accounting
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ConversationSummaryMemory / compressão de contexto conversacional
|
||||
# -----------------------------------------------------------------------------
|
||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
||||
MEMORY_CONTEXT_STRATEGY=summary
|
||||
MEMORY_HISTORY_LIMIT=80
|
||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
||||
MEMORY_SUMMARY_USE_LLM=true
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
|
||||
###############################################################################
|
||||
# MCP Gateway
|
||||
###############################################################################
|
||||
# true = framework routes tool calls to the dedicated MCP Gateway.
|
||||
# false = framework calls MCP servers directly from mcp_servers.yaml.
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
# MCP_GATEWAY_TOKEN=
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
|
||||
###############################################################################
|
||||
# LONG-TERM MEMORY
|
||||
###############################################################################
|
||||
ENABLE_LONG_TERM_MEMORY=true
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
||||
# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY
|
||||
# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
||||
@@ -26,6 +26,8 @@ class AgentState(TypedDict, total=False):
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
active_transaction: dict[str, Any]
|
||||
last_transaction: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
|
||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
cancelar_pedido:
|
||||
map:
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente pelo usuário. Retorne null quando não houver identificador de pedido na mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
|
||||
solicitar_troca:
|
||||
map:
|
||||
session_key: session_id
|
||||
|
||||
@@ -79,6 +79,25 @@ intents:
|
||||
- Quero saber sobre meu pacote de internet.
|
||||
- Tenho roaming internacional?
|
||||
|
||||
|
||||
- name: retail_order_cancel
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
description: Cancelamento explícito de pedido ou compra.
|
||||
priority: 20
|
||||
mcp_tools:
|
||||
- consultar_pedido
|
||||
- cancelar_pedido
|
||||
keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar a compra
|
||||
- cancelar compra
|
||||
examples:
|
||||
- Quero cancelar meu pedido.
|
||||
- Cancele o pedido.
|
||||
- Quero cancelar a compra.
|
||||
|
||||
- name: retail_order_tracking
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
|
||||
@@ -7,6 +7,12 @@ defaults:
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_pedido:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id]
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
@@ -61,6 +61,23 @@ tools:
|
||||
- rastreamento
|
||||
- transportadora
|
||||
- previsão
|
||||
cancelar_pedido:
|
||||
description: Simula o cancelamento de um pedido de varejo.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires:
|
||||
- order_id
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
selection_keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar compra
|
||||
- cancelar a compra
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
profiles:
|
||||
default:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.2
|
||||
max_tokens: 2048
|
||||
supervisor:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 700
|
||||
route_continuity:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
router:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
guardrail:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 600
|
||||
grl:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 700
|
||||
judge:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 800
|
||||
rag_rewriter:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 300
|
||||
rag_compressor:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 1200
|
||||
rag_generation:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.1
|
||||
max_tokens: 1800
|
||||
summary_memory:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.1
|
||||
max_tokens: 1200
|
||||
noc:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 700
|
||||
billing_agent:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.2
|
||||
product_agent:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.2
|
||||
backoffice_agent:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.2
|
||||
mcp_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
|
||||
transaction_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
timeout_seconds: 8
|
||||
21
Tuning-Performance/Offline_Workflow_Regression/README.md
Normal file
21
Tuning-Performance/Offline_Workflow_Regression/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Offline Workflow Regression
|
||||
|
||||
O backend de produção de `WorkflowRuntime` continua sendo **LangGraph**. A ausência do pacote `langgraph` em produção é erro de configuração.
|
||||
|
||||
Para builders restritos/offline, o runtime aceita `allow_deterministic_fallback=True`. Esse modo é deliberadamente opt-in e existe somente para exercitar a DSL do framework (actions, edges, condições, pause/resume e trace) quando a dependência externa não pode ser instalada. Ele não é selecionado automaticamente.
|
||||
|
||||
Exemplo de teste:
|
||||
|
||||
```python
|
||||
runtime = WorkflowRuntime(
|
||||
repository,
|
||||
actions=registry,
|
||||
allow_deterministic_fallback=True,
|
||||
)
|
||||
first = await runtime.arun("workflow", payload)
|
||||
assert first.status == "PAUSED"
|
||||
final = await runtime.aresume("workflow", first.execution_id, {"resposta": "SIM"})
|
||||
assert final.status == "COMPLETED"
|
||||
```
|
||||
|
||||
O objetivo é não transformar indisponibilidade de rede/PyPI em `pytest.skip`, sem mascarar o requisito de LangGraph do runtime de produção.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Implementação no framework
|
||||
|
||||
A evolução adiciona `WorkflowPause` e `WorkflowExpectedInput` ao modelo de workflow e mantém o LangGraph como detalhe interno de `WorkflowRuntime`.
|
||||
|
||||
O runtime aceita condições declarativas `all`, `any`, `not`, `eq`, `neq`, `exists`, `path/equals`, `path/not_equals` e `path/in`. Em um nó com `pause`, a action é executada primeiro e a interrupção ocorre em um nó técnico separado. Na retomada, `langgraph.types.Command(resume=...)` injeta o valor esperado e segue para `resume_from` sem repetir a action que precedeu o pause.
|
||||
|
||||
`FrameworkStateGraph` é a facade para aplicações que ainda precisam compor um grafo de agentes; templates oficiais devem usar a facade e não importar LangGraph diretamente.
|
||||
|
||||
## Preservação de estado em falhas posteriores
|
||||
|
||||
O `WorkflowRuntime` também preserva o último snapshot persistido do LangGraph quando uma action posterior falha. O resultado `FAILED` contém `output`, `state` e `trace` dos nodes que já terminaram com sucesso.
|
||||
|
||||
Isso é necessário para workflows transacionais: por exemplo, se um protocolo foi criado e uma chamada posterior falha, o chamador ainda recebe o `protocol_number` persistido e pode executar recuperação/idempotência sem repetir o primeiro side effect.
|
||||
|
||||
O runtime não transforma falha em sucesso e não reexecuta automaticamente a action; ele apenas preserva a evidência durável já existente no checkpointer.
|
||||
34
Tuning-Performance/Pause_Resume_Workflow/README.md
Normal file
34
Tuning-Performance/Pause_Resume_Workflow/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Pause/Resume Workflow — LangGraph encapsulado pelo Agent Framework OCI
|
||||
|
||||
Este exemplo demonstra uma capability genérica do `agent_framework_oci`: workflows determinísticos podem interromper a execução para obter uma resposta do cliente e retomar posteriormente pelo mesmo `execution_id`, sem que o domínio importe ou monte um `StateGraph`.
|
||||
|
||||
## Conceito
|
||||
|
||||
A aplicação declara o workflow em YAML. `WorkflowRuntime` transforma a definição em LangGraph internamente, utiliza o checkpointer configurado pelo framework e expõe somente:
|
||||
|
||||
- `arun(name, payload)` — inicia ou executa o workflow;
|
||||
- `aresume(name, execution_id, value)` — retoma o workflow pausado;
|
||||
- `WorkflowRunResult.status` — `PAUSED`, `COMPLETED` ou `FAILED`.
|
||||
|
||||
O `pause` é compilado em um nó separado da action anterior. Isto impede que uma action com efeito colateral seja executada novamente quando o cliente responde.
|
||||
|
||||
## Executar
|
||||
|
||||
A partir da raiz do exemplo, com as dependências do framework instaladas:
|
||||
|
||||
```bash
|
||||
python -m app.demo
|
||||
pytest -q
|
||||
```
|
||||
|
||||
## YAML
|
||||
|
||||
`workflows/confirmacao.v1.yaml` mostra `expected_input`, normalização, valores permitidos e `resume_from`.
|
||||
|
||||
## Persistência
|
||||
|
||||
O exemplo usa `MemorySaver` apenas para ser autocontido. Em aplicações reais use `create_langgraph_checkpointer(settings)`. Assim `execution_id` é o `thread_id` do LangGraph e a retomada sobrevive a processos/replicas conforme o provider configurado (por exemplo Autonomous Database).
|
||||
|
||||
## Regra arquitetural
|
||||
|
||||
Código de domínio não deve importar `langgraph.graph.StateGraph`. Para grafos de agentes use `FrameworkStateGraph`; para workflows determinísticos de negócio use `WorkflowRuntime`.
|
||||
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.workflows import FileWorkflowRepository, WorkflowActionRegistry, WorkflowRuntime
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def build_runtime(*, offline_test_fallback: bool = False) -> WorkflowRuntime:
|
||||
actions = WorkflowActionRegistry()
|
||||
|
||||
async def preparar(params, state):
|
||||
return {"assunto": params.get("assunto") or "operação"}
|
||||
|
||||
async def perguntar(params, state):
|
||||
return {"mensagem": f"Deseja confirmar {params['assunto']}?"}
|
||||
|
||||
async def decidir(params, state):
|
||||
return {
|
||||
"mensagem": "Operação confirmada." if params["resposta"] == "SIM" else "Operação cancelada.",
|
||||
"confirmado": params["resposta"] == "SIM",
|
||||
}
|
||||
|
||||
actions.register("preparar_operacao", preparar)
|
||||
actions.register("montar_pergunta", perguntar)
|
||||
actions.register("registrar_decisao", decidir)
|
||||
|
||||
checkpointer = None
|
||||
if not offline_test_fallback:
|
||||
# Produção/exemplo real continua usando LangGraph + checkpointer. O import
|
||||
# fica aqui para que a regressão offline do repositório não dependa de rede.
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
checkpointer = MemorySaver()
|
||||
|
||||
return WorkflowRuntime(
|
||||
FileWorkflowRepository(ROOT / "workflows"),
|
||||
actions=actions,
|
||||
checkpointer=checkpointer,
|
||||
allow_deterministic_fallback=offline_test_fallback,
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
runtime = build_runtime()
|
||||
first = await runtime.arun("confirmacao", {"assunto": "a alteração do plano"})
|
||||
print(first.model_dump(mode="json"))
|
||||
assert first.status == "PAUSED"
|
||||
resumed = await runtime.aresume("confirmacao", first.execution_id, {"resposta_usuario": "sim"})
|
||||
print(resumed.model_dump(mode="json"))
|
||||
assert resumed.status == "COMPLETED"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.demo import build_runtime
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pause_resume_does_not_repeat_previous_action():
|
||||
# Regressão offline: exercita a mesma DSL/WorkflowRuntime sem exigir download
|
||||
# de LangGraph no builder. Produção continua usando build_runtime() default.
|
||||
runtime = build_runtime(offline_test_fallback=True)
|
||||
first = await runtime.arun("confirmacao", {"assunto": "o cancelamento"})
|
||||
assert first.status == "PAUSED"
|
||||
assert first.pause["expected_input"]["key"] == "resposta_usuario"
|
||||
before = [item for item in first.trace if item.get("action") == "preparar_operacao"]
|
||||
assert len(before) == 1
|
||||
resumed = await runtime.aresume("confirmacao", first.execution_id, {"resposta_usuario": "SIM"})
|
||||
assert resumed.status == "COMPLETED"
|
||||
after = [item for item in resumed.trace if item.get("action") == "preparar_operacao"]
|
||||
assert len(after) == 1
|
||||
assert resumed.state["vars"]["decidir"]["confirmado"] is True
|
||||
@@ -0,0 +1 @@
|
||||
version: 1
|
||||
@@ -0,0 +1,32 @@
|
||||
name: confirmacao
|
||||
version: 1
|
||||
start: preparar
|
||||
nodes:
|
||||
- id: preparar
|
||||
action: preparar_operacao
|
||||
input:
|
||||
assunto: $.input.assunto
|
||||
- id: perguntar
|
||||
action: montar_pergunta
|
||||
input:
|
||||
assunto: $.vars.preparar.assunto
|
||||
pause:
|
||||
enabled: true
|
||||
return_from: $.output.mensagem
|
||||
expected_input:
|
||||
key: resposta_usuario
|
||||
allowed_values: [SIM, NAO]
|
||||
normalize: upper_strip
|
||||
resume_from: decidir
|
||||
- id: decidir
|
||||
action: registrar_decisao
|
||||
input:
|
||||
resposta: $.input.resposta_usuario
|
||||
assunto: $.vars.preparar.assunto
|
||||
edges:
|
||||
- from: preparar
|
||||
to: perguntar
|
||||
- from: perguntar
|
||||
to: END
|
||||
- from: decidir
|
||||
to: END
|
||||
@@ -6,3 +6,5 @@ Variantes e documentos de referência para comparar funcionalidades e impacto de
|
||||
- `Route_Stickness`: continuidade de rota, handoff e políticas transacionais conversacionais.
|
||||
- `Long_Term_Memory`: memória de longo prazo.
|
||||
- `Deterministic_Transactional_Workflow`: transações multi-etapas executadas por workflow LangGraph determinístico após clarification e confirmação.
|
||||
- `Transaction_Evidence`: persistência e correlação de resultados transacionais como evidência operacional para turnos posteriores e groundedness.
|
||||
- `Transaction_Pre_Validation`: pré-validação MCP side-effect-free antes da confirmação transacional; regras de elegibilidade permanecem no domínio.
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
###############################################################################
|
||||
# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA
|
||||
# Este arquivo é lido por Pydantic Settings no framework e no backend template.
|
||||
###############################################################################
|
||||
|
||||
APP_NAME=ai-agent-template
|
||||
APP_ENV=local
|
||||
LOG_LEVEL=INFO
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
|
||||
###############################################################################
|
||||
# LLM - OCI Generative AI como provider principal
|
||||
###############################################################################
|
||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
||||
LLM_PROVIDER=oci_openai
|
||||
LLM_TEMPERATURE=0.2
|
||||
LLM_MAX_TOKENS=2048
|
||||
LLM_TIMEOUT_SECONDS=120
|
||||
|
||||
# OCI OpenAI-compatible endpoint
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6
|
||||
OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
# OCI SDK / signer / profiles
|
||||
OCI_CONFIG_FILE=~/.oci/config
|
||||
OCI_PROFILE=DEFAULT
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
OCI_REGION=us-chicago-1
|
||||
|
||||
###############################################################################
|
||||
# Persistência
|
||||
###############################################################################
|
||||
# Opções: memory, autonomous, mongodb
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
SQLITE_DB_PATH=./data/agent_framework.db
|
||||
|
||||
# Autonomous Database
|
||||
ADB_USER=admin
|
||||
ADB_PASSWORD=fjhsdf04954hf
|
||||
ADB_DSN=oradb23aidev_high
|
||||
ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev
|
||||
ADB_WALLET_PASSWORD=fjhsdf04954hf
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
|
||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
||||
MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
|
||||
MONGODB_DATABASE=agent_platform
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
ENABLE_REDIS_CACHE=false
|
||||
|
||||
###############################################################################
|
||||
# RAG / Vector / Graph
|
||||
###############################################################################
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
GRAPH_STORE_PROVIDER=sqlite
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=mock
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
|
||||
###############################################################################
|
||||
# Observabilidade
|
||||
###############################################################################
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_TRACE_MODE=compact # Opcional: verbose, compact
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-2f9da109-5b0f-4c78-b61d-9598ed787eba
|
||||
LANGFUSE_SECRET_KEY=sk-lf-a4cb0cdd-f2ea-4468-9911-cebeb91ba944
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
|
||||
###############################################################################
|
||||
# Analytics / Observer corporativo
|
||||
###############################################################################
|
||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
||||
ENABLE_ANALYTICS=false
|
||||
# Providers aceitos: oci_streaming,pubsub,noop
|
||||
ANALYTICS_PROVIDERS=pubsub
|
||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
||||
AGENT_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TOPIC_PATH=
|
||||
GCP_PROJECT_ID=
|
||||
GCP_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TIMEOUT_SECONDS=30
|
||||
# Credencial GCP segue padrão Google:
|
||||
# GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-service-account.json
|
||||
|
||||
###############################################################################
|
||||
# OCI Streaming
|
||||
###############################################################################
|
||||
ENABLE_OCI_STREAMING=false
|
||||
OCI_STREAM_ENDPOINT=
|
||||
OCI_STREAM_OCID=
|
||||
OCI_STREAM_PARTITION_KEY=agent-events
|
||||
|
||||
###############################################################################
|
||||
# Guardrails, Judges, Supervisor
|
||||
###############################################################################
|
||||
ENABLE_INPUT_GUARDRAILS=true
|
||||
ENABLE_OUTPUT_GUARDRAILS=true
|
||||
ENABLE_JUDGES=true
|
||||
ENABLE_SUPERVISOR=true
|
||||
ENABLE_OUTPUT_SUPERVISOR=true
|
||||
ENABLE_PARALLEL_GUARDRAILS=true
|
||||
GUARDRAILS_FAIL_FAST=true
|
||||
OUTPUT_SUPERVISOR_MAX_RETRIES=3
|
||||
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
|
||||
JUDGES_CONFIG_PATH=./config/judges.yaml
|
||||
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
|
||||
|
||||
###############################################################################
|
||||
# Gateway de canais
|
||||
###############################################################################
|
||||
DEFAULT_CHANNEL=web
|
||||
# embedded = backend may parse simple/native channel payloads.
|
||||
# external = backend only accepts GatewayRequest normalized by an external Channel Gateway.
|
||||
FRAMEWORK_CHANNEL_INPUT_MODE=embedded
|
||||
ENABLE_VOICE_ADAPTER=true
|
||||
ENABLE_WHATSAPP_ADAPTER=true
|
||||
ENABLE_TEXT_ADAPTER=true
|
||||
|
||||
#################################################
|
||||
# ENTERPRISE ROUTING
|
||||
#################################################
|
||||
# Arquivo YAML com intents, keywords, políticas de estado e fallback.
|
||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
# true = usa LLM para classificar quando keywords/estado não resolverem.
|
||||
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
|
||||
ENABLE_LLM_ROUTER=true
|
||||
|
||||
# Semantic route stickiness (optional).
|
||||
# Uses a lightweight LLM profile to decide only CONTINUE vs ROUTE.
|
||||
# There are no regexes or deterministic language rules.
|
||||
ENABLE_ROUTE_STICKINESS=true
|
||||
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
|
||||
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
|
||||
ROUTE_STICKINESS_HISTORY_TURNS=2
|
||||
ROUTE_STICKINESS_MAX_TOKENS=80
|
||||
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
|
||||
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
|
||||
SESSION_ALREADY_ENDED_MESSAGE=Este atendimento já foi encerrado. Inicie uma nova sessão para continuar.
|
||||
|
||||
###############################################################################
|
||||
# MCP / Tools
|
||||
###############################################################################
|
||||
ENABLE_MCP_TOOLS=true
|
||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||
TOOL_POLICIES_PATH=./config/tool_policies.yaml
|
||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
||||
|
||||
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
||||
ROUTING_MODE=router
|
||||
|
||||
# Usage/cost accounting
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ConversationSummaryMemory / compressão de contexto conversacional
|
||||
# -----------------------------------------------------------------------------
|
||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
||||
MEMORY_CONTEXT_STRATEGY=summary
|
||||
MEMORY_HISTORY_LIMIT=80
|
||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
||||
MEMORY_SUMMARY_USE_LLM=true
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
|
||||
###############################################################################
|
||||
# MCP Gateway
|
||||
###############################################################################
|
||||
# true = framework routes tool calls to the dedicated MCP Gateway.
|
||||
# false = framework calls MCP servers directly from mcp_servers.yaml.
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
# MCP_GATEWAY_TOKEN=
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
|
||||
###############################################################################
|
||||
# LONG-TERM MEMORY
|
||||
###############################################################################
|
||||
ENABLE_LONG_TERM_MEMORY=true
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
||||
# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY
|
||||
# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
||||
@@ -3,5 +3,8 @@ from __future__ import annotations
|
||||
# Compatibilidade local do template/backend.
|
||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||
from app.presentation import register_tool_renderers
|
||||
|
||||
register_tool_renderers()
|
||||
|
||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .tool_renderers import register_tool_renderers
|
||||
|
||||
__all__ = ["register_tool_renderers"]
|
||||
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.presentation import register_tool_response_renderer
|
||||
|
||||
|
||||
def _money_brl(value: Any) -> str:
|
||||
try:
|
||||
return f"{float(value):.2f}".replace(".", ",")
|
||||
except (TypeError, ValueError):
|
||||
return str(value)
|
||||
|
||||
|
||||
def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
return f"[{agent_label}] Fatura consultada: {result}."
|
||||
|
||||
|
||||
def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
plano = result.get("plano")
|
||||
if plano is None:
|
||||
return None
|
||||
parts = [f"[{agent_label}] Seu plano é {plano}"]
|
||||
internet_gb = result.get("internet_gb")
|
||||
status = result.get("status")
|
||||
if internet_gb is not None:
|
||||
parts.append(f"com {internet_gb} GB")
|
||||
if status is not None:
|
||||
parts.append(f"status {status}")
|
||||
return ", ".join(parts) + "."
|
||||
|
||||
|
||||
def render_retail_order(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
status = result.get("status")
|
||||
if order_id is None or status is None:
|
||||
return None
|
||||
lines = [f"[{agent_label}] Pedido {order_id}: status {status}."]
|
||||
total = result.get("valor_total")
|
||||
if total is not None:
|
||||
lines.append(f"Valor total: R$ {_money_brl(total)}.")
|
||||
items = result.get("itens") or []
|
||||
rendered_items: list[str] = []
|
||||
if isinstance(items, list):
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
value = item.get("descricao") or item.get("nome") or item.get("sku")
|
||||
else:
|
||||
value = item
|
||||
if value not in (None, ""):
|
||||
rendered_items.append(str(value))
|
||||
if rendered_items:
|
||||
lines.append("Itens: " + "; ".join(rendered_items) + ".")
|
||||
return " ".join(lines)
|
||||
|
||||
|
||||
def render_retail_delivery(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None:
|
||||
order_id = result.get("order_id")
|
||||
transportadora = result.get("transportadora")
|
||||
codigo = result.get("codigo_rastreio")
|
||||
previsao = result.get("previsao_entrega")
|
||||
if any(v is None for v in (order_id, transportadora, codigo, previsao)):
|
||||
return None
|
||||
return (
|
||||
f"[{agent_label}] Entrega do pedido {order_id}: transportadora {transportadora}, "
|
||||
f"rastreio {codigo}, previsão {previsao}."
|
||||
)
|
||||
|
||||
|
||||
def register_tool_renderers() -> None:
|
||||
register_tool_response_renderer("telecom.invoice", render_telecom_invoice)
|
||||
register_tool_response_renderer("telecom.plan", render_telecom_plan)
|
||||
register_tool_response_renderer("retail.order", render_retail_order)
|
||||
register_tool_response_renderer("retail.delivery", render_retail_delivery)
|
||||
@@ -26,6 +26,8 @@ class AgentState(TypedDict, total=False):
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
active_transaction: dict[str, Any]
|
||||
last_transaction: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
|
||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
cancelar_pedido:
|
||||
map:
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente pelo usuário. Retorne null quando não houver identificador de pedido na mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
|
||||
solicitar_troca:
|
||||
map:
|
||||
session_key: session_id
|
||||
|
||||
@@ -79,6 +79,25 @@ intents:
|
||||
- Quero saber sobre meu pacote de internet.
|
||||
- Tenho roaming internacional?
|
||||
|
||||
|
||||
- name: retail_order_cancel
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
description: Cancelamento explícito de pedido ou compra.
|
||||
priority: 20
|
||||
mcp_tools:
|
||||
- consultar_pedido
|
||||
- cancelar_pedido
|
||||
keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar a compra
|
||||
- cancelar compra
|
||||
examples:
|
||||
- Quero cancelar meu pedido.
|
||||
- Cancele o pedido.
|
||||
- Quero cancelar a compra.
|
||||
|
||||
- name: retail_order_tracking
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
|
||||
@@ -7,6 +7,12 @@ defaults:
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_pedido:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id]
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
@@ -10,6 +10,9 @@ tools:
|
||||
- fatura
|
||||
- conta
|
||||
- boleto
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.invoice
|
||||
consultar_pagamentos:
|
||||
description: Consulta histórico de pagamentos do cliente.
|
||||
mcp_server: telecom
|
||||
@@ -28,6 +31,9 @@ tools:
|
||||
asset_id: string
|
||||
selection_keywords:
|
||||
- plano
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: telecom.plan
|
||||
listar_servicos:
|
||||
description: Lista serviços ativos e adicionais VAS.
|
||||
mcp_server: telecom
|
||||
@@ -49,6 +55,9 @@ tools:
|
||||
- consultar pedido
|
||||
- status do pedido
|
||||
- pedido
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.order
|
||||
consultar_entrega:
|
||||
description: Consulta entrega e rastreamento do pedido.
|
||||
mcp_server: retail
|
||||
@@ -61,6 +70,26 @@ tools:
|
||||
- rastreamento
|
||||
- transportadora
|
||||
- previsão
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: retail.delivery
|
||||
cancelar_pedido:
|
||||
description: Simula o cancelamento de um pedido de varejo.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires:
|
||||
- order_id
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
selection_keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar compra
|
||||
- cancelar a compra
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
|
||||
@@ -78,3 +78,10 @@ profiles:
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
|
||||
transaction_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
timeout_seconds: 8
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
###############################################################################
|
||||
# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA
|
||||
# Este arquivo é lido por Pydantic Settings no framework e no backend template.
|
||||
###############################################################################
|
||||
|
||||
APP_NAME=ai-agent-template
|
||||
APP_ENV=local
|
||||
LOG_LEVEL=INFO
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
|
||||
###############################################################################
|
||||
# LLM - OCI Generative AI como provider principal
|
||||
###############################################################################
|
||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
||||
LLM_PROVIDER=oci_openai
|
||||
LLM_TEMPERATURE=0.2
|
||||
LLM_MAX_TOKENS=2048
|
||||
LLM_TIMEOUT_SECONDS=120
|
||||
|
||||
# OCI OpenAI-compatible endpoint
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6
|
||||
OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
# OCI SDK / signer / profiles
|
||||
OCI_CONFIG_FILE=~/.oci/config
|
||||
OCI_PROFILE=DEFAULT
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
OCI_REGION=us-chicago-1
|
||||
|
||||
###############################################################################
|
||||
# Persistência
|
||||
###############################################################################
|
||||
# Opções: memory, autonomous, mongodb
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
SQLITE_DB_PATH=./data/agent_framework.db
|
||||
|
||||
# Autonomous Database
|
||||
ADB_USER=admin
|
||||
ADB_PASSWORD=fjhsdf04954hf
|
||||
ADB_DSN=oradb23aidev_high
|
||||
ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev
|
||||
ADB_WALLET_PASSWORD=fjhsdf04954hf
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
|
||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
||||
MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
|
||||
MONGODB_DATABASE=agent_platform
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
ENABLE_REDIS_CACHE=false
|
||||
|
||||
###############################################################################
|
||||
# RAG / Vector / Graph
|
||||
###############################################################################
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
GRAPH_STORE_PROVIDER=sqlite
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=mock
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
|
||||
###############################################################################
|
||||
# Observabilidade
|
||||
###############################################################################
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_TRACE_MODE=compact # Opcional: verbose, compact
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-2f9da109-5b0f-4c78-b61d-9598ed787eba
|
||||
LANGFUSE_SECRET_KEY=sk-lf-a4cb0cdd-f2ea-4468-9911-cebeb91ba944
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
|
||||
###############################################################################
|
||||
# Analytics / Observer corporativo
|
||||
###############################################################################
|
||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
||||
ENABLE_ANALYTICS=false
|
||||
# Providers aceitos: oci_streaming,pubsub,noop
|
||||
ANALYTICS_PROVIDERS=pubsub
|
||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
||||
AGENT_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TOPIC_PATH=
|
||||
GCP_PROJECT_ID=
|
||||
GCP_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TIMEOUT_SECONDS=30
|
||||
# Credencial GCP segue padrão Google:
|
||||
# GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-service-account.json
|
||||
|
||||
###############################################################################
|
||||
# OCI Streaming
|
||||
###############################################################################
|
||||
ENABLE_OCI_STREAMING=false
|
||||
OCI_STREAM_ENDPOINT=
|
||||
OCI_STREAM_OCID=
|
||||
OCI_STREAM_PARTITION_KEY=agent-events
|
||||
|
||||
###############################################################################
|
||||
# Guardrails, Judges, Supervisor
|
||||
###############################################################################
|
||||
ENABLE_INPUT_GUARDRAILS=true
|
||||
ENABLE_OUTPUT_GUARDRAILS=true
|
||||
ENABLE_JUDGES=true
|
||||
ENABLE_SUPERVISOR=true
|
||||
ENABLE_OUTPUT_SUPERVISOR=true
|
||||
ENABLE_PARALLEL_GUARDRAILS=true
|
||||
GUARDRAILS_FAIL_FAST=true
|
||||
OUTPUT_SUPERVISOR_MAX_RETRIES=3
|
||||
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
|
||||
JUDGES_CONFIG_PATH=./config/judges.yaml
|
||||
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
|
||||
|
||||
###############################################################################
|
||||
# Gateway de canais
|
||||
###############################################################################
|
||||
DEFAULT_CHANNEL=web
|
||||
ENABLE_VOICE_ADAPTER=true
|
||||
ENABLE_WHATSAPP_ADAPTER=true
|
||||
ENABLE_TEXT_ADAPTER=true
|
||||
|
||||
#################################################
|
||||
# ENTERPRISE ROUTING
|
||||
#################################################
|
||||
# Arquivo YAML com intents, keywords, políticas de estado e fallback.
|
||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
# true = usa LLM para classificar quando keywords/estado não resolverem.
|
||||
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
|
||||
ENABLE_LLM_ROUTER=true
|
||||
|
||||
# Continuidade semântica, handoff humano e encerramento global.
|
||||
ENABLE_ROUTE_STICKINESS=true
|
||||
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
|
||||
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
|
||||
ROUTE_STICKINESS_HISTORY_TURNS=2
|
||||
ROUTE_STICKINESS_MAX_TOKENS=80
|
||||
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
|
||||
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
|
||||
SESSION_ALREADY_ENDED_MESSAGE=Este atendimento já foi encerrado. Inicie uma nova sessão para continuar.
|
||||
|
||||
###############################################################################
|
||||
# MCP / Tools
|
||||
###############################################################################
|
||||
ENABLE_MCP_TOOLS=true
|
||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||
TOOL_POLICIES_PATH=./config/tool_policies.yaml
|
||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
||||
|
||||
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
||||
ROUTING_MODE=router
|
||||
|
||||
# Usage/cost accounting
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ConversationSummaryMemory / compressão de contexto conversacional
|
||||
# -----------------------------------------------------------------------------
|
||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
||||
MEMORY_CONTEXT_STRATEGY=summary
|
||||
MEMORY_HISTORY_LIMIT=80
|
||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
||||
MEMORY_SUMMARY_USE_LLM=true
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
|
||||
###############################################################################
|
||||
# MCP Gateway
|
||||
###############################################################################
|
||||
# true = framework routes tool calls to the dedicated MCP Gateway.
|
||||
# false = framework calls MCP servers directly from mcp_servers.yaml.
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
# MCP_GATEWAY_TOKEN=
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
|
||||
###############################################################################
|
||||
# LONG-TERM MEMORY
|
||||
###############################################################################
|
||||
ENABLE_LONG_TERM_MEMORY=true
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
||||
# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY
|
||||
# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
||||
@@ -26,6 +26,8 @@ class AgentState(TypedDict, total=False):
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
active_transaction: dict[str, Any]
|
||||
last_transaction: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
|
||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
cancelar_pedido:
|
||||
map:
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente pelo usuário. Retorne null quando não houver identificador de pedido na mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
|
||||
solicitar_troca:
|
||||
map:
|
||||
session_key: session_id
|
||||
|
||||
@@ -79,6 +79,25 @@ intents:
|
||||
- Quero saber sobre meu pacote de internet.
|
||||
- Tenho roaming internacional?
|
||||
|
||||
|
||||
- name: retail_order_cancel
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
description: Cancelamento explícito de pedido ou compra.
|
||||
priority: 20
|
||||
mcp_tools:
|
||||
- consultar_pedido
|
||||
- cancelar_pedido
|
||||
keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar a compra
|
||||
- cancelar compra
|
||||
examples:
|
||||
- Quero cancelar meu pedido.
|
||||
- Cancele o pedido.
|
||||
- Quero cancelar a compra.
|
||||
|
||||
- name: retail_order_tracking
|
||||
domain: retail
|
||||
agent: orders_agent
|
||||
|
||||
@@ -7,6 +7,12 @@ defaults:
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_pedido:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id]
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
@@ -61,6 +61,23 @@ tools:
|
||||
- rastreamento
|
||||
- transportadora
|
||||
- previsão
|
||||
cancelar_pedido:
|
||||
description: Simula o cancelamento de um pedido de varejo.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires:
|
||||
- order_id
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
selection_keywords:
|
||||
- cancelar pedido
|
||||
- cancelamento do pedido
|
||||
- cancelar compra
|
||||
- cancelar a compra
|
||||
|
||||
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
|
||||
@@ -78,3 +78,10 @@ profiles:
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
|
||||
transaction_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
timeout_seconds: 8
|
||||
|
||||
172
Tuning-Performance/Transaction_Evidence/README.md
Normal file
172
Tuning-Performance/Transaction_Evidence/README.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Transaction Evidence / Evidência Operacional de Transação
|
||||
|
||||
Esta variante demonstra a funcionalidade **Transaction Evidence** do `agent_framework_oci`.
|
||||
Ela preserva o resultado estruturado de transações executadas no estado/checkpoint do
|
||||
LangGraph e o reutiliza, quando relevante, em turnos posteriores como evidência
|
||||
operacional para composição de resposta e groundedness.
|
||||
|
||||
> Transaction Evidence **não é Long Term Memory**. É estado operacional associado à
|
||||
> sessão/workflow e aos recursos envolvidos na transação.
|
||||
|
||||
## Problema que resolve
|
||||
|
||||
Sem esta funcionalidade, uma transação pode ocorrer corretamente em um turno:
|
||||
|
||||
```text
|
||||
quero cancelar pedido
|
||||
PED-1001
|
||||
sim
|
||||
→ protocolo CANCEL-2026-001
|
||||
```
|
||||
|
||||
mas, no turno seguinte:
|
||||
|
||||
```text
|
||||
quero meu pedido
|
||||
```
|
||||
|
||||
o runtime pode consultar apenas `consultar_pedido`. Se a resposta mencionar o
|
||||
cancelamento/protocolo visto no turno anterior, o judge de groundedness pode tratar
|
||||
isso como informação não evidenciada.
|
||||
|
||||
Com Transaction Evidence, o resultado transacional confirmado fica estruturado em
|
||||
`transaction_evidence` e pode aparecer como `relevant_transaction_evidence` quando
|
||||
correlacionado ao recurso atual.
|
||||
|
||||
## Fluxo demonstrado
|
||||
|
||||
```text
|
||||
cancelar_pedido(PED-1001)
|
||||
↓
|
||||
transação executada
|
||||
↓
|
||||
transaction_evidence
|
||||
- tool_name
|
||||
- arguments
|
||||
- transaction_id
|
||||
- status
|
||||
- result/protocolo
|
||||
↓
|
||||
checkpoint da sessão
|
||||
↓
|
||||
novo turno: "quero meu pedido"
|
||||
↓
|
||||
consultar_pedido(PED-1001)
|
||||
+
|
||||
relevant_transaction_evidence(PED-1001)
|
||||
↓
|
||||
resposta + groundedness com evidência operacional
|
||||
```
|
||||
|
||||
## Regras de segurança e correlação
|
||||
|
||||
A implementação do framework:
|
||||
|
||||
- registra apenas resultados transacionais efetivamente executados;
|
||||
- mantém histórico bounded das evidências mais recentes;
|
||||
- correlaciona por identificadores de recurso, como `order_id`, `invoice_id`,
|
||||
`asset_id`, `resource_key` e outros campos `*_id`;
|
||||
- evita reutilizar evidência de um recurso diferente;
|
||||
- disponibiliza somente a evidência relevante do turno em
|
||||
`relevant_transaction_evidence`;
|
||||
- expõe a evidência relevante no metadata da resposta para diagnóstico;
|
||||
- inclui a evidência relevante no contexto usado pelos judges/groundedness.
|
||||
|
||||
## Configuração
|
||||
|
||||
Não existe uma variável obrigatória `ENABLE_TRANSACTION_EVIDENCE` nesta versão.
|
||||
A funcionalidade faz parte do runtime transacional do framework e depende da
|
||||
persistência de estado/checkpoint já usada pelo agente.
|
||||
|
||||
Os principais pontos do exemplo são:
|
||||
|
||||
- `app/state.py`: campos `transaction_evidence`, `last_transaction_evidence` e
|
||||
`relevant_transaction_evidence`;
|
||||
- `app/workflows/agent_graph.py`: injeta evidência relevante no contexto dos judges;
|
||||
- `app/main.py`: publica `transaction_evidence` no metadata da resposta;
|
||||
- framework: `runtime/agent_runtime.py`: grava, correlaciona e materializa a evidência.
|
||||
|
||||
## Executar
|
||||
|
||||
```bash
|
||||
cd Tuning-Performance/Transaction_Evidence/agent_template_backend
|
||||
pip install -e ../../../libs/agent_framework
|
||||
pip install -r requirements.txt
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
```
|
||||
|
||||
Use o frontend normalmente apontando para `http://localhost:8000`.
|
||||
|
||||
## Cenário de teste recomendado
|
||||
|
||||
Execute na mesma sessão:
|
||||
|
||||
```text
|
||||
quero meu pedido
|
||||
quero cancelar pedido
|
||||
PED-1001
|
||||
sim
|
||||
quero meu pedido
|
||||
```
|
||||
|
||||
Na última rodada, valide no JSON de resposta:
|
||||
|
||||
```json
|
||||
{
|
||||
"route": "orders_agent",
|
||||
"intent": "retail_order_tracking",
|
||||
"transaction_evidence": [
|
||||
{
|
||||
"tool_name": "cancelar_pedido",
|
||||
"status": "COMPLETED"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
A estrutura exata do `result` depende da tool transacional, mas o protocolo retornado
|
||||
pela transação deve estar presente na evidência relevante quando o recurso correlacionar.
|
||||
|
||||
## Resultado esperado
|
||||
|
||||
No último `quero meu pedido`:
|
||||
|
||||
1. a intent deve ser `retail_order_tracking`;
|
||||
2. `consultar_pedido` deve ser executada normalmente;
|
||||
3. a evidência da transação anterior de `PED-1001` deve ser recuperada;
|
||||
4. a resposta pode mencionar o cancelamento/protocolo porque essa informação agora
|
||||
está suportada por evidência operacional;
|
||||
5. o judge de groundedness deve receber essa mesma evidência.
|
||||
|
||||
## Diferença para Long Term Memory
|
||||
|
||||
| Transaction Evidence | Long Term Memory |
|
||||
|---|---|
|
||||
| Estado operacional | Memória semântica/pessoal |
|
||||
| Resultado de tools/transações | Preferências e fatos duráveis |
|
||||
| Correlacionado a recursos | Correlacionado ao sujeito |
|
||||
| Usado para grounding operacional | Usado para personalização/contexto |
|
||||
| Curto/médio prazo de workflow | Longo prazo entre conversas |
|
||||
|
||||
---
|
||||
|
||||
## English summary
|
||||
|
||||
This variant demonstrates **Transaction Evidence**: executed transactional tool
|
||||
outcomes are persisted as structured operational evidence in LangGraph state/checkpoint.
|
||||
On later turns, the runtime correlates prior evidence with the current resource and
|
||||
exposes it as `relevant_transaction_evidence` to both response composition and
|
||||
judges/groundedness. It is operational workflow state, not Long Term Memory.
|
||||
|
||||
Recommended test sequence:
|
||||
|
||||
```text
|
||||
show my order
|
||||
cancel my order
|
||||
PED-1001
|
||||
yes
|
||||
show my order
|
||||
```
|
||||
|
||||
On the last turn, verify that the response metadata contains relevant transaction
|
||||
evidence for the cancellation of `PED-1001`.
|
||||
@@ -0,0 +1,207 @@
|
||||
###############################################################################
|
||||
# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA
|
||||
# Este arquivo é lido por Pydantic Settings no framework e no backend template.
|
||||
###############################################################################
|
||||
|
||||
APP_NAME=ai-agent-template
|
||||
APP_ENV=local
|
||||
LOG_LEVEL=INFO
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
|
||||
###############################################################################
|
||||
# LLM - OCI Generative AI como provider principal
|
||||
###############################################################################
|
||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
||||
LLM_PROVIDER=oci_sdk
|
||||
LLM_TEMPERATURE=0.2
|
||||
LLM_MAX_TOKENS=2048
|
||||
LLM_TIMEOUT_SECONDS=120
|
||||
|
||||
# OCI OpenAI-compatible endpoint
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS
|
||||
OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
#OCI_GENAI_BASE_URL=https://pegruagntaiatenddev.pe.inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
|
||||
#OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
#OCI_GENAI_API_KEY=
|
||||
#OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
|
||||
# OCI_AUTH_MODE=config_file|instance_principal|resource_principal
|
||||
OCI_AUTH_MODE=config_file
|
||||
# OCI SDK / signer / profiles
|
||||
OCI_CONFIG_FILE=~/.oci/config
|
||||
OCI_PROFILE=LATINOAMERICA-Chicago
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q
|
||||
OCI_REGION=us-chicago-1
|
||||
|
||||
###############################################################################
|
||||
# Persistência
|
||||
###############################################################################
|
||||
# Opções: memory, autonomous, mongodb
|
||||
SESSION_REPOSITORY_PROVIDER=autonomous
|
||||
MEMORY_REPOSITORY_PROVIDER=autonomous
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
|
||||
|
||||
# Autonomous Database
|
||||
ADB_USER=admin
|
||||
ADB_PASSWORD=Moniquinha19721972
|
||||
ADB_DSN=oradb23ai_high
|
||||
ADB_WALLET_LOCATION=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai
|
||||
ADB_WALLET_PASSWORD=Moniquinha1972
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
|
||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
||||
MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
|
||||
MONGODB_DATABASE=agent_platform
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
ENABLE_REDIS_CACHE=false
|
||||
|
||||
###############################################################################
|
||||
# RAG / Vector / Graph
|
||||
###############################################################################
|
||||
VECTOR_STORE_PROVIDER=autonomous
|
||||
GRAPH_STORE_PROVIDER=autonomous
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=oci
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
|
||||
###############################################################################
|
||||
# Observabilidade
|
||||
###############################################################################
|
||||
ENABLE_LANGFUSE=true
|
||||
# Opcional: verbose, compact
|
||||
LANGFUSE_TRACE_MODE=compact
|
||||
# Nome customizado do trace pai, ex.: backoffice.checklist.workflow ou backoffice.emulador.workflow
|
||||
LANGFUSE_COMPACT_VISIBLE_EVENT_PREFIXES=AGA.,NOC., IC.
|
||||
LANGFUSE_COMPACT_SUPPRESSED_PREFIXES=llm.chat_completion
|
||||
LANGFUSE_IGNORE_HEALTHCHECKS=true
|
||||
LANGFUSE_IGNORED_PATHS=/health,/ready,/metrics
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-4a1e3921-5158-4fd3-a16d-7a77549fb312
|
||||
LANGFUSE_SECRET_KEY=sk-lf-efc6fd59-c5ec-4858-b6ec-4aa129734915
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
|
||||
|
||||
###############################################################################
|
||||
# Analytics / Observer corporativo
|
||||
###############################################################################
|
||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
||||
ENABLE_ANALYTICS=false
|
||||
# Providers aceitos: oci_streaming,pubsub,noop
|
||||
ANALYTICS_PROVIDERS=oci_streaming
|
||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
||||
AGENT_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TOPIC_PATH=
|
||||
GCP_PROJECT_ID=
|
||||
GCP_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TIMEOUT_SECONDS=30
|
||||
# Credencial GCP segue padrão Google:
|
||||
# GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-service-account.json
|
||||
|
||||
###############################################################################
|
||||
# OCI Streaming
|
||||
###############################################################################
|
||||
ENABLE_OCI_STREAMING=false
|
||||
OCI_STREAM_ENDPOINT=
|
||||
OCI_STREAM_OCID=
|
||||
OCI_STREAM_PARTITION_KEY=agent-events
|
||||
|
||||
###############################################################################
|
||||
# Guardrails, Judges, Supervisor
|
||||
###############################################################################
|
||||
ENABLE_INPUT_GUARDRAILS=true
|
||||
ENABLE_OUTPUT_GUARDRAILS=true
|
||||
ENABLE_JUDGES=true
|
||||
ENABLE_SUPERVISOR=true
|
||||
ENABLE_OUTPUT_SUPERVISOR=true
|
||||
ENABLE_PARALLEL_GUARDRAILS=true
|
||||
GUARDRAILS_FAIL_FAST=true
|
||||
OUTPUT_SUPERVISOR_MAX_RETRIES=3
|
||||
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
|
||||
JUDGES_CONFIG_PATH=./config/judges.yaml
|
||||
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
|
||||
|
||||
###############################################################################
|
||||
# Gateway de canais
|
||||
###############################################################################
|
||||
DEFAULT_CHANNEL=web
|
||||
# embedded = backend may parse simple/native channel payloads.
|
||||
# external = backend only accepts GatewayRequest normalized by an external Channel Gateway.
|
||||
FRAMEWORK_CHANNEL_INPUT_MODE=embedded
|
||||
ENABLE_VOICE_ADAPTER=true
|
||||
ENABLE_WHATSAPP_ADAPTER=true
|
||||
ENABLE_TEXT_ADAPTER=true
|
||||
|
||||
#################################################
|
||||
# ENTERPRISE ROUTING
|
||||
#################################################
|
||||
# Arquivo YAML com intents, keywords, políticas de estado e fallback.
|
||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
# true = usa LLM para classificar quando keywords/estado não resolverem.
|
||||
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
|
||||
ENABLE_LLM_ROUTER=true
|
||||
|
||||
# Semantic route stickiness (optional).
|
||||
# Uses a lightweight LLM profile to decide only CONTINUE vs ROUTE.
|
||||
# There are no regexes or deterministic language rules.
|
||||
ENABLE_ROUTE_STICKINESS=true
|
||||
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
|
||||
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
|
||||
ROUTE_STICKINESS_HISTORY_TURNS=2
|
||||
ROUTE_STICKINESS_MAX_TOKENS=80
|
||||
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
|
||||
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
|
||||
|
||||
###############################################################################
|
||||
# MCP / Tools
|
||||
###############################################################################
|
||||
ENABLE_MCP_TOOLS=true
|
||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
||||
|
||||
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
||||
ROUTING_MODE=router
|
||||
|
||||
# Usage/cost accounting
|
||||
USAGE_REPOSITORY_PROVIDER=autonomous
|
||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ConversationSummaryMemory / compressão de contexto conversacional
|
||||
# -----------------------------------------------------------------------------
|
||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
||||
MEMORY_CONTEXT_STRATEGY=summary
|
||||
MEMORY_HISTORY_LIMIT=80
|
||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
||||
MEMORY_SUMMARY_USE_LLM=true
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
|
||||
###############################################################################
|
||||
# LONG-TERM MEMORY
|
||||
###############################################################################
|
||||
ENABLE_LONG_TERM_MEMORY=true
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
||||
# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY
|
||||
# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
||||
@@ -0,0 +1,6 @@
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY agent_framework /agent_framework
|
||||
COPY agent_template_backend /app
|
||||
RUN pip install --no-cache-dir -e /agent_framework -r requirements.txt
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
# Agent Template Backend Enterprise
|
||||
|
||||
Este folder é uma cópia completa do `agent_template_backend`, sem cortes de
|
||||
arquitetura. Ele mantém workflow, router, output supervisor, guardrails,
|
||||
analytics, observer, MCP, memória, checkpoints e configurações.
|
||||
|
||||
A diferença é que a lógica de negócio dos agentes de exemplo foi removida da
|
||||
execução e preservada comentada nos próprios arquivos:
|
||||
|
||||
- `app/agents/billing_agent.py`
|
||||
- `app/agents/product_agent.py`
|
||||
- `app/agents/orders_agent.py`
|
||||
- `app/agents/support_agent.py`
|
||||
|
||||
## O que o desenvolvedor deve alterar
|
||||
|
||||
1. Escolher ou criar um agente em `app/agents/`.
|
||||
2. Implementar o método `run()`.
|
||||
3. Ajustar prompts e tools, se necessário.
|
||||
4. Emitir ICs de negócio relevantes para a jornada.
|
||||
5. Manter NOC/GRL nos pontos operacionais e de guardrails.
|
||||
|
||||
## O que já está integrado
|
||||
|
||||
- `AgentObserver`
|
||||
- `observer.emit_ic()`
|
||||
- `observer.emit_noc()`
|
||||
- `observer.emit_grl()`
|
||||
- `AnalyticsPublisher`
|
||||
- OCI Streaming
|
||||
- GCP Pub/Sub
|
||||
- OutputSupervisor
|
||||
- GuardrailPipeline com suporte a execução paralela/fail-fast no framework
|
||||
- MCP Tool Router
|
||||
- LangGraph
|
||||
- Memory
|
||||
- Checkpoint
|
||||
- Langfuse / OpenTelemetry
|
||||
|
||||
## Exemplos adicionados
|
||||
|
||||
Veja `app/examples/`:
|
||||
|
||||
- `ic_examples.py`
|
||||
- `noc_examples.py`
|
||||
- `grl_examples.py`
|
||||
- `mcp_examples.py`
|
||||
- `observer_examples.py`
|
||||
|
||||
## Convenção rápida
|
||||
|
||||
- IC = evento de negócio / curadoria / informacional.
|
||||
- NOC = evento operacional / saúde técnica.
|
||||
- GRL = evento de guardrail / segurança / validação.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Agentes do Template Backend Enterprise
|
||||
|
||||
Os arquivos desta pasta preservam a estrutura real esperada pelo workflow, mas
|
||||
não executam lógica de negócio pronta.
|
||||
|
||||
Cada agente mostra:
|
||||
|
||||
- como emitir IC;
|
||||
- como emitir NOC;
|
||||
- como emitir GRL;
|
||||
- como coletar MCP via `_collect_tool_context()`;
|
||||
- como recuperar RAG via `_retrieve_rag_context()`;
|
||||
- onde chamar LLM/cache.
|
||||
|
||||
A implementação original do exemplo está comentada no fim de cada arquivo.
|
||||
@@ -0,0 +1,129 @@
|
||||
from app.agents.prompting import apply_agent_profile_prompt
|
||||
from app.agents.runtime import AgentRuntimeMixin
|
||||
|
||||
|
||||
class BillingAgent(AgentRuntimeMixin):
|
||||
name = "billingAgent"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
telemetry=None,
|
||||
tool_router=None,
|
||||
rag_service=None,
|
||||
cache=None,
|
||||
settings=None,
|
||||
observer=None,
|
||||
memory=None,
|
||||
summary_memory=None,
|
||||
):
|
||||
self.llm = llm
|
||||
self.telemetry = telemetry
|
||||
self.tool_router = tool_router
|
||||
self.rag_service = rag_service
|
||||
self.cache = cache
|
||||
self.settings = settings
|
||||
self.observer = observer
|
||||
self.memory = memory
|
||||
self.summary_memory = summary_memory
|
||||
|
||||
async def run(self, state):
|
||||
await self._emit_ic(
|
||||
"IC.BILLING_AGENT_STARTED",
|
||||
state,
|
||||
{"business_component": "faturas"},
|
||||
component="agent.billing.start",
|
||||
)
|
||||
|
||||
tool_context = await self._collect_tool_context(state)
|
||||
if tool_context:
|
||||
await self._emit_ic(
|
||||
"IC.BILLING_MCP_CONTEXT_COLLECTED",
|
||||
state,
|
||||
{"tool_result_count": len(tool_context)},
|
||||
component="agent.billing.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="BillingAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
"IC.BILLING_RAG_CONTEXT_RETRIEVED",
|
||||
state,
|
||||
{
|
||||
"document_count": rag_metadata.get("document_count"),
|
||||
"graph_neighbors": rag_metadata.get("graph_neighbors"),
|
||||
"latency_ms": rag_metadata.get("latency_ms"),
|
||||
},
|
||||
component="agent.billing.rag",
|
||||
)
|
||||
|
||||
# Prepara ConversationSummaryMemory antes de montar o prompt.
|
||||
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=apply_agent_profile_prompt(
|
||||
state,
|
||||
"Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.",
|
||||
),
|
||||
mcp_results=tool_context,
|
||||
rag_context=rag_context,
|
||||
rag_metadata=rag_metadata,
|
||||
)
|
||||
|
||||
answer = await self._invoke_llm_cached(state, "BillingAgent", messages)
|
||||
result = {
|
||||
"answer": f"[BillingAgent] {answer}",
|
||||
"next_state": "BILLING_ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
"IC.BILLING_AGENT_COMPLETED",
|
||||
state,
|
||||
{
|
||||
"answer_chars": len(result.get("answer") or ""),
|
||||
"has_mcp_results": bool(tool_context),
|
||||
"rag_enabled": bool(rag_metadata.get("enabled")),
|
||||
"memory_context": state.get("memory_context_metadata"),
|
||||
},
|
||||
component="agent.billing.completed",
|
||||
)
|
||||
return result
|
||||
|
||||
async def _collect_tool_context(self, state):
|
||||
return await self._collect_mcp_context(state)
|
||||
@@ -0,0 +1,129 @@
|
||||
from app.agents.prompting import apply_agent_profile_prompt
|
||||
from app.agents.runtime import AgentRuntimeMixin
|
||||
|
||||
|
||||
class OrdersAgent(AgentRuntimeMixin):
|
||||
name = "orders_agent"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
telemetry=None,
|
||||
tool_router=None,
|
||||
rag_service=None,
|
||||
cache=None,
|
||||
settings=None,
|
||||
observer=None,
|
||||
memory=None,
|
||||
summary_memory=None,
|
||||
):
|
||||
self.llm = llm
|
||||
self.telemetry = telemetry
|
||||
self.tool_router = tool_router
|
||||
self.rag_service = rag_service
|
||||
self.cache = cache
|
||||
self.settings = settings
|
||||
self.observer = observer
|
||||
self.memory = memory
|
||||
self.summary_memory = summary_memory
|
||||
|
||||
async def run(self, state):
|
||||
await self._emit_ic(
|
||||
"IC.ORDERS_AGENT_STARTED",
|
||||
state,
|
||||
{"business_component": "pedidos"},
|
||||
component="agent.orders.start",
|
||||
)
|
||||
|
||||
tool_context = await self._collect_tool_context(state)
|
||||
if tool_context:
|
||||
await self._emit_ic(
|
||||
"IC.ORDERS_MCP_CONTEXT_COLLECTED",
|
||||
state,
|
||||
{"tool_result_count": len(tool_context)},
|
||||
component="agent.orders.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="OrdersAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
"IC.ORDERS_RAG_CONTEXT_RETRIEVED",
|
||||
state,
|
||||
{
|
||||
"document_count": rag_metadata.get("document_count"),
|
||||
"graph_neighbors": rag_metadata.get("graph_neighbors"),
|
||||
"latency_ms": rag_metadata.get("latency_ms"),
|
||||
},
|
||||
component="agent.orders.rag",
|
||||
)
|
||||
|
||||
# Prepara ConversationSummaryMemory antes de montar o prompt.
|
||||
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=apply_agent_profile_prompt(
|
||||
state,
|
||||
"Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.",
|
||||
),
|
||||
mcp_results=tool_context,
|
||||
rag_context=rag_context,
|
||||
rag_metadata=rag_metadata,
|
||||
)
|
||||
|
||||
answer = await self._invoke_llm_cached(state, "OrdersAgent", messages)
|
||||
result = {
|
||||
"answer": f"[OrdersAgent] {answer}",
|
||||
"next_state": "ORDER_ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
"IC.ORDERS_AGENT_COMPLETED",
|
||||
state,
|
||||
{
|
||||
"answer_chars": len(result.get("answer") or ""),
|
||||
"has_mcp_results": bool(tool_context),
|
||||
"rag_enabled": bool(rag_metadata.get("enabled")),
|
||||
"memory_context": state.get("memory_context_metadata"),
|
||||
},
|
||||
component="agent.orders.completed",
|
||||
)
|
||||
return result
|
||||
|
||||
async def _collect_tool_context(self, state):
|
||||
return await self._collect_mcp_context(state)
|
||||
@@ -0,0 +1,129 @@
|
||||
from app.agents.prompting import apply_agent_profile_prompt
|
||||
from app.agents.runtime import AgentRuntimeMixin
|
||||
|
||||
|
||||
class ProductAgent(AgentRuntimeMixin):
|
||||
name = "productAgent"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
telemetry=None,
|
||||
tool_router=None,
|
||||
rag_service=None,
|
||||
cache=None,
|
||||
settings=None,
|
||||
observer=None,
|
||||
memory=None,
|
||||
summary_memory=None,
|
||||
):
|
||||
self.llm = llm
|
||||
self.telemetry = telemetry
|
||||
self.tool_router = tool_router
|
||||
self.rag_service = rag_service
|
||||
self.cache = cache
|
||||
self.settings = settings
|
||||
self.observer = observer
|
||||
self.memory = memory
|
||||
self.summary_memory = summary_memory
|
||||
|
||||
async def run(self, state):
|
||||
await self._emit_ic(
|
||||
"IC.PRODUCT_AGENT_STARTED",
|
||||
state,
|
||||
{"business_component": "produtos"},
|
||||
component="agent.product.start",
|
||||
)
|
||||
|
||||
tool_context = await self._collect_tool_context(state)
|
||||
if tool_context:
|
||||
await self._emit_ic(
|
||||
"IC.PRODUCT_MCP_CONTEXT_COLLECTED",
|
||||
state,
|
||||
{"tool_result_count": len(tool_context)},
|
||||
component="agent.product.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="ProductAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
"IC.PRODUCT_RAG_CONTEXT_RETRIEVED",
|
||||
state,
|
||||
{
|
||||
"document_count": rag_metadata.get("document_count"),
|
||||
"graph_neighbors": rag_metadata.get("graph_neighbors"),
|
||||
"latency_ms": rag_metadata.get("latency_ms"),
|
||||
},
|
||||
component="agent.product.rag",
|
||||
)
|
||||
|
||||
# Prepara ConversationSummaryMemory antes de montar o prompt.
|
||||
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=apply_agent_profile_prompt(
|
||||
state,
|
||||
"Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.",
|
||||
),
|
||||
mcp_results=tool_context,
|
||||
rag_context=rag_context,
|
||||
rag_metadata=rag_metadata,
|
||||
)
|
||||
|
||||
answer = await self._invoke_llm_cached(state, "ProductAgent", messages)
|
||||
result = {
|
||||
"answer": f"[ProductAgent] {answer}",
|
||||
"next_state": "PRODUCT_ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
"IC.PRODUCT_AGENT_COMPLETED",
|
||||
state,
|
||||
{
|
||||
"answer_chars": len(result.get("answer") or ""),
|
||||
"has_mcp_results": bool(tool_context),
|
||||
"rag_enabled": bool(rag_metadata.get("enabled")),
|
||||
"memory_context": state.get("memory_context_metadata"),
|
||||
},
|
||||
component="agent.product.completed",
|
||||
)
|
||||
return result
|
||||
|
||||
async def _collect_tool_context(self, state):
|
||||
return await self._collect_mcp_context(state)
|
||||
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def apply_agent_profile_prompt(state: dict, default_prompt: str) -> str:
|
||||
"""Adiciona o prefixo de prompt configurado para o agent_template selecionado.
|
||||
|
||||
Cada agent_id pode definir metadata.system_prefix em config/agents.yaml. Isso
|
||||
mantém prompts isolados sem duplicar o código dos agentes especializados.
|
||||
"""
|
||||
profile = state.get("agent_profile") or (state.get("context") or {}).get("agent_profile") or {}
|
||||
metadata = profile.get("metadata") or {}
|
||||
prefix = (metadata.get("system_prefix") or "").strip()
|
||||
if not prefix:
|
||||
return default_prompt
|
||||
return f"{prefix}\n\n{default_prompt}"
|
||||
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
# Compatibilidade local do template/backend.
|
||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||
from app.presentation import register_tool_renderers
|
||||
|
||||
register_tool_renderers()
|
||||
|
||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
||||
@@ -0,0 +1,129 @@
|
||||
from app.agents.prompting import apply_agent_profile_prompt
|
||||
from app.agents.runtime import AgentRuntimeMixin
|
||||
|
||||
|
||||
class SupportAgent(AgentRuntimeMixin):
|
||||
name = "support_agent"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm,
|
||||
telemetry=None,
|
||||
tool_router=None,
|
||||
rag_service=None,
|
||||
cache=None,
|
||||
settings=None,
|
||||
observer=None,
|
||||
memory=None,
|
||||
summary_memory=None,
|
||||
):
|
||||
self.llm = llm
|
||||
self.telemetry = telemetry
|
||||
self.tool_router = tool_router
|
||||
self.rag_service = rag_service
|
||||
self.cache = cache
|
||||
self.settings = settings
|
||||
self.observer = observer
|
||||
self.memory = memory
|
||||
self.summary_memory = summary_memory
|
||||
|
||||
async def run(self, state):
|
||||
await self._emit_ic(
|
||||
"IC.SUPPORT_AGENT_STARTED",
|
||||
state,
|
||||
{"business_component": "suporte"},
|
||||
component="agent.support.start",
|
||||
)
|
||||
|
||||
tool_context = await self._collect_tool_context(state)
|
||||
if tool_context:
|
||||
await self._emit_ic(
|
||||
"IC.SUPPORT_MCP_CONTEXT_COLLECTED",
|
||||
state,
|
||||
{"tool_result_count": len(tool_context)},
|
||||
component="agent.support.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="SupportAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
"IC.SUPPORT_RAG_CONTEXT_RETRIEVED",
|
||||
state,
|
||||
{
|
||||
"document_count": rag_metadata.get("document_count"),
|
||||
"graph_neighbors": rag_metadata.get("graph_neighbors"),
|
||||
"latency_ms": rag_metadata.get("latency_ms"),
|
||||
},
|
||||
component="agent.support.rag",
|
||||
)
|
||||
|
||||
# Prepara ConversationSummaryMemory antes de montar o prompt.
|
||||
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=apply_agent_profile_prompt(
|
||||
state,
|
||||
"Você é um agente de suporte de varejo para troca, devolução e garantia.",
|
||||
),
|
||||
mcp_results=tool_context,
|
||||
rag_context=rag_context,
|
||||
rag_metadata=rag_metadata,
|
||||
)
|
||||
|
||||
answer = await self._invoke_llm_cached(state, "SupportAgent", messages)
|
||||
result = {
|
||||
"answer": f"[SupportAgent] {answer}",
|
||||
"next_state": "SUPPORT_ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
"IC.SUPPORT_AGENT_COMPLETED",
|
||||
state,
|
||||
{
|
||||
"answer_chars": len(result.get("answer") or ""),
|
||||
"has_mcp_results": bool(tool_context),
|
||||
"rag_enabled": bool(rag_metadata.get("enabled")),
|
||||
"memory_context": state.get("memory_context_metadata"),
|
||||
},
|
||||
component="agent.support.completed",
|
||||
)
|
||||
return result
|
||||
|
||||
async def _collect_tool_context(self, state):
|
||||
return await self._collect_mcp_context(state)
|
||||
@@ -0,0 +1 @@
|
||||
"""Exemplos de uso do template backend enterprise."""
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Exemplos de GRL.
|
||||
|
||||
GRL representa eventos de guardrails. Em regra, GRL.001..GRL.009 são emitidos
|
||||
pelo pipeline de guardrails e pelo OutputSupervisor do framework. Use emissão
|
||||
manual apenas para validações customizadas do agente.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
async def exemplo_guardrail_observado(observer: Any, state: dict[str, Any], rail_code: str, reason: str) -> None:
|
||||
await observer.emit_grl(
|
||||
"OBSERVE",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"rail_code": rail_code,
|
||||
"reason": reason,
|
||||
},
|
||||
component="examples.grl",
|
||||
)
|
||||
|
||||
|
||||
async def exemplo_guardrail_block(observer: Any, state: dict[str, Any], rail_code: str, reason: str) -> None:
|
||||
await observer.emit_grl(
|
||||
"004",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"rail_code": rail_code,
|
||||
"reason": reason,
|
||||
"action": "block",
|
||||
},
|
||||
component="examples.grl",
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Exemplos de IC - Item de Controle.
|
||||
|
||||
ICs representam eventos de negócio. Eles alimentam Informacional, Curadoria,
|
||||
analytics, BigQuery ou qualquer publisher configurado no framework.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
async def exemplo_fatura_consultada(observer: Any, state: dict[str, Any], invoice_id: str) -> None:
|
||||
await observer.emit_ic(
|
||||
"IC.FATURA_CONSULTADA",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"invoice_id": invoice_id,
|
||||
},
|
||||
component="examples.ic",
|
||||
)
|
||||
|
||||
|
||||
async def exemplo_acao_concluida(observer: Any, state: dict[str, Any], action_name: str, ok: bool) -> None:
|
||||
await observer.emit_ic(
|
||||
"IC.ACAO_CONCLUIDA",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"action_name": action_name,
|
||||
"ok": ok,
|
||||
},
|
||||
component="examples.ic",
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user