Transformar uma pergunta (usando linguagem natural) em uma consulta SQL é uma capacidade muito útil para uso da IA. O Oracle AI Database tem o SELECT AI para fazer isso.
Contudo, há situações que precisamos de algo mais estruturado e coordenado, pois uma pergunta pode demandar regras e definições bem específicas. Para isso temos o SELECT AI AGENT.
Neste artigo, vamos explorar a lógica envolvida no uso desta capacidade. Como resultado teremos um time de agentes que responderá perguntas típicas de um gerente de loja sobre cadastro de clientes, vendas realizadas e comissão de vendas que a loja receberá para distribuição entre os colaboradores. Para realizar isto usaremos algumas APIs do Oracle AI Database que permitem interagir com modelos de LLM e realizar ações agênticas dentro do banco de dados.
O que é o Select AI Agent?
O Select AI Agent é um framework de agentes dentro do Oracle AI Database. Você já tem acesso a ele ao provisionar um banco de dados Oracle em seu serviço autonômo (auto-gerenciado). Se você estiver em outros ambientes como BaseDB, Exa-D, Exa-Scale, ou On-Premise, será necessário instalar a familia de pacotes DBMS_CLOUD*. Os detalhes de instalação desta familia de pacotes estão aqui. Neste artigo usaremos a DBMS_CLOUD, DBMS_CLOUD_AI e DBMS_CLOUD_AI_AGENT.
Esse pacotes podem possuir diferentes capacidades de acordo com a versão do Oracle AI Database, para detalhes do que está disponível em cada versão veja a tabela Capacity Summary disponível aqui.
Neste artigo usaremos duas capacidades disponíveis na versão 26ai do Autonomous Database:
- AI AGENT para organizar as tarefas e chamar ferramentas
- NL2SQL para criar comandos SQL dinamicamente a partir de perguntas dos usuário
Pré-requisitos
Esta seção explica os pré-requisitos para um ambiente Oracle Autonomous Database.
Complete estas instruções antes de começar a usar o SELECT AI AGENT.
1) Usuário e Permissões
O administrador do banco (usuário ADMIN) deve criar um usuário e conceder permissões necessárias:
-- Crição do usuário (se não existir)
CREATE USER MYAPP IDENTIFIED BY "TheAppUser#26ai";
GRANT CONNECT, RESOURCE, UNLIMITED TABLESPACE TO MYAPP;
-- Se estiver no Autonomous DB, e quiser acessar via SQL Developer Web
-- REST ENABLE
BEGIN
ORDS_ADMIN.ENABLE_SCHEMA(
p_enabled => TRUE,
p_schema => 'MYAPP',
p_url_mapping_type => 'BASE_PATH',
p_url_mapping_pattern => 'myapp',
p_auto_rest_auth=> FALSE
);
-- ENABLE DATA SHARING
C##ADP$SERVICE.DBMS_SHARE.ENABLE_SCHEMA(
SCHEMA_NAME => 'MYAPP',
ENABLED => TRUE
);
commit;
END;
/
-- Permissões relevantes para usar SELECT AI AGENT
GRANT EXECUTE ON DBMS_CLOUD TO MYAPP;
GRANT EXECUTE ON DBMS_CLOUD_AI TO MYAPP;
GRANT EXECUTE ON DBMS_CLOUD_AI_AGENT TO MYAPP;
GRANT CREATE CREDENTIAL TO MYAPP;
2) Autorizar saída de rede para serviços externos
O administrador do banco deve executar esse comando para permitr que o usuário MYAPP consiga chamar o endpoint do serviço Generative AI do OCI. O parâmetro host indica a URL cuja saída de rede está liberada.
BEGIN
DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
host => 'inference.generativeai.us-chicago-1.oci.oraclecloud.com',
lower_port => 443,
upper_port => 443,
ace => xs$ace_type(
privilege_list => xs$name_list('connect'),
principal_name => 'MYAPP',
principal_type => xs_acl.ptype_db
)
);
DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
host => 'inference.generativeai.us-chicago-1.oci.oraclecloud.com',
ace => xs$ace_type(
privilege_list => xs$name_list('resolve'),
principal_name => 'MYAPP',
principal_type => xs_acl.ptype_db
)
);
END;
/
O pacote DBMS_NETWORK_ACL_ADMIN define quais usuários ou roles do banco podem acessar quais hosts externos autorizados — por exemplo APIs HTTPS, SMTP, serviços de IA e endpoints de busca web.
Isto é um requisito de segurança importante. O Oracle AI Database coloca uma camada de segurança para controlar quem pode acessar URLs externas e em quais endereços.
O endpoint usado é o local de inferência do OCI Generative AI na região de Chicago.
inference.generativeai.us-chicago-1.oci.oraclecloud.com
O nome us-chicago-1 na URL indica a região OCI do endpoint. Você pode consumir modelos em diferentes regiões quando usa o OCI Generative AI. Veja aqui a lista dos endpoints disponíveis.
O serviço OCI Generative AI oferece modelos On-Demand (inferência é cobrada por chamada) ou Dedicated (inferência ocorre em um ambiente privado). A URL do exemplo é de um endpoint de inferência sob demanda. Veja mais detalhes aqui.
3) Autorização de uso do OCI Generative AI
Por enquanto você já sabe onde iremos fazer a inferência, mas é necessário ter autorizar para usar o serviço. Isto é feito através de políticas do OCI IAM (Identity and Access Management).
No OCI, as políticas do IAM são regras de autorização que definem quem pode fazer o quê, em qual escopo. O exemplo libera um grupo específico a gerenciar a familia de recursos generative-ai-family em um compartimento específico.
allow group <your-group-name> to manage generative-ai-family
in compartment <your-compartment-name>
O OCI fornece uma ótima gradularidade de autorização. Para mais detalhes sobre como autorizar o acesso ao OCI Generative AI clique aqui.
A permissão que este artigo usa foi concedida pelo administrador do Tenancy.
4) Subscrição de região no seu Tenancy
Atente-se ao fato que seu banco de dados pode estar em execução em uma região OCI diferente do endpoint de inferência do OCI Generative AI que deseja usar. Se este for seu caso, você precisa subscrever seu Tenancy do OCI nas regiões que usará. Este processo é explicado aqui.
Você pode desejar fazer isso pois a disponibilidade de modelos pode variar por região. Aqui você tem a lista completa dos modelos disponíveis em diferentes regiões do OCI.
Este artigo usa um banco na região de São Paulo, mas o ponto de inferência de LLM está em Chigado. Isto significa que o Tenancy em uso tem subscrição para: sa-saopaulo-1 e us-chicago-1.
5) Criar uma credencial de acesso às APIs do OCI
A partir de agora todos os comandos executados serão feitos pelo usuário MYAPP.
Use o código abaixo para criar uma CREDENCIAL que dá acesso a uma endpoint de LLM. Este exemplo assume que usaremos o serviço OCI Generative AI (que requer uma OCI API Key válida).
Importante: Substitua os valores dos atributos user_ocid, tenancy_ocid, private_key e fingerprint pelos dados corretos de sua API Key previamente criada. Veja como criar uma API Key neste artigo.
DECLARE
jo JSON_OBJECT_T;
BEGIN
jo := JSON_OBJECT_T();
jo.put('user_ocid', 'ocid1.user.oc1..xxxxxxxx');
jo.put('tenancy_ocid', 'ocid1.tenancy.oc1..xxxxxxxx');
jo.put('private_key', q'~
xxxxxxxxxxxxxxxxxxxxx
xxx-private-key-xxxxx
xxxxxxxxx==
~');
jo.put('fingerprint', 'xxxx-finger-print-xxxx');
DBMS_CLOUD.CREATE_CREDENTIAL(
credential_name => 'OCI_CRED_CLOUD',
user_ocid => jo.get_string('user_ocid'),
tenancy_ocid => jo.get_string('tenancy_ocid'),
private_key => jo.get_string('private_key'),
fingerprint => jo.get_string('fingerprint')
);
END;
/
Atenção: Observe que a chave privada fica exatamente entre os caracteres ‘˜’ (inicio e fim), em linhas separadas. Cuidado ao preparar o comando, as quebras de linha são importantes. Veja exemplo abaixo.
q’~
xxxxxxxxxxxxxxxxxxxxx
xxx-private-key-xxxxx
xxxxxxxxx==
~’
Aqui você tem a documentação da DBMS_CLOUD.
6) Perfil de acesso para IA
O usuário MYAPP deverá criar pelo menos um perfil de IA para conseguir acessar modelos externos ao banco de dados. O comando abaixo cria um Profile chamado GENAI_DADOS_DE_CLIENTE.
Em relação aos atributos usados no código, destacam-se:
- Provedor do serviço é o oci (ou seja, será chamada uma API do serviço OCI Generative)
- O código referencia a credencial GENAI_DADOS_DE_CLIENTE (criada anteriormente)
- O modelo xai.grok-4.3 é um dos disponíveis para inferência sob demanda na região de Chicago
- Não importa quantas tabelas o usuário MYAPP tenha, apenas 3 tabelas podem interagir com LLM.
- Commets definido como verdadeiro indica que o Oracle AI Database irá enviar comentários nas tabelas e suas colunas como metadado para o modelo escolhido. Isso ajudará a criar um comando SQL melhor durante um NL2SQL.
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'GENAI_DADOS_GERAIS',
attributes => '{
"provider": "oci",
"credential_name": "OCI_CRED_CLOUD",
"model": "xai.grok-4.3",
"object_list": [
{"owner": "MYAPP", "name": "CLIENTES"},
{"owner": "MYAPP", "name": "LOJAS"}
],
"object_list_mode": "all",
"comments": true,
"temperature": 0,
"max_tokens": 1500
}',
description => 'Restrige acesso a 2 tabelas.'
);
END;
/
Você encontra aqui mais opções de atributos e detalhes de uso da DBMS_CLOUD_AI.CREATE_PROFILE
Uma observação interessante: as tabelas indicadas no código não precisam existir neste momento.
O comando abaixo cria outro perfil que limita os dados acessos pelos agentes para ler visões que unificam dados de várias tabelas simultaneamente ou restringem acesso a colunas irrelevantes para o tipo de pergunta (incluso dados sensíveis que não devem ser acessados). A ideia é interessante para tirar o esforço do modeo LLM de montar um SQL muito complexo. Outro benefício é reduzir o volume de metadado enviado ao LLM (redução de tokens).
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'GENAI_DASHBOARD_VENDAS',
attributes => '{
"provider": "oci",
"credential_name": "OCI_CRED_CLOUD",
"model": "xai.grok-4.3",
"object_list": [
{"owner": "MYAPP", "name": "VIEW_VENDAS"},
{"owner": "MYAPP", "name": "VIEW_METAS"}
],
"object_list_mode": "all",
"comments": true,
"temperature": 0,
"max_tokens": 1500
}',
description => 'Restrige acesso a 2 visões relacionadas a vendas.'
);
END;
/
Neste comando acima, o perfil pode acessar duas visões. A primeira unifica a tabela clientes, vendas e lojas. A segunda limita colunas relacionadas a metas na tabela lojas (com objetivo de facilitar eventuais comparações entre meta mensal e venda mensal por loja).
Importante: Ao trabalhar com o framework do SELECT AI AGENT, cada objeto pode usar um perfil de IA diferente, permitindo limitar o acesso a dados, ou habilitar o uso de um modelo de IA específico para um determinado caso de uso. Por isso estamos criando diferentes perfils neste artigo.
Por fim, existem objetos agênticos que não precisam acessar nenhum dado em tabelas ou visões. Para esses casos, podemos criar um perfil que não tem os atributos “object_lists” e “object_list_mode”. Isto remete diretamente a questões de governaça e segregação de funções.
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'GENAI_APENAS_LLM',
attributes => '{
"provider": "oci",
"credential_name": "OCI_CRED_CLOUD",
"model": "xai.grok-4.3",
"temperature": 0,
"max_tokens": 1500
}',
description => 'Só permite interação com LLM, sem direito de acesso a dados diretamente.'
);
END;
/
Na prática, o perfil ‘GENAI_APENAS_LLM‘ pode interagir com o modelo de LLM cofigurado, mas os dados usados por ele deverão ser obrigatoriamente obtidos por outros objetos do framework que tenham permissão de acesso.
Antes de seguir, podemos validar se os perfis de IA estão chegando corretamente ao destino de inferência. Estes comandos já estão usando o SELECT AI, mesmo não lendo dados efetivamente. Note que cada comando usa um perfil de IA diferente.
SELECT DBMS_CLOUD_AI.GENERATE(
prompt => 'Responda somente: OK',
profile_name => 'GENAI_DADOS_GERAIS',
action => 'chat'
) AS resposta
FROM dual;
SELECT DBMS_CLOUD_AI.GENERATE(
prompt => 'Responda somente: OK',
profile_name => 'GENAI_DASHBOARD_VENDAS',
action => 'chat'
) AS resposta
FROM dual;
SELECT DBMS_CLOUD_AI.GENERATE(
prompt => 'Responda somente: OK',
profile_name => 'GENAI_APENAS_LLM',
action => 'chat'
) AS resposta
FROM dual;
Nestes comandos foi passado uma prompt que diz ao LLM ‘Responda somente: OK’. A ação Chat faz a API não realizar NL2SQL, apenas encaminhar o prompt diretamente ao modelo LLM configurado.
7) Carregar dados
Você precisa de alguns dados para teste, então execute os comandos abaixo como MYAPP.
BEGIN
EXECUTE IMMEDIATE q'[
CREATE TABLE clientes (
cliente_id NUMBER CONSTRAINT demo_clientes_pk PRIMARY KEY,
nome VARCHAR2(100) NOT NULL,
cidade VARCHAR2(60) NOT NULL,
estado CHAR(2) NOT NULL,
segmento VARCHAR2(30) NOT NULL,
data_cadastro DATE NOT NULL,
email VARCHAR2(120) NOT NULL,
CONSTRAINT demo_clientes_email_uk UNIQUE (email)
)
]';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN RAISE; END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE q'[
CREATE TABLE lojas (
loja_id NUMBER CONSTRAINT demo_lojas_pk PRIMARY KEY,
nome VARCHAR2(100) NOT NULL,
cidade VARCHAR2(60) NOT NULL,
estado CHAR(2) NOT NULL,
regiao VARCHAR2(15) NOT NULL,
data_abertura DATE NOT NULL,
meta_mensal NUMBER(12,2) NOT NULL,
ativa CHAR(1) DEFAULT 'S' NOT NULL,
CONSTRAINT demo_lojas_ativa_ck CHECK (ativa IN ('S', 'N'))
)
]';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN RAISE; END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE q'[
CREATE TABLE vendas (
venda_id NUMBER CONSTRAINT demo_vendas_pk PRIMARY KEY,
cliente_id NUMBER NOT NULL,
loja_id NUMBER NOT NULL,
data_venda DATE NOT NULL,
produto VARCHAR2(100) NOT NULL,
categoria VARCHAR2(40) NOT NULL,
quantidade NUMBER(5) NOT NULL,
valor_unitario NUMBER(12,2) NOT NULL,
desconto NUMBER(5,2) DEFAULT 0 NOT NULL,
valor_total NUMBER(12,2) NOT NULL,
canal VARCHAR2(15) NOT NULL,
forma_pagamento VARCHAR2(20) NOT NULL,
CONSTRAINT demo_vendas_cliente_fk FOREIGN KEY (cliente_id)
REFERENCES clientes (cliente_id),
CONSTRAINT demo_vendas_loja_fk FOREIGN KEY (loja_id)
REFERENCES lojas (loja_id),
CONSTRAINT demo_vendas_qtd_ck CHECK (quantidade > 0),
CONSTRAINT demo_vendas_desconto_ck CHECK (desconto BETWEEN 0 AND 100)
)
]';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN RAISE; END IF;
END;
/
-- Metadados úteis para o Select AI gerar SQL mais preciso.
COMMENT ON TABLE clientes IS 'Cadastro de clientes fictícios da rede varejista.';
COMMENT ON COLUMN clientes.segmento IS 'Segmento comercial do cliente tem os seguintes valores possíveis: Premium, Recorrente ou Novo. Use essa colunas para localizar clientes que a empresa considera especiais, clientes que fazem compras frequentes ou cliente que fizeram sua primeira compra.';
COMMENT ON TABLE lojas IS 'Lojas físicas fictícias da rede varejista.';
COMMENT ON COLUMN lojas.meta_mensal IS 'Meta de faturamento mensal da loja em reais. As palavras Cota, meta, ou target de venda estão no mesmo contexto e referenciam essa coluna.';
COMMENT ON TABLE vendas IS 'Vendas fictícias para análise comercial. Cada linha representa uma venda.';
COMMENT ON COLUMN vendas.valor_total IS 'Valor final da venda em reais, já considerado o desconto.';
COMMENT ON COLUMN vendas.canal IS 'Canal da venda: Loja Física, E-commerce ou WhatsApp.';
MERGE INTO clientes t
USING (
SELECT 1 cliente_id, 'Ana Martins' nome, 'São Paulo' cidade, 'SP' estado, 'Premium' segmento, DATE '2024-01-15' data_cadastro, 'ana.martins@exemplo.com' email FROM dual UNION ALL
SELECT 2, 'Bruno Almeida', 'Campinas', 'SP', 'Recorrente', DATE '2024-02-10', 'bruno.almeida@exemplo.com' FROM dual UNION ALL
SELECT 3, 'Carla Souza', 'Rio de Janeiro', 'RJ', 'Premium', DATE '2024-03-05', 'carla.souza@exemplo.com' FROM dual UNION ALL
SELECT 4, 'Diego Lima', 'Niterói', 'RJ', 'Novo', DATE '2024-04-22', 'diego.lima@exemplo.com' FROM dual UNION ALL
SELECT 5, 'Elisa Rocha', 'Belo Horizonte', 'MG', 'Recorrente', DATE '2024-05-14', 'elisa.rocha@exemplo.com' FROM dual UNION ALL
SELECT 6, 'Felipe Costa', 'Uberlândia', 'MG', 'Novo', DATE '2024-06-01', 'felipe.costa@exemplo.com' FROM dual UNION ALL
SELECT 7, 'Gabriela Santos', 'Curitiba', 'PR', 'Premium', DATE '2024-06-18', 'gabriela.santos@exemplo.com' FROM dual UNION ALL
SELECT 8, 'Henrique Melo', 'Londrina', 'PR', 'Recorrente', DATE '2024-07-09', 'henrique.melo@exemplo.com' FROM dual UNION ALL
SELECT 9, 'Isabela Freitas', 'Porto Alegre', 'RS', 'Novo', DATE '2024-08-03', 'isabela.freitas@exemplo.com' FROM dual UNION ALL
SELECT 10, 'João Pereira', 'Caxias do Sul', 'RS', 'Recorrente', DATE '2024-08-29', 'joao.pereira@exemplo.com' FROM dual UNION ALL
SELECT 11, 'Karina Nunes', 'Salvador', 'BA', 'Premium', DATE '2024-09-11', 'karina.nunes@exemplo.com' FROM dual UNION ALL
SELECT 12, 'Lucas Barros', 'Feira de Santana', 'BA', 'Novo', DATE '2024-10-07', 'lucas.barros@exemplo.com' FROM dual UNION ALL
SELECT 13, 'Mariana Alves', 'Recife', 'PE', 'Premium', DATE '2024-10-26', 'mariana.alves@exemplo.com' FROM dual UNION ALL
SELECT 14, 'Nicolas Ribeiro', 'Olinda', 'PE', 'Novo', DATE '2024-11-14', 'nicolas.ribeiro@exemplo.com' FROM dual UNION ALL
SELECT 15, 'Olivia Teixeira', 'Fortaleza', 'CE', 'Recorrente', DATE '2024-12-02', 'olivia.teixeira@exemplo.com' FROM dual UNION ALL
SELECT 16, 'Paulo Correia', 'Brasília', 'DF', 'Premium', DATE '2025-01-16', 'paulo.correia@exemplo.com' FROM dual UNION ALL
SELECT 17, 'Renata Castro', 'Goiânia', 'GO', 'Novo', DATE '2025-02-03', 'renata.castro@exemplo.com' FROM dual UNION ALL
SELECT 18, 'Sergio Araujo', 'Manaus', 'AM', 'Recorrente', DATE '2025-02-24', 'sergio.araujo@exemplo.com' FROM dual UNION ALL
SELECT 19, 'Tatiana Moraes', 'Florianópolis', 'SC', 'Premium', DATE '2025-03-12', 'tatiana.moraes@exemplo.com' FROM dual UNION ALL
SELECT 20, 'Victor Ramos', 'Joinville', 'SC', 'Novo', DATE '2025-04-08', 'victor.ramos@exemplo.com' FROM dual
) s
ON (t.cliente_id = s.cliente_id)
WHEN MATCHED THEN UPDATE SET
t.nome = s.nome, t.cidade = s.cidade, t.estado = s.estado,
t.segmento = s.segmento, t.data_cadastro = s.data_cadastro, t.email = s.email
WHEN NOT MATCHED THEN INSERT (cliente_id, nome, cidade, estado, segmento, data_cadastro, email)
VALUES (s.cliente_id, s.nome, s.cidade, s.estado, s.segmento, s.data_cadastro, s.email);
MERGE INTO lojas t
USING (
SELECT 1 loja_id, 'Loja Paulista' nome, 'São Paulo' cidade, 'SP' estado, 'Sudeste' regiao, DATE '2018-03-10' data_abertura, 18000 meta_mensal, 'S' ativa FROM dual UNION ALL
SELECT 2, 'Loja Carioca', 'Rio de Janeiro', 'RJ', 'Sudeste', DATE '2019-06-22', 15000, 'S' FROM dual UNION ALL
SELECT 3, 'Loja Mineira', 'Belo Horizonte', 'MG', 'Sudeste', DATE '2020-09-15', 12000, 'S' FROM dual UNION ALL
SELECT 4, 'Loja Sul', 'Curitiba', 'PR', 'Sul', DATE '2021-02-08', 10000, 'S' FROM dual UNION ALL
SELECT 5, 'Loja Nordeste', 'Salvador', 'BA', 'Nordeste', DATE '2022-11-19', 5000, 'S' FROM dual
) s
ON (t.loja_id = s.loja_id)
WHEN MATCHED THEN UPDATE SET
t.nome = s.nome, t.cidade = s.cidade, t.estado = s.estado, t.regiao = s.regiao,
t.data_abertura = s.data_abertura, t.meta_mensal = s.meta_mensal, t.ativa = s.ativa
WHEN NOT MATCHED THEN INSERT (loja_id, nome, cidade, estado, regiao, data_abertura, meta_mensal, ativa)
VALUES (s.loja_id, s.nome, s.cidade, s.estado, s.regiao, s.data_abertura, s.meta_mensal, s.ativa);
-- Gera 240 vendas distribuídas pelos últimos 180 dias, com produtos, canais e descontos variados.
DECLARE
v_produto VARCHAR2(100);
v_categoria VARCHAR2(40);
v_valor_unitario NUMBER(12,2);
v_quantidade NUMBER(5);
v_desconto NUMBER(5,2);
BEGIN
FOR i IN 1 .. 240 LOOP
CASE MOD(i, 6)
WHEN 0 THEN v_produto := 'Notebook Pro 14'; v_categoria := 'Eletrônicos'; v_valor_unitario := 4799.90;
WHEN 1 THEN v_produto := 'Smartphone Vision X'; v_categoria := 'Eletrônicos'; v_valor_unitario := 2399.90;
WHEN 2 THEN v_produto := 'Fone Bluetooth Air'; v_categoria := 'Acessórios'; v_valor_unitario := 349.90;
WHEN 3 THEN v_produto := 'Monitor Ultra 27'; v_categoria := 'Eletrônicos'; v_valor_unitario := 1599.90;
WHEN 4 THEN v_produto := 'Mochila Executiva'; v_categoria := 'Acessórios'; v_valor_unitario := 289.90;
WHEN 5 THEN v_produto := 'Teclado Mecânico Pro'; v_categoria := 'Acessórios'; v_valor_unitario := 499.90;
END CASE;
v_quantidade := MOD(i, 3) + 1;
v_desconto := CASE WHEN MOD(i, 7) = 0 THEN 15 WHEN MOD(i, 5) = 0 THEN 10 ELSE 0 END;
MERGE INTO vendas t
USING (
SELECT i AS venda_id,
MOD(i - 1, 20) + 1 AS cliente_id,
MOD(i - 1, 5) + 1 AS loja_id,
TRUNC(SYSDATE) - MOD(i * 7, 180) AS data_venda,
v_produto AS produto,
v_categoria AS categoria,
v_quantidade AS quantidade,
v_valor_unitario AS valor_unitario,
v_desconto AS desconto,
ROUND(v_quantidade * v_valor_unitario * (1 - v_desconto / 100), 2) AS valor_total,
CASE MOD(i, 3) WHEN 0 THEN 'Loja Física' WHEN 1 THEN 'E-commerce' ELSE 'WhatsApp' END AS canal,
CASE MOD(i, 4) WHEN 0 THEN 'Cartão' WHEN 1 THEN 'PIX' WHEN 2 THEN 'Boleto' ELSE 'Carteira Digital' END AS forma_pagamento
FROM dual
) s
ON (t.venda_id = s.venda_id)
WHEN MATCHED THEN UPDATE SET
t.cliente_id = s.cliente_id, t.loja_id = s.loja_id, t.data_venda = s.data_venda,
t.produto = s.produto, t.categoria = s.categoria, t.quantidade = s.quantidade,
t.valor_unitario = s.valor_unitario, t.desconto = s.desconto, t.valor_total = s.valor_total,
t.canal = s.canal, t.forma_pagamento = s.forma_pagamento
WHEN NOT MATCHED THEN INSERT (
venda_id, cliente_id, loja_id, data_venda, produto, categoria, quantidade,
valor_unitario, desconto, valor_total, canal, forma_pagamento
) VALUES (
s.venda_id, s.cliente_id, s.loja_id, s.data_venda, s.produto, s.categoria, s.quantidade,
s.valor_unitario, s.desconto, s.valor_total, s.canal, s.forma_pagamento
);
END LOOP;
END;
/
BEGIN
EXECUTE IMMEDIATE 'CREATE INDEX demo_vendas_data_ix ON vendas (data_venda)';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN RAISE; END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE 'CREATE INDEX demo_vendas_loja_ix ON vendas (loja_id)';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN RAISE; END IF;
END;
/
CREATE OR REPLACE VIEW view_vendas AS
SELECT
l.loja_id,
l.nome AS nome_loja,
l.cidade AS cidade_loja,
l.estado AS estado_loja,
l.regiao AS regiao_loja,
l.ativa AS status_loja,
v.venda_id,
v.data_venda,
v.produto AS produto_vendido,
v.categoria AS categoria_venda,
v.quantidade AS quantidade_venda,
v.valor_unitario AS valor_unitario_venda,
v.desconto AS desconto_pct,
ROUND(
v.valor_total * v.desconto / NULLIF(100 - v.desconto, 0),
2
) AS valor_desconto,
v.valor_total AS valor_total_venda,
v.canal AS canal_venda,
v.forma_pagamento AS forma_pagamento_venda,
c.cliente_id,
c.nome AS nome_cliente,
c.cidade AS cidade_cliente,
c.estado AS estado_cliente,
c.segmento
FROM vendas v
JOIN lojas l
ON l.loja_id = v.loja_id
JOIN clientes c
ON c.cliente_id = v.cliente_id;
CREATE OR REPLACE VIEW view_metas AS
SELECT
l.loja_id,
l.ativa AS status_loja,
l.meta_mensal
FROM lojas l;
-- comentários sobre view_vendas
COMMENT ON TABLE view_vendas IS
'Visão consolidada das vendas, lojas e clientes. Cada linha representa uma venda realizada.';
COMMENT ON COLUMN view_vendas.loja_id IS
'Identificador único da loja onde a venda foi realizada.';
COMMENT ON COLUMN view_vendas.nome_loja IS
'Nome da loja onde a venda foi realizada.';
COMMENT ON COLUMN view_vendas.cidade_loja IS
'Cidade onde a loja está localizada.';
COMMENT ON COLUMN view_vendas.estado_loja IS
'Estado brasileiro onde a loja está localizada.';
COMMENT ON COLUMN view_vendas.regiao_loja IS
'Região geográfica da loja, como Sudeste, Sul ou Nordeste.';
COMMENT ON COLUMN view_vendas.status_loja IS
'Situação da loja: S para ativa e N para inativa.';
COMMENT ON COLUMN view_vendas.venda_id IS
'Identificador único da venda.';
COMMENT ON COLUMN view_vendas.data_venda IS
'Data em que a venda foi realizada.';
COMMENT ON COLUMN view_vendas.produto_vendido IS
'Nome do produto vendido.';
COMMENT ON COLUMN view_vendas.categoria_venda IS
'Categoria comercial do produto vendido.';
COMMENT ON COLUMN view_vendas.quantidade_venda IS
'Quantidade de unidades vendidas.';
COMMENT ON COLUMN view_vendas.valor_unitario_venda IS
'Preço unitário do produto antes do desconto.';
COMMENT ON COLUMN view_vendas.desconto_pct IS
'Percentual de desconto aplicado à venda, de 0 a 100.';
COMMENT ON COLUMN view_vendas.valor_desconto IS
'Valor monetário do desconto concedido na venda, em reais.';
COMMENT ON COLUMN view_vendas.valor_total_venda IS
'Valor final da venda, em reais, já considerado o desconto. A palavra faturamento ou receita estão no mesmo contexto e referenciam essa coluna.';
COMMENT ON COLUMN view_vendas.canal_venda IS
'Canal em que a venda ocorreu, como Loja Física, E-commerce ou WhatsApp.';
COMMENT ON COLUMN view_vendas.forma_pagamento_venda IS
'Forma de pagamento utilizada na venda: Carteira Digital, Cartão, PIX ou Boleto.';
COMMENT ON COLUMN view_vendas.cliente_id IS
'Identificador único do cliente que realizou a compra.';
COMMENT ON COLUMN view_vendas.nome_cliente IS
'Nome do cliente que realizou a compra.';
COMMENT ON COLUMN view_vendas.cidade_cliente IS
'Cidade de residência do cliente.';
COMMENT ON COLUMN view_vendas.estado_cliente IS
'Estado de residência do cliente.';
COMMENT ON COLUMN view_vendas.segmento IS
'Segmento comercial do cliente, como Premium, Recorrente ou Novo.';
-- comentários sobre view_metas
COMMENT ON TABLE view_metas IS
'Visão das metas mensais de faturamento das lojas. Cada linha representa uma loja.';
COMMENT ON COLUMN view_metas.loja_id IS
'Identificador único da loja.';
COMMENT ON COLUMN view_metas.status_loja IS
'Situação da loja: S para ativa e N para inativa.';
COMMENT ON COLUMN view_metas.meta_mensal IS
'Meta de faturamento mensal da loja em reais. As palavras Cota, meta, ou target de venda estão no mesmo contexto e referenciam essa coluna.';
-- Dados Carregados
select count(1) from CLIENTES;
select count(1) from LOJAS;
select count(1) from VENDAS;
Note que foi incluindo alguns comentários para tabelas/visões e suas colunas. Isso é muito importante para que o modelo LLM definido no perfil de IA crie comandos SQL melhores em ações NL2SQL.
Por exemplo, a coluna meta_mensal da tabela lojas tem o seguinte comentário: ‘Meta de faturamento mensal da loja em reais. As palavras Cota ou Meta sozinhas também referenciam essa coluna.’. Com isso, o modelo sabe que uma pergunta como “Qual a cota das lojas de São Paulo?” deve ser respondida usando a coluna meta_mensal.
Os comentários também ajudam em filtros. Por exemplo, a coluna segmento da tabela clientes tem o seguinte comentário: ‘Segmento comercial do cliente tem os seguintes valores possíveis: Premium, Recorrente ou Novo. Use essa colunas para localizar clientes que a empresa considera especiais, clientes que fazem compras frequentes ou cliente que fizeram sua primeira compra.’. Com isso, ao responder uma pergunta como “Quais não meus clientes frequentes?” o comando SQL gerado pelo modelo deverá colocar algo como:
… FROM CLIENTES WHERE UPPER(SEGMENTO)= UPPER(‘Recorrente’);
8) Teste com NL2SQL
O comando abaixo gera uma código SQL que respode a pergunta: Quais nossos nossos clientes frequentes por estado? A ação showsql retorna o comando SQL gerado para responder a pergunta.
SELECT DBMS_CLOUD_AI.GENERATE(
prompt => 'Quais nossos clientes frequentes?',
profile_name => 'GENAI_DADOS_GERAIS',
action => 'showsql'
) AS resposta
FROM dual;
Outras opções de ação são:
- runsql : Executa o comando gerado imediatamente
- explainsql : Explica o que foi considerado para criar o comando SQL
- narrate : Envia os dados retornados pelo SQL executado para o LLM, e solicita que ele gere uma resposta natural usando o conteúdo.
Há muitas coisas interessantes sobre a função GENERATE do pacote DBMS_CLOUD_AI, incluindo outras opções de ação. Veja os detalhes aqui.
Resumo dos pré-requisitos realizados
- Criação um usuário MYAP e concessam de permissões (inclui pacotes DBMS_CLOUD* )
- Liberação de saída de rede do banco para o endpoint de inferência do OCI Generative AI
- Autorização de uso do OCI Generative AI através de políticas IAM
- Subscrição da região onde está o endpoint de inferência LLM usado
- Criação de credencial para uso das APIs do OCI Generative AI
- Criação de perfils de IA (controle de acesso a dados e definição de chamada ao LLM)
- Carga de dados para testes
- Validação da configuração feita usando SELECT AI (NL2SQL)
Usando o SELECT AI AGENT
Passamos por várias etapas preparatórias, e agora vamos realizar nossa primeira implementação de agentes dentro do Oracle AI Database. Para isso, precisamos lidar com 4 tipos de objetos do framework, são eles: Ferramentas, Tarefas, Agentes e Times.
Ferramentas (Tools)
Cada ferramenta entrega uma capacidade de ação. As opções hoje são:
- SQL: Converte linguagem natural em SQL para consultar dados autorizados (perfil de IA)
- RAG: Busca conteúdo em um índice vetorial e responde com base em documentos
- WEBSEARCH: Pesquisa informações na web
- NOTIFICATION: Envia notificações via Slack ou e-mail SMTP
- Chamada PL/SQL: Executa uma função criada no Oracle AI Database
A chamada PL/SQL dá grande flexibilidade pois é possível criar uma função dentro do Oracle AI Database que lê alguns dados, aplica alguma regra de negócio bem definida, usa alguns dados como entrada para um modelo de Machine Learning (classificação ou regressão), realiza alguns cálculos geo-espaciais, e por fim retorna um JSON com uma estrutura bem definida (como uma aplicação ou outro agente espera receber).
As ferramentas são criadas usando o pacote DBMS_CLOUD_AI_AGENT, subprograma CREATE_TOOL.
Rode este comando com o usuário MYAPP.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'OBTER_SQL_DADOS_GERAIS',
attributes => '{
"tool_type": "SQL",
"instruction": "Utilize apenas as ações showsql ou explainsql. Gere e apresente o SQL. Mostrar a explicação da construção do código SQL quando pedido, mas nunca execute a consulta. Se a solicitção não indicar uma ação explicitamente, use showsql por padrão.",
"tool_params": {
"profile_name": "GENAI_DADOS_CLIENTE"
}
}',
description => 'Gera SQL para CLIENTES, LOJAS e VENDAS sem executar.'
);
END;
/
No código temos:
- tool_name: Nome da ferramenta (OBTER_SQL_SOBRE_DADOS)
- attribute: inicia uma série de informações que definem como a ferramenta é usada.
- tool_type: Atributo que indica o que a ferramenta faz, exemplos são: SQL, RAG, WEBSEARCH ou NOTIFICATION. Como o valor no exemplo acima é SQL, a ferramenta usará o SELECT AI para realizar a ação envolvendo NL2SQL.
- instruction: É um prompt que explica como usar a ferramenta. Neste caso, estamos dizendo que a ferramenta não deve executa o SQL, apenas retornar o código gerado. Deste modo, ao chamar internamente a função “dbms_cloud_ai_agent.sql_tool”, a ferramenta sempre usa showsql ou explainsql como ação.
- tool_params: São os parâmetros de configuração da ferramenta
- profile_name: É o nome do perfil de IA que foi criado anteriormente. Note que cada ferramenta pode ter um perfil de IA específico, com locais de inferência diferentes, modelos diferentes, acessos a objetos de bancos diferentes, etc. Neste exemplo estamos usando o profile GENAI_DADOS_CLIENTE que permite acesso a duas tabelas (clientes e lojas).
- description: Apenas uma explicação para ajudar no entendimento do que a ferramenta faz.
Agora vamos criar outra ferramenta que seja capaz de gerar o código SQL, executá-lo, e enviar os dados lidos ao seu chamador. Note no código que a instrução da ferramenta mudou. O perfil de IA continua o mesmo (GENAI_DADOS_CLIENTE).
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'CONSULTAR_DADOS',
attributes => '{
"tool_type": "SQL",
"instruction": "Sempre use a ação runsql. Execute a consulta e apresente os dados retornados. Não mostre o código SQL.",
"tool_params": {
"profile_name": "GENAI_DADOS_CLIENTE"
}
}',
description => 'Consulta e retorna dados de CLIENTES, LOJAS e VENDAS.'
);
END;
/
Na seção de pré-requisitos foi criado um perfil chamado GENAI_DASHBOARD_VENDAS. Ele liberava acesso a duas visões (VIEW_VENDA e VIEW_METAS). Vamos criar uma tool para acessar estes dados.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'CONSULTAR_DASHBOARD_VENDAS',
attributes => '{
"tool_type": "SQL",
"instruction": "Use sempre a ação runsql. Use para consultar apenas dados relacionados a vendas e metas de vendas das lojas.",
"tool_params": {
"profile_name": "GENAI_DASHBOARD_VENDAS"
}
}',
description => 'Consulta dados do dashboard de vendas e metas.'
);
END;
/
A próxima ferramenta chamará a função PL/SQL CALCULAR_COMISSAO_LOJA. Ela calcula a comissão total que uma loja tem direito a receber conforme as vendas mensais.
O código da função agrupa vendas por mês e loja, compara as vendas com a meta da loja, e aplica a seguinte regra de cálculo: Vendas abaixo de 80% da meta = 0%; 80–99,99% = 1%; 100–119,99% = 2%; 120% ou mais = 3%. Os parâmetros de chamada da função são: p_loja_id (identificador da loja) e p_mes_ref (um texto que espera receber o formato YYYY-MM-DD para extrair o mês de referência).
Crie a função agora.
CREATE OR REPLACE FUNCTION calcular_comissao_loja (
p_loja_id IN lojas.loja_id%TYPE,
p_mes_ref IN VARCHAR2
) RETURN NUMBER
IS
l_mes_ref DATE;
l_meta lojas.meta_mensal%TYPE;
l_vendas NUMBER := 0;
l_percentual NUMBER := 0;
BEGIN
BEGIN
l_mes_ref := TO_DATE(p_mes_ref, 'FXYYYY-MM-DD');
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(
-20003,
'Data inválida. Informe no formato YYYY-MM-DD. Exemplo: 2026-09-01.'
);
END;
SELECT meta_mensal
INTO l_meta
FROM lojas
WHERE loja_id = p_loja_id;
SELECT NVL(SUM(valor_total), 0)
INTO l_vendas
FROM vendas
WHERE loja_id = p_loja_id
AND data_venda >= TRUNC(l_mes_ref, 'MM')
AND data_venda < ADD_MONTHS(TRUNC(l_mes_ref, 'MM'), 1);
IF l_vendas / l_meta >= 1.20 THEN
l_percentual := 0.03;
ELSIF l_vendas / l_meta >= 1.00 THEN
l_percentual := 0.02;
ELSIF l_vendas / l_meta >= 0.80 THEN
l_percentual := 0.01;
END IF;
RETURN ROUND(l_vendas * l_percentual, 2);
END;
/
Agora execute o código de criação da ferramenta.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'OBTER_COMISSAO_LOJA',
attributes => '{
"instruction": "Use esta ferramenta para calcular a comissão de uma loja em um mês informado pelo usuário. Solicite o mês no formato YYYY-MM-DD, usando preferencialmente o primeiro dia do mês.",
"function": "CALCULAR_COMISSAO_LOJA",
"tool_inputs": [
{
"name": "P_LOJA_ID",
"description": "Identificador numérico da loja."
},
{
"name": "P_MES_REF",
"description": "Data obrigatória como texto no formato exato YYYY-MM-DD. Exemplo: 2026-09-01. Não use nomes de meses, horários ou expressões como SYSDATE."
}
]
}',
description => 'Calcula a comissão mensal de uma loja conforme o atingimento de sua meta.'
);
END;
/
Dois novos atributos foram usados: “function” passando a função CALCULAR_COMISSAO_LOJA, e “tool_inputs” que explica são parâmetros de entrada da função e como eles devem ser usados.
Observe que o atributo “tool_type” não foi usado em conjunto com o atributo “function”. Veja também que não precisamos definir nenhum perfil de IA, pois na prática, essa ferramenta executa algo bem definido e retorna o resultado para seu chamador sem necessidade de LLM neste momento. Contudo, é possível incluir dentro de uma função eventuais chamadas a modelos de IA (LLM ou Embeddings).
Resumo do que criamos aqui (Ferramentas)

Tarefa (Task)
No SELECT AI AGENT, a tarefa define uma unidade de trabalho. Para isso precisamos informar seu objetivo, indicar quais ferramentas podem ser usadas, além da inclusão de eventuais regras a serem seguidas para sua correta execução.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TASK(
task_name => 'TASK_DADOS_CADASTRAIS',
attributes => '{
"instruction": "Responda à solicitação: {query}. Use apenas quando a pergunta estiver relacionada a dados cadastrais de clientes e lojas. Não use para perguntas relacionadas a detalhes de vendas realizadas. Ao usar CONSULTAR_DADOS garanta que ação é sempre runsql. ",
"tools": ["CONSULTAR_DADOS"],
"enable_human_tool": true
}',
description => 'Consulta dados gerais de cadastro de clientes e lojas.'
);
END;
/
Alguns pontos importantes:
- task_name: Nome da tarefa que estará disponível para uso pelos agentes
- instruction: Prompt que informa o que a tarefa deve fazer. Observe o texto {query}. Ele é um parâmentro interno que faz a pergunta do usuário ser incorporada ao prompt.
- tools: Indica quais ferramentas essa tarefa pode usar.
- enable_human_tool: permite que o agente pare a execução para pedir esclarecimentos ao usuário.
Agora criaremos uma tarefa que responde sobre vendas (lendo as visões criadas anteriormente). Veja que estamos usando a ferramenta CONSULTAR_DASHBOARD_VENDAS.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TASK(
task_name => 'TASK_DASHBOARD_VENDAS',
attributes => '{
"instruction": "Responda à solicitação: {query}. Use exclusivamente a ferramenta CONSULTAR_DASHBOARD_VENDAS para consultas sobre vendas ou eventuais comparações de vendas e metas das lojas. Execute a consulta e apresente os dados de forma objetiva. Se faltarem filtros importantes, como período ou loja, solicite esclarecimentos.",
"tools": ["CONSULTAR_DASHBOARD_VENDAS"],
"enable_human_tool": true
}',
description => 'Consulta dados do dashboard de vendas e metas.'
);
END;
/
Agora criaremos a tarefa CONSULTAR_COMISSAO_LOJA para obter o valor de comissão de uma loja.
Temos a função CALCULAR_COMISSAO_LOJA incluída na ferramenta OBTER_COMISSAO_LOJA para realizar o cálculo necessário. Lembre que essa função precisa receber o ID da loja e uma referência de mês para ser executada. Essa necessidade foi mencionada no prompt de instrução.
Execute o código abaixo:
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TASK(
task_name => 'CONSULTAR_COMISSAO_LOJA',
attributes => '{
"instruction": "Para responder à pergunta {query}, use exclusivamente a ferramenta OBTER_COMISSAO_LOJA. Informe o valor da comissão em reais. Se não houver loja ou mês, solicite essa informação.",
"tools": ["OBTER_COMISSAO_LOJA"],
"enable_human_tool": true
}',
description => 'Consulta a comissão mensal de uma loja.'
);
END;
/
Como temos “enable_human_tool”: true, então, se o usuário perguntar: “Qual é a comissão de venda da loja Paulista em Setembro de 2026?”, a resposta será algo como: “Informe o código da loja.”.
Se você deseja mudar esse comportamento, uma opção seria definir outra ferramenta na mesma tarefa. O código abaixo está usando as ferramentas CONSULTAR_DADOS e OBTER_COMISSAO_LOJA na mesma tarefa CONSULTAR_COMISSAO_LOJA_E_DADOS_GERAIS. Veja que o atributo “instruction” explica o que deve ser feito.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TASK(
task_name => 'CONSULTAR_COMISSAO_DADOS_GERAIS',
attributes => '{
"instruction": "Para responder à pergunta {query}, use a ferramenta CONSULTAR_DADOS para obter o identificador da loja se a pergunta nao vier com ele. Em seguida use OBTER_COMISSAO_LOJA. Informe o valor da comissão em reais.",
"tools": ["CONSULTAR_DADOS","OBTER_COMISSAO_LOJA"],
"enable_human_tool": true
}',
description => 'Consulta a comissão mensal de uma loja.'
);
END;
/
Resumo do que criamos aqui (Tarefas)

Visão geral das conexões que fizemos até agora entre tarefas e ferramentas:

Agente (Agent)
Um AGENTE é uma entidade inteligente que é capaz de executar tarefas.
Na criação devemos indicar o nome do agente, um prompt que define seu papel( role ), e um perfil de IA que permitirá a interação com um LLM.
Note que nos exemplos estamos usando o perfil de IA GENAI_APENAS_LLM (perfil que não acessa dados diretamente, apenas consome um ponto de inferência). O acesso aos dados são feitos pelas ferramentas (que tem o perfil de IA adequado para sua função/ação específica).
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_AGENT(
agent_name => 'AGENTE_DADOS_GERAIS',
attributes => '{
"profile_name": "GENAI_APENAS_LLM",
"role": "Você realiza consultas e retorna dados gerais relacionados a clientes e lojas. Não está em seu escopo acessar dados de vendas. Não invente uma resposta, use apenas o que tarefas e ferramentas autorizadas retornarem. Seja objetivo.",
"enable_human_tool": true
}',
description => 'Agente responsável por retornar dados relacionados a clientes e lojas.'
);
END;
/
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_AGENT(
agent_name => 'AGENTE_DASHBOARD_VENDAS',
attributes => '{
"profile_name": "GENAI_DASHBOARD_VENDAS",
"role": "Você é um agente especialista em análise de vendas e metas de lojas. Responda perguntas sobre vendas, metas, desempenho por loja, períodos, totais, médias e comparações comerciais. Use somente dados disponíveis no dashboard de vendas. Não altere dados. Se a pergunta não informar período, loja ou métrica suficiente, peça esclarecimentos antes de consultar."
}',
description => 'Agente especialista em consultas e análise do dashboard de vendas e metas.'
);
END;
/
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_AGENT(
agent_name => 'AGENTE_COMISSAO_LOJA',
attributes => '{
"profile_name": "GENAI_APENAS_LLM",
"role": "Você responde perguntas sobre o valor de comissões mensais das lojas. Retorne o valor da comissão em reais. Use exclusivamente as tarefas e ferramentas explicitamente autorizadas para retornar a comissão da loja. Observe que bonus da loja, distribuição de lucro, premiação, vitória da loja são termos que deve ser entendidos como comissão.",
"enable_human_tool": true
}',
description => 'Agente responsável por informar a comissão mensal das lojas.'
);
END;
/
Note que a descrição do papel (role) do agente AGENTE_COMISSAO_LOJA inclui um jargão específico da empresa, eles usam a palavra “vitória” como sinônimo de “comissão“.
Até agora que temos três agentes com três papeis diferentes:
- Acessar dados cadastrais de clientes e lojas
- Responder perguntas sobre vendas realizadas
- Obter o valor de comissão de venda
Vamos finalizar criando um agente orquestrador que irá coordenar a chamada ao agente mais adequado para responder a pergunta do usuário. Leia com atenção seu papel.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_AGENT(
agent_name => 'AGENTE_SUPERVISOR_LOJAS',
attributes => q'~{
"profile_name": "GENAI_APENAS_LLM",
"supervisor": true,
"enable_human_tool": true,
"role": "Você é o agente supervisor da rede de lojas. Sua função é entender a solicitação do usuário e delegar para o agente especialista correto. Use AGENTE_DADOS_GERAIS para dados cadastrais de clientes e lojas. Use AGENTE_DASHBOARD_VENDAS para vendas, metas, desempenho por loja, totais, médias, períodos e comparações comerciais. Use AGENTE_COMISSAO_LOJA para cálculo ou consulta de comissão mensal baseada no atingimento de metas das lojas. Não invente dados. Se faltarem informações essenciais, como loja, período ou mês de referência, solicite esclarecimento."
}~',
description => 'Agente supervisor para consultas cadastrais, vendas, metas e comissões da rede de lojas.'
);
END;
/
Resumo do que criamos aqui (Agentes)

Time (Team)
Um TIME serve para unir vários agentes especializados dentro de um mesmo fluxo de atendimento.
Os times são necesssário pois o usuário pode fazer perguntas sobre contextos muito diferentes, que acabam exigindo diferentes competências, por exemplo: consultar dados, interpretar regras de negócio, chamar funções que fazem coisa específicas, acessar documentos com pesquisa vetorial ou combinar resultados. Em vez de colocar todas essas responsabilidades em um agente gigante, você separa em agentes menores, cada um com seu papel, suas tarefa e ferramentas bem definidas, depois você agrupa adequadamente em times de atendimento.
Alguns objetivos dos times são:
- Organização: cada agente cuida de uma parte do problema.
- Governança: cada agente só acessa as ferramentas permitidas para sua função.
- Qualidade da resposta: o trabalho é direcionado para o especialista certo.
- Manutenção: fica mais fácil alterar uma tarefa ou ferramenta sem reescrever todo o agente.
- Escalabilidade: novos agentes podem ser adicionados ao time conforme surgem novos casos
Alguns pontos a destacar no código abaixo:
- “process”: “sequential” define que um agente deve concluir antes que outro seja acionado.
- “supervisor_agent” é o agente que tem o papel de delegar trabalho para os agentes do time.
- “agents” é uma lista que define quais agentes estão disponíveis no time para realizar tarefas
- Pares “name” + “task” informam o nome do agente que deve ser usado e quais tarefas cada agente está autorizado a usar.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TEAM(
team_name => 'TIME_REDE_LOJAS',
attributes => q'~{
"process": "sequential",
"supervisor_agent": "AGENTE_SUPERVISOR_LOJAS",
"agents": [
{
"name": "AGENTE_DADOS_GERAIS",
"task": "TASK_DADOS_CADASTRAIS"
},
{
"name": "AGENTE_DASHBOARD_VENDAS",
"task": "TASK_DASHBOARD_VENDAS"
},
{
"name": "AGENTE_COMISSAO_LOJA",
"task": "CONSULTAR_COMISSAO_LOJA"
}
]
}~',
description => 'Time supervisionado para consultas cadastrais, vendas, metas e comissões da rede de lojas.'
);
END;
/
Veja o fluxo que foi criado com o time TIME_REDE_LOJAS.

Você pode testar o time usando este código:
SET SERVEROUTPUT ON;
DECLARE
l_conversation_id VARCHAR2(128);
l_resposta CLOB;
BEGIN
l_conversation_id := DBMS_CLOUD_AI.CREATE_CONVERSATION();
l_resposta := DBMS_CLOUD_AI_AGENT.RUN_TEAM(
team_name => 'TIME_REDE_LOJAS',
user_prompt => 'Quais lojas temos na região Sul do país?',
params => '{"conversation_id":"' || l_conversation_id || '"}'
);
DBMS_OUTPUT.PUT_LINE(l_resposta);
END;
/
Recomendo que você realize outros testes alterando as perguntas. Veja algumas sugestões.
Acesso a dados cadastrais (usa AGENTE_DADOS_GERAIS)
- Quais informações cadastrais sobre nossas lojas tenho acesso?
- Quais são nossas lojas e onde estão localizadas?
- Quais as lojas com as 3 maiores metas? use o cadastro de lojas
- Quantas loja temos na região sudeste e quais são?
- Quais informações cadastrais sobre clientes tenho acesso?
- Quantos clientes temos por estado?
- Quais os segmentos de clientes que temos?
- Quais os clientes premium por estado?
- Temos algum cliente premium em Florianópolis? indique nome e email
- Temos algum cliente que tem Ramos no nome?
- Temos o telefone do cliente Victor Ramos?
- Quais dados temos do cliente victor ramos?
Acesso a dados de vendas (usa AGENTE_DASHBOARD_VENDAS)
- Quais são nossos canais de venda?
- Qual canal vendeu mais no mês de setembro de 2026?
- Mostre as vendas das lojas físicas em Setembro/26, agrupado por loja.
- Quais os 3 clientes que mais compram nas lojas físicas em Set/26?
- Quais os 3 clientes que mais compram independente do canal?
- Qual loja mais vendeu em unidades de produtos em Ago/26?
- Quais os 2 produtos mais vendidos na Loja Paulista em Agosto de 26?
- Qual o produto mais vendido em número de unidades nos últimos 3 meses?
- Qual loja teve maior receita em Set/26?
- Qual loja bateu sua meta de vendas em Setembro/26? Indique o percentual de atingimento.
- Mostre quantidade de vendas e valor total de venda dos últimos 6 meses para todas as lojas?
- Quais foram as vendas da Loja Mineira entre Abril e Setembro de 2026? Mostre o valor mês a mês. Comece a resposta dizendo: O resultado foi.
Executa função CALCULAR_COMISSAO_LOJA (usa AGENTE_COMISSAO_LOJA)
- Qual foi a commissão da loja Mineira no mês de Setembro de 2026?
- Qual o id da loja mineira?
- Qual foi a commissão da loja id 3?
- Qual foi a commissão da loja id 3 em setembro de 2026?
- Qual foi a vitória da loja id 3 em setembro/26?
- Qual foi a commissão da loja 3 no mês de Setembro e Agosto de 2026?
A pergunta “Qual foi a vitória da loja id 3 em setembro/26?” está usando aquele jargão comentado anteriormente, se beneficiando deste prompt “… Observe que bonus da loja, distribuição de lucro, premiação, vitória da loja são termos que deve ser entendidos como comissão…”.
Na pergunda “Qual foi a commissão da loja Mineira no mês de Setembro?” você deve ter percebido que o time não conseguiu responder a pergunta pois precisava receber explicitamente o ID da loja. Isso aconteceu pela forma como criamos a tarefa CONSULTAR_COMISSAO_LOJA. Veja abaixo como foi definida sua instrução e ferramentas autorizadas.
“instruction”: “Para responder à pergunta {query}, use exclusivamente a ferramenta OBTER_COMISSAO_LOJA. Informe o valor da comissão em reais. Se não houver loja ou mês, solicite essa informação.”
“tools”: [“OBTER_COMISSAO_LOJA”]
Já na tarefa CONSULTAR_COMISSAO_DADOS_GERAIS foi definido isto:
“instruction”: “Para responder à pergunta {query}, use a ferramenta CONSULTAR_DADOS para obter o identificador da loja se a pergunta não vier com ele. Em seguida use OBTER_COMISSAO_LOJA. Informe o valor da comissão em reais.”
“tools”: [“CONSULTAR_DADOS”,”OBTER_COMISSAO_LOJA”]
O código abaixo cria um novo time que usa justamente essa tarefa ajustada (com duas ferramentas).
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TEAM(
team_name => 'TIME_REDE_LOJAS_NOVO',
attributes => q'~{
"process": "sequential",
"supervisor_agent": "AGENTE_SUPERVISOR_LOJAS",
"agents": [
{
"name": "AGENTE_DADOS_GERAIS",
"task": "TASK_DADOS_CADASTRAIS"
},
{
"name": "AGENTE_DASHBOARD_VENDAS",
"task": "TASK_DASHBOARD_VENDAS"
},
{
"name": "AGENTE_COMISSAO_LOJA",
"task": "CONSULTAR_COMISSAO_DADOS_GERAIS"
}
]
}~',
description => 'Time supervisionado para consultas cadastrais, vendas, metas e comissões da rede de lojas.'
);
END;
/
Esse é o fluxo que criamos com o time TIME_REDE_LOJAS_NOVO:

Agora teste este time perguntando: “Qual foi a commissão da loja Mineira no mês de Agosto e Setembro de 2026?“. Espera-se que receba uma resposta como esta: “A comissão da loja Mineira foi de R$ 471,05 em agosto de 2026 e R$ 506,15 em setembro de 2026.”
DECLARE
l_conversation_id VARCHAR2(128);
l_resposta CLOB;
BEGIN
l_conversation_id := DBMS_CLOUD_AI.CREATE_CONVERSATION();
l_resposta := DBMS_CLOUD_AI_AGENT.RUN_TEAM(
team_name => 'TIME_REDE_LOJAS_NOVO',
user_prompt => 'qual foi a commissão da loja Mineira no mês de Agosto e Setembro de 2026?',
params => '{"conversation_id":"' || l_conversation_id || '"}'
);
DBMS_OUTPUT.PUT_LINE(l_resposta);
END;
/
Uma observação sobre a resposta. A função CALCULAR_COMISSAO_LOJA recebe ID da Loja e uma Data de Referência (de onde extrai o mês). Então, a ferramenta OBTER_COMISSAO_LOJA teve que ser chamada duas vezes pela tarefa CONSULTAR_COMISSAO_DADOS_GERAIS (Agosto e Setembro).
Note que os exemplos neste artigo tem o objetivo de mostrar as capacidades do SELECT AI AGENT que inclui a integração de times, agentes, tarefas e ferramentas. Algumas condições forçam determinados comportamentos (como o loop da função citada). Contudo, vale algumas reflexões importantes de uso:
a) Poderia ser introduzido um ajuste na função CALCULAR_COMISSAO_LOJA para resolver o nome da loja dentro dela mesmo, evitando ter que descobrir o ID da loja antes.
b) Poderia incluir na função o tratamento para receber um array de datas relevantes. Assim, o LLM usado pela tarefa poderia gerar e enviar para a função algo assim: [ “2026-08-01”, “2026-09-01” ] ).
Com isto a aplicação diminuiria o número de requisições e tokens consumidos. A economia ainda é potencializada no Oracle AI Database pela possibilidade de uso de vários modelos e tipos de dados armazenados em um único repositório, permitindo fazer muitas coisas com uma única chamada.
Você pode estar se perguntando: Como eu vejo todos esses objetos criados e o raciocínio durante execução de times, agentes e tarefas?
Aqui tem uma série de visões relevantes para isso:
-- setup
SELECT * FROM user_ai_agent_tools;
SELECT * FROM user_ai_agent_tool_attributes;
SELECT * FROM user_ai_agent_task;
SELECT * FROM user_ai_agent_task_attributes;
SELECT * FROM user_ai_agents;
SELECT * FROM user_ai_agent_attributes;
SELECT * FROM user_ai_agent_teams;
SELECT * FROM user_ai_agent_team_attributes;
-- Execução dos times
SELECT * FROM user_ai_agent_team_history;
SELECT * FROM user_ai_agent_task_history;
SELECT * FROM user_ai_agent_tool_history;
Importante: Você deve ter notado no código do teste a criação de uma conversa. Alguns comentários:
- É necessário uma conversa persistente para lembrar interações passadas. A conversa serve para guardar o contexto entre perguntas. Existem algumas visões úteis: user_cloud_ai_conversations, user_cloud_ai_conversation_prompts
- A conversa fica armazenada no Oracle AI Database por 7 dias (padrão do atributo retention_days). Você pode ajustar otempo quando você cria ou altera as definições de uma conversa.
- Você pode voltar na conversa, mesmo depois de desconectar da sessão. Veja o subprograma DBMS_CLOUD_AI.SET_CONVERSATION_ID.
- Veja a documentação da DBMS_CLOUD_AI para outras opções interessantes.
Antes de terminar, alguns exemplos que não poderiam faltar…
No inicio deste artigo foi criada a ferramenta OBTER_SQL_DADOS_GERAIS quer servia apenas para gerar código SQL (NL2SQL), sem executar a instrução SQL efetivamente. Reveja sua definição:
SELECT JSON_SERIALIZE(
DBMS_CLOUD_AI_AGENT.DESCRIBE_TOOL(
tool_name => 'OBTER_SQL_DADOS_GERAIS'
)
RETURNING CLOB PRETTY
) AS tool_json
FROM dual;
Observe alguns pontos na descrição retornada:
- A ferramenta é do tipo SQL ( “tool_type”: “SQL” ), e esse tipo precisa de um LLM associado.
- A associação do LLM foi feita através do perfil de IA. (“profile_name”:”GENAI_DADOS_CLIENTE”)
- A instrução da ferramenta recebeu um enriquecimento de prompt automaticamente.
- O atributo “function” : “dbms_cloud_ai_agent.sql_tool” também foi adicionado automaticamente (sem explicita referência no código de criação da ferramenta).
{
"id" : 67,
"attributes" :
{
"tool_type" : "SQL",
"tool_params" : "{\"profile_name\":\"GENAI_DADOS_CLIENTE\"}",
"instruction" : "This tool is used to work with SQL queries using natural language. Input should be a natural language query about data or database operations. The tool behavior depends on the configured action: RUNSQL - generates and executes the SQL query returning actual data; SHOWSQL - generates and displays the SQL statement without executing it; EXPLAINSQL - generates SQL and provides a natural language explanation of what the query does. Always provide clear, specific questions about the data you want to retrieve or analyze.\nUtilize apenas as ações showsql ou explainsql. Gere e apresente o SQL. Mostrar a explicação da construção do código SQL quando pedido, mas nunca execute a consulta. Se a solicitção não indicar uma ação explicitamente, use showsql por padrão.",
"function" : "dbms_cloud_ai_agent.sql_tool"
},
"function_args" :
[
{
"argument_name" : null,
"data_type" : "CLOB",
"in_out" : "OUT",
"position" : 0,
"defaulted" : "N"
},
{
"argument_name" : "TOOL_NAME",
"data_type" : "VARCHAR2",
"in_out" : "IN",
"position" : 1,
"defaulted" : "N"
},
{
"argument_name" : "QUERY",
"data_type" : "CLOB",
"in_out" : "IN",
"position" : 2,
"defaulted" : "N"
},
{
"argument_name" : "ACTION",
"data_type" : "VARCHAR2",
"in_out" : "IN",
"position" : 3,
"defaulted" : "N"
}
],
"description" : "Gera SQL para CLIENTES, LOJAS e VENDAS sem executar."
}
Vamos testar a ferramenta diretamente usando RUN_TOOL. Veja que o atributo input tem dois parâmetros: query e action. Isto é obrigatório na chamada da função “dbms_cloud_ai_agent.sql_tool”.
SELECT JSON_SERIALIZE(DBMS_CLOUD_AI_AGENT.RUN_TOOL(
tool_name => 'OBTER_SQL_DADOS_GERAIS',
input => '{
"query": "Quais os clientes no estado de São Paulo?" ,
"action": "explainsql"
}'
) RETURNING CLOB PRETTY ) AS resultado
FROM dual;
Aqui vale um comentário importante: Ao usar RUN_TOOL estamos descartando a LLM e fazendo uma chamada direta a ferramente (por isso tivemos que montar o atributo input). Quando usamos os times, agentes e tarefas em conjuto, o LLM associado a ferramenta é quem preparar o atributo input.
Para concluirmos, gostaria que mostrar a capacidade de chamar uma ferramenta diretamente no agente.
Até agora vimos agentes associados a tarefas durante a criação dos times. Isto significa que tarefas podem ser reaproveitadas entre diversos agentes, dando grande flexibilidade de reuso.
Contudo, você pode ter uma caso de uso mais simples, onde o reuso de tarefas não seja necessário. Neste caso, associar o agente a uma ferramenta diretamente talvez seja interessante para simplificar o design da solução.
Veja que o agente AGENTE_OBTER_SQL usa o atributo “tools”.
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_AGENT(
agent_name => 'AGENTE_OBTER_SQL',
attributes => q'~{
"profile_name": "GENAI_APENAS_LLM",
"role": "Você é um agente que ajuda a entender o código SQL pela ferramenta OBTER_SQL_DADOS_GERAIS. Você nunca executa comandos SQL diretamente na base de dados.",
"enable_human_tool": false,
"tools": ["OBTER_SQL_DADOS_GERAIS"]
}~',
description => 'Agente que chama diretamente a tool OBTER_SQL_DADOS_GERAIS sem usar task.'
);
END;
/
Isso leva a uma mudança na criação do time TIME_OBTER_SQL. Como o agente já tem uma ferramenta anexada, então, não é necessário citar o par agente + tarefa (como foi feito anteriormente).
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TEAM(
team_name => 'TIME_OBTER_SQL',
attributes => q'~{
"process": "sequential",
"agents": [ {"name": "AGENTE_OBTER_SQL" }]
}~',
description => 'Time com agente direto que chama tool sem task.'
);
END;
/
Faça um teste pedindo que a construção do código SQL seja explicada. Isso fará o parametro input ser construído pela ferramenta com “action”: “explainsql”.
SET SERVEROUTPUT ON;
DECLARE
l_conversation_id VARCHAR2(128);
l_response CLOB;
BEGIN
l_conversation_id := DBMS_CLOUD_AI.CREATE_CONVERSATION();
l_response := DBMS_CLOUD_AI_AGENT.RUN_TEAM(
team_name => 'TIME_OBTER_SQL',
user_prompt => 'Quais os clientes por estado? explique o código gerado',
params => '{"conversation_id":"' || l_conversation_id || '"}'
);
DBMS_OUTPUT.PUT_LINE(DBMS_LOB.SUBSTR(l_response, 4000, 1));
END;
/
Você pode testar a chamada acima usando as perguntas que vimos anteriormente neste artigo.
Veja o código SQL criado e as explicações de sua construção. Isto pode ajudar na análise, resolução de problemas e melhorias. Por exemplo, encontrando locais onde uma simplificação de acesso pode ajudar, ou melhorando o comentário de tabelas e colunas, ou ajustando determinados prompts.
Conclusão
Espero que esse artigo tenha fornecido fundamentos relevantes para criar uma aplicação interessante usando o SELECT AI AGENT do Oracle AI Database. Vimos muitas coisas, partindo dos pré-requisitos e experimentando alguns conceitos avançados.
Incluindo:
- Como instalar a familia de pacotes DBMS_CLOUD* (em ambientes não Autonomous Database)
- Permissões necessárias para uso do SELECT AI e SELECT AI AGENT
- Liberção de saída de rede para chegar no serviço OCI Generative AI
- Criação de credencial para uso das APIs do OCI Generative AI
- Criação de perfil de IA do Oracle AI Database
- Criação de objetos da aplicação e carga de dados (para realizar testes)
- Criação e uso de Ferramentas, Tarefas, Agentes e Times do SELECT AI AGENT
- Execução de testes tocando todos os agentes criados
- Algumas recomendações de segurança e boa prática de implementação
- Visões relevantes para obter informações de definição e uso
- Além de links para outros blogs e documentações relevantes.
Recomendo que revisite este material para fortalecer seu aprendizado.
