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.
|
# Compatibilidade local do template/backend.
|
||||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||||
|
from app.presentation import register_tool_renderers
|
||||||
|
|
||||||
|
register_tool_renderers()
|
||||||
|
|
||||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
__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]
|
available_mcp_tools: list[str]
|
||||||
selected_tool_call: dict[str, Any]
|
selected_tool_call: dict[str, Any]
|
||||||
pending_tool_call: dict[str, Any]
|
pending_tool_call: dict[str, Any]
|
||||||
|
active_transaction: dict[str, Any]
|
||||||
|
last_transaction: dict[str, Any]
|
||||||
transaction_status: str
|
transaction_status: str
|
||||||
confirmation_required: bool
|
confirmation_required: bool
|
||||||
confirmation_received: bool
|
confirmation_received: bool
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
|||||||
mensagem.
|
mensagem.
|
||||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||||
group: 1
|
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:
|
solicitar_troca:
|
||||||
map:
|
map:
|
||||||
session_key: session_id
|
session_key: session_id
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ intents:
|
|||||||
- Quero saber sobre meu pacote de internet.
|
- Quero saber sobre meu pacote de internet.
|
||||||
- Tenho roaming internacional?
|
- 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
|
- name: retail_order_tracking
|
||||||
domain: retail
|
domain: retail
|
||||||
agent: orders_agent
|
agent: orders_agent
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ defaults:
|
|||||||
require_confirmation: false
|
require_confirmation: false
|
||||||
|
|
||||||
tool_policies:
|
tool_policies:
|
||||||
|
cancelar_pedido:
|
||||||
|
operation_type: transactional
|
||||||
|
require_confirmation: true
|
||||||
|
requires: [order_id]
|
||||||
|
|
||||||
|
|
||||||
solicitar_troca:
|
solicitar_troca:
|
||||||
operation_type: transactional
|
operation_type: transactional
|
||||||
require_confirmation: true
|
require_confirmation: true
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ tools:
|
|||||||
- fatura
|
- fatura
|
||||||
- conta
|
- conta
|
||||||
- boleto
|
- boleto
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.invoice
|
||||||
consultar_pagamentos:
|
consultar_pagamentos:
|
||||||
description: Consulta histórico de pagamentos do cliente.
|
description: Consulta histórico de pagamentos do cliente.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -28,6 +31,9 @@ tools:
|
|||||||
asset_id: string
|
asset_id: string
|
||||||
selection_keywords:
|
selection_keywords:
|
||||||
- plano
|
- plano
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.plan
|
||||||
listar_servicos:
|
listar_servicos:
|
||||||
description: Lista serviços ativos e adicionais VAS.
|
description: Lista serviços ativos e adicionais VAS.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -49,6 +55,9 @@ tools:
|
|||||||
- consultar pedido
|
- consultar pedido
|
||||||
- status do pedido
|
- status do pedido
|
||||||
- pedido
|
- pedido
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: retail.order
|
||||||
consultar_entrega:
|
consultar_entrega:
|
||||||
description: Consulta entrega e rastreamento do pedido.
|
description: Consulta entrega e rastreamento do pedido.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
@@ -61,6 +70,26 @@ tools:
|
|||||||
- rastreamento
|
- rastreamento
|
||||||
- transportadora
|
- transportadora
|
||||||
- previsão
|
- 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:
|
solicitar_troca:
|
||||||
description: Simula abertura de solicitação de troca.
|
description: Simula abertura de solicitação de troca.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
|
|||||||
@@ -78,3 +78,10 @@ profiles:
|
|||||||
temperature: 0
|
temperature: 0
|
||||||
max_tokens: 80
|
max_tokens: 80
|
||||||
timeout_seconds: 5
|
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]
|
available_mcp_tools: list[str]
|
||||||
selected_tool_call: dict[str, Any]
|
selected_tool_call: dict[str, Any]
|
||||||
pending_tool_call: dict[str, Any]
|
pending_tool_call: dict[str, Any]
|
||||||
|
active_transaction: dict[str, Any]
|
||||||
|
last_transaction: dict[str, Any]
|
||||||
transaction_status: str
|
transaction_status: str
|
||||||
confirmation_required: bool
|
confirmation_required: bool
|
||||||
confirmation_received: bool
|
confirmation_received: bool
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
|||||||
mensagem.
|
mensagem.
|
||||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||||
group: 1
|
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:
|
solicitar_troca:
|
||||||
map:
|
map:
|
||||||
session_key: session_id
|
session_key: session_id
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ intents:
|
|||||||
- Quero saber sobre meu pacote de internet.
|
- Quero saber sobre meu pacote de internet.
|
||||||
- Tenho roaming internacional?
|
- 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
|
- name: retail_order_tracking
|
||||||
domain: retail
|
domain: retail
|
||||||
agent: orders_agent
|
agent: orders_agent
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ defaults:
|
|||||||
require_confirmation: false
|
require_confirmation: false
|
||||||
|
|
||||||
tool_policies:
|
tool_policies:
|
||||||
|
cancelar_pedido:
|
||||||
|
operation_type: transactional
|
||||||
|
require_confirmation: true
|
||||||
|
requires: [order_id]
|
||||||
|
|
||||||
|
|
||||||
solicitar_troca:
|
solicitar_troca:
|
||||||
operation_type: transactional
|
operation_type: transactional
|
||||||
require_confirmation: true
|
require_confirmation: true
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ tools:
|
|||||||
- fatura
|
- fatura
|
||||||
- conta
|
- conta
|
||||||
- boleto
|
- boleto
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.invoice
|
||||||
consultar_pagamentos:
|
consultar_pagamentos:
|
||||||
description: Consulta histórico de pagamentos do cliente.
|
description: Consulta histórico de pagamentos do cliente.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -28,6 +31,9 @@ tools:
|
|||||||
asset_id: string
|
asset_id: string
|
||||||
selection_keywords:
|
selection_keywords:
|
||||||
- plano
|
- plano
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.plan
|
||||||
listar_servicos:
|
listar_servicos:
|
||||||
description: Lista serviços ativos e adicionais VAS.
|
description: Lista serviços ativos e adicionais VAS.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -49,6 +55,9 @@ tools:
|
|||||||
- consultar pedido
|
- consultar pedido
|
||||||
- status do pedido
|
- status do pedido
|
||||||
- pedido
|
- pedido
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: retail.order
|
||||||
consultar_entrega:
|
consultar_entrega:
|
||||||
description: Consulta entrega e rastreamento do pedido.
|
description: Consulta entrega e rastreamento do pedido.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
@@ -61,6 +70,26 @@ tools:
|
|||||||
- rastreamento
|
- rastreamento
|
||||||
- transportadora
|
- transportadora
|
||||||
- previsão
|
- 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:
|
solicitar_troca:
|
||||||
description: Simula abertura de solicitação de troca.
|
description: Simula abertura de solicitação de troca.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
|
|||||||
@@ -78,3 +78,10 @@ profiles:
|
|||||||
temperature: 0
|
temperature: 0
|
||||||
max_tokens: 80
|
max_tokens: 80
|
||||||
timeout_seconds: 5
|
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.
|
# Compatibilidade local do template/backend.
|
||||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||||
|
from app.presentation import register_tool_renderers
|
||||||
|
|
||||||
|
register_tool_renderers()
|
||||||
|
|
||||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
__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]
|
available_mcp_tools: list[str]
|
||||||
selected_tool_call: dict[str, Any]
|
selected_tool_call: dict[str, Any]
|
||||||
pending_tool_call: dict[str, Any]
|
pending_tool_call: dict[str, Any]
|
||||||
|
active_transaction: dict[str, Any]
|
||||||
|
last_transaction: dict[str, Any]
|
||||||
transaction_status: str
|
transaction_status: str
|
||||||
confirmation_required: bool
|
confirmation_required: bool
|
||||||
confirmation_received: bool
|
confirmation_received: bool
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
|||||||
mensagem.
|
mensagem.
|
||||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||||
group: 1
|
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:
|
solicitar_troca:
|
||||||
map:
|
map:
|
||||||
session_key: session_id
|
session_key: session_id
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ intents:
|
|||||||
- Quero saber sobre meu pacote de internet.
|
- Quero saber sobre meu pacote de internet.
|
||||||
- Tenho roaming internacional?
|
- 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
|
- name: retail_order_tracking
|
||||||
domain: retail
|
domain: retail
|
||||||
agent: orders_agent
|
agent: orders_agent
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ defaults:
|
|||||||
require_confirmation: false
|
require_confirmation: false
|
||||||
|
|
||||||
tool_policies:
|
tool_policies:
|
||||||
|
cancelar_pedido:
|
||||||
|
operation_type: transactional
|
||||||
|
require_confirmation: true
|
||||||
|
requires: [order_id]
|
||||||
|
|
||||||
|
|
||||||
solicitar_troca:
|
solicitar_troca:
|
||||||
operation_type: transactional
|
operation_type: transactional
|
||||||
require_confirmation: true
|
require_confirmation: true
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ tools:
|
|||||||
- fatura
|
- fatura
|
||||||
- conta
|
- conta
|
||||||
- boleto
|
- boleto
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.invoice
|
||||||
consultar_pagamentos:
|
consultar_pagamentos:
|
||||||
description: Consulta histórico de pagamentos do cliente.
|
description: Consulta histórico de pagamentos do cliente.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -28,6 +31,9 @@ tools:
|
|||||||
asset_id: string
|
asset_id: string
|
||||||
selection_keywords:
|
selection_keywords:
|
||||||
- plano
|
- plano
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.plan
|
||||||
listar_servicos:
|
listar_servicos:
|
||||||
description: Lista serviços ativos e adicionais VAS.
|
description: Lista serviços ativos e adicionais VAS.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -49,6 +55,9 @@ tools:
|
|||||||
- consultar pedido
|
- consultar pedido
|
||||||
- status do pedido
|
- status do pedido
|
||||||
- pedido
|
- pedido
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: retail.order
|
||||||
consultar_entrega:
|
consultar_entrega:
|
||||||
description: Consulta entrega e rastreamento do pedido.
|
description: Consulta entrega e rastreamento do pedido.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
@@ -61,6 +70,26 @@ tools:
|
|||||||
- rastreamento
|
- rastreamento
|
||||||
- transportadora
|
- transportadora
|
||||||
- previsão
|
- 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:
|
solicitar_troca:
|
||||||
description: Simula abertura de solicitação de troca.
|
description: Simula abertura de solicitação de troca.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
|
|||||||
@@ -78,3 +78,10 @@ profiles:
|
|||||||
temperature: 0
|
temperature: 0
|
||||||
max_tokens: 80
|
max_tokens: 80
|
||||||
timeout_seconds: 5
|
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.
|
# Compatibilidade local do template/backend.
|
||||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||||
|
from app.presentation import register_tool_renderers
|
||||||
|
|
||||||
|
register_tool_renderers()
|
||||||
|
|
||||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
__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]
|
available_mcp_tools: list[str]
|
||||||
selected_tool_call: dict[str, Any]
|
selected_tool_call: dict[str, Any]
|
||||||
pending_tool_call: dict[str, Any]
|
pending_tool_call: dict[str, Any]
|
||||||
|
active_transaction: dict[str, Any]
|
||||||
|
last_transaction: dict[str, Any]
|
||||||
transaction_status: str
|
transaction_status: str
|
||||||
confirmation_required: bool
|
confirmation_required: bool
|
||||||
confirmation_received: bool
|
confirmation_received: bool
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
|||||||
mensagem.
|
mensagem.
|
||||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||||
group: 1
|
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:
|
solicitar_troca:
|
||||||
map:
|
map:
|
||||||
session_key: session_id
|
session_key: session_id
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ intents:
|
|||||||
- Quero saber sobre meu pacote de internet.
|
- Quero saber sobre meu pacote de internet.
|
||||||
- Tenho roaming internacional?
|
- 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
|
- name: retail_order_tracking
|
||||||
domain: retail
|
domain: retail
|
||||||
agent: orders_agent
|
agent: orders_agent
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ defaults:
|
|||||||
require_confirmation: false
|
require_confirmation: false
|
||||||
|
|
||||||
tool_policies:
|
tool_policies:
|
||||||
|
cancelar_pedido:
|
||||||
|
operation_type: transactional
|
||||||
|
require_confirmation: true
|
||||||
|
requires: [order_id]
|
||||||
|
|
||||||
|
|
||||||
solicitar_troca:
|
solicitar_troca:
|
||||||
operation_type: transactional
|
operation_type: transactional
|
||||||
require_confirmation: true
|
require_confirmation: true
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ tools:
|
|||||||
- fatura
|
- fatura
|
||||||
- conta
|
- conta
|
||||||
- boleto
|
- boleto
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.invoice
|
||||||
consultar_pagamentos:
|
consultar_pagamentos:
|
||||||
description: Consulta histórico de pagamentos do cliente.
|
description: Consulta histórico de pagamentos do cliente.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -28,6 +31,9 @@ tools:
|
|||||||
asset_id: string
|
asset_id: string
|
||||||
selection_keywords:
|
selection_keywords:
|
||||||
- plano
|
- plano
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.plan
|
||||||
listar_servicos:
|
listar_servicos:
|
||||||
description: Lista serviços ativos e adicionais VAS.
|
description: Lista serviços ativos e adicionais VAS.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -49,6 +55,9 @@ tools:
|
|||||||
- consultar pedido
|
- consultar pedido
|
||||||
- status do pedido
|
- status do pedido
|
||||||
- pedido
|
- pedido
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: retail.order
|
||||||
consultar_entrega:
|
consultar_entrega:
|
||||||
description: Consulta entrega e rastreamento do pedido.
|
description: Consulta entrega e rastreamento do pedido.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
@@ -61,6 +70,26 @@ tools:
|
|||||||
- rastreamento
|
- rastreamento
|
||||||
- transportadora
|
- transportadora
|
||||||
- previsão
|
- 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:
|
solicitar_troca:
|
||||||
description: Simula abertura de solicitação de troca.
|
description: Simula abertura de solicitação de troca.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
|
|||||||
@@ -72,3 +72,10 @@ profiles:
|
|||||||
temperature: 0
|
temperature: 0
|
||||||
max_tokens: 80
|
max_tokens: 80
|
||||||
timeout_seconds: 5
|
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]
|
available_mcp_tools: list[str]
|
||||||
selected_tool_call: dict[str, Any]
|
selected_tool_call: dict[str, Any]
|
||||||
pending_tool_call: dict[str, Any]
|
pending_tool_call: dict[str, Any]
|
||||||
|
active_transaction: dict[str, Any]
|
||||||
|
last_transaction: dict[str, Any]
|
||||||
transaction_status: str
|
transaction_status: str
|
||||||
confirmation_required: bool
|
confirmation_required: bool
|
||||||
confirmation_received: bool
|
confirmation_received: bool
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
|||||||
mensagem.
|
mensagem.
|
||||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||||
group: 1
|
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:
|
solicitar_troca:
|
||||||
map:
|
map:
|
||||||
session_key: session_id
|
session_key: session_id
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ intents:
|
|||||||
- Quero saber sobre meu pacote de internet.
|
- Quero saber sobre meu pacote de internet.
|
||||||
- Tenho roaming internacional?
|
- 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
|
- name: retail_order_tracking
|
||||||
domain: retail
|
domain: retail
|
||||||
agent: orders_agent
|
agent: orders_agent
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ defaults:
|
|||||||
require_confirmation: false
|
require_confirmation: false
|
||||||
|
|
||||||
tool_policies:
|
tool_policies:
|
||||||
|
cancelar_pedido:
|
||||||
|
operation_type: transactional
|
||||||
|
require_confirmation: true
|
||||||
|
requires: [order_id]
|
||||||
|
|
||||||
|
|
||||||
solicitar_troca:
|
solicitar_troca:
|
||||||
operation_type: transactional
|
operation_type: transactional
|
||||||
require_confirmation: true
|
require_confirmation: true
|
||||||
|
|||||||
@@ -61,6 +61,23 @@ tools:
|
|||||||
- rastreamento
|
- rastreamento
|
||||||
- transportadora
|
- transportadora
|
||||||
- previsão
|
- 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:
|
solicitar_troca:
|
||||||
description: Simula abertura de solicitação de troca.
|
description: Simula abertura de solicitação de troca.
|
||||||
mcp_server: retail
|
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.
|
- `Route_Stickness`: continuidade de rota, handoff e políticas transacionais conversacionais.
|
||||||
- `Long_Term_Memory`: memória de longo prazo.
|
- `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.
|
- `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.
|
# Compatibilidade local do template/backend.
|
||||||
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
|
||||||
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
|
||||||
|
from app.presentation import register_tool_renderers
|
||||||
|
|
||||||
|
register_tool_renderers()
|
||||||
|
|
||||||
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]
|
__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]
|
available_mcp_tools: list[str]
|
||||||
selected_tool_call: dict[str, Any]
|
selected_tool_call: dict[str, Any]
|
||||||
pending_tool_call: dict[str, Any]
|
pending_tool_call: dict[str, Any]
|
||||||
|
active_transaction: dict[str, Any]
|
||||||
|
last_transaction: dict[str, Any]
|
||||||
transaction_status: str
|
transaction_status: str
|
||||||
confirmation_required: bool
|
confirmation_required: bool
|
||||||
confirmation_received: bool
|
confirmation_received: bool
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
|||||||
mensagem.
|
mensagem.
|
||||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||||
group: 1
|
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:
|
solicitar_troca:
|
||||||
map:
|
map:
|
||||||
session_key: session_id
|
session_key: session_id
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ intents:
|
|||||||
- Quero saber sobre meu pacote de internet.
|
- Quero saber sobre meu pacote de internet.
|
||||||
- Tenho roaming internacional?
|
- 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
|
- name: retail_order_tracking
|
||||||
domain: retail
|
domain: retail
|
||||||
agent: orders_agent
|
agent: orders_agent
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ defaults:
|
|||||||
require_confirmation: false
|
require_confirmation: false
|
||||||
|
|
||||||
tool_policies:
|
tool_policies:
|
||||||
|
cancelar_pedido:
|
||||||
|
operation_type: transactional
|
||||||
|
require_confirmation: true
|
||||||
|
requires: [order_id]
|
||||||
|
|
||||||
|
|
||||||
solicitar_troca:
|
solicitar_troca:
|
||||||
operation_type: transactional
|
operation_type: transactional
|
||||||
require_confirmation: true
|
require_confirmation: true
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ tools:
|
|||||||
- fatura
|
- fatura
|
||||||
- conta
|
- conta
|
||||||
- boleto
|
- boleto
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.invoice
|
||||||
consultar_pagamentos:
|
consultar_pagamentos:
|
||||||
description: Consulta histórico de pagamentos do cliente.
|
description: Consulta histórico de pagamentos do cliente.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -28,6 +31,9 @@ tools:
|
|||||||
asset_id: string
|
asset_id: string
|
||||||
selection_keywords:
|
selection_keywords:
|
||||||
- plano
|
- plano
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: telecom.plan
|
||||||
listar_servicos:
|
listar_servicos:
|
||||||
description: Lista serviços ativos e adicionais VAS.
|
description: Lista serviços ativos e adicionais VAS.
|
||||||
mcp_server: telecom
|
mcp_server: telecom
|
||||||
@@ -49,6 +55,9 @@ tools:
|
|||||||
- consultar pedido
|
- consultar pedido
|
||||||
- status do pedido
|
- status do pedido
|
||||||
- pedido
|
- pedido
|
||||||
|
response:
|
||||||
|
mode: renderer
|
||||||
|
renderer: retail.order
|
||||||
consultar_entrega:
|
consultar_entrega:
|
||||||
description: Consulta entrega e rastreamento do pedido.
|
description: Consulta entrega e rastreamento do pedido.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
@@ -61,6 +70,26 @@ tools:
|
|||||||
- rastreamento
|
- rastreamento
|
||||||
- transportadora
|
- transportadora
|
||||||
- previsão
|
- 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:
|
solicitar_troca:
|
||||||
description: Simula abertura de solicitação de troca.
|
description: Simula abertura de solicitação de troca.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
|
|||||||
@@ -78,3 +78,10 @@ profiles:
|
|||||||
temperature: 0
|
temperature: 0
|
||||||
max_tokens: 80
|
max_tokens: 80
|
||||||
timeout_seconds: 5
|
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]
|
available_mcp_tools: list[str]
|
||||||
selected_tool_call: dict[str, Any]
|
selected_tool_call: dict[str, Any]
|
||||||
pending_tool_call: dict[str, Any]
|
pending_tool_call: dict[str, Any]
|
||||||
|
active_transaction: dict[str, Any]
|
||||||
|
last_transaction: dict[str, Any]
|
||||||
transaction_status: str
|
transaction_status: str
|
||||||
confirmation_required: bool
|
confirmation_required: bool
|
||||||
confirmation_received: bool
|
confirmation_received: bool
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ mcp_parameter_mapping:
|
|||||||
mensagem.
|
mensagem.
|
||||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||||
group: 1
|
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:
|
solicitar_troca:
|
||||||
map:
|
map:
|
||||||
session_key: session_id
|
session_key: session_id
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ intents:
|
|||||||
- Quero saber sobre meu pacote de internet.
|
- Quero saber sobre meu pacote de internet.
|
||||||
- Tenho roaming internacional?
|
- 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
|
- name: retail_order_tracking
|
||||||
domain: retail
|
domain: retail
|
||||||
agent: orders_agent
|
agent: orders_agent
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ defaults:
|
|||||||
require_confirmation: false
|
require_confirmation: false
|
||||||
|
|
||||||
tool_policies:
|
tool_policies:
|
||||||
|
cancelar_pedido:
|
||||||
|
operation_type: transactional
|
||||||
|
require_confirmation: true
|
||||||
|
requires: [order_id]
|
||||||
|
|
||||||
|
|
||||||
solicitar_troca:
|
solicitar_troca:
|
||||||
operation_type: transactional
|
operation_type: transactional
|
||||||
require_confirmation: true
|
require_confirmation: true
|
||||||
|
|||||||
@@ -61,6 +61,23 @@ tools:
|
|||||||
- rastreamento
|
- rastreamento
|
||||||
- transportadora
|
- transportadora
|
||||||
- previsão
|
- 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:
|
solicitar_troca:
|
||||||
description: Simula abertura de solicitação de troca.
|
description: Simula abertura de solicitação de troca.
|
||||||
mcp_server: retail
|
mcp_server: retail
|
||||||
|
|||||||
@@ -78,3 +78,10 @@ profiles:
|
|||||||
temperature: 0
|
temperature: 0
|
||||||
max_tokens: 80
|
max_tokens: 80
|
||||||
timeout_seconds: 5
|
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