265 lines
10 KiB
SQL
265 lines
10 KiB
SQL
create or replace procedure prc_26AI_NL2SQL (
|
|
p_app_user in varchar2,
|
|
p_prompt in clob,
|
|
p_profile in varchar2,
|
|
p_action in varchar2 default 'narrate',
|
|
p_limit_rows in number default 100,
|
|
p_force_runsql in varchar2 default 'N',
|
|
p_result out clob
|
|
)
|
|
as
|
|
/*
|
|
|
|
Criado por: fernando.leal@oracle.com
|
|
Data: Oct/2025
|
|
Objetivo: demonstrar casos de uso do Oracle AI Database 26ai
|
|
|
|
v1 - rotina para controle de regras ao SelectAI - leal
|
|
|
|
begin
|
|
dbms_cloud_ai.create_profile(
|
|
profile_name => 'PROF_CENSO_V1',
|
|
attributes =>
|
|
'{"provider": "oci",
|
|
"credential_name": "OCI_CRED",
|
|
"oci_compartment_id": "ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
"region": "us-chicago-1",
|
|
"model": "meta.llama-4-maverick-17b-128e-instruct-fp8",
|
|
"oci_apiformat": "GENERIC",
|
|
"object_list_mode": "automated",
|
|
"object_list": [
|
|
{"owner": "AICHAT1", "name": "TB_26AI_CENSO"}
|
|
],
|
|
"enforce_object_list": true,
|
|
"constraints":"true",
|
|
"comments": true,
|
|
"annotations": true,
|
|
"temperature": 0.1
|
|
}'
|
|
);
|
|
end;
|
|
|
|
|
|
*/
|
|
v_response clob;
|
|
v_narrate clob;
|
|
v_pre_prompt_geral clob;
|
|
v_pre_prompt_contexto clob;
|
|
v_contexto clob;
|
|
|
|
v_categoria varchar2(200);
|
|
v_profile varchar2(200);
|
|
|
|
v_estimated_rows number;
|
|
--
|
|
-- Limite de linhas que pode ser narrado
|
|
--
|
|
v_limit_rows number := p_limit_rows;
|
|
--
|
|
-- caso numero de linhas seja muito alto (definido por v_limit_rows), a acao narrate sera alterada para runwsql automaticamente
|
|
--
|
|
v_force_runsql varchar2(1) := upper(p_force_runsql);
|
|
v_dt_start TIMESTAMP ;
|
|
v_runsql clob;
|
|
begin
|
|
|
|
v_dt_start := current_timestamp;
|
|
|
|
v_pre_prompt_geral := 'Sempre utilize alias para referencias nome da tabela. Nunca cite owner.';
|
|
|
|
--
|
|
-- Ajustar e definir as categorias existentes. As categorias irao definir pre prompts mais especificos
|
|
--
|
|
SELECT DBMS_CLOUD_AI.GENERATE(prompt => 'Com base na pergunta: "' || p_prompt || '", defina qual a melhor categoria para ela: "cidades","estados","população","censo","outros".
|
|
A resposta deve ser em JSON, e deve seguir este exemplo: { "categoria": "exemplo" }. APENAS ESTE JSON DEVE SER RETORNADO.',
|
|
profile_name => p_profile,
|
|
action => 'chat') response
|
|
INTO v_contexto;
|
|
|
|
begin
|
|
SELECT jt.categoria
|
|
INTO v_categoria
|
|
FROM JSON_TABLE(
|
|
replace(replace(v_contexto,'```',''),'json',''),
|
|
'$[*]' COLUMNS (
|
|
categoria VARCHAR2(50) PATH '$.categoria'
|
|
)
|
|
) jt;
|
|
exception
|
|
when others then
|
|
p_result := v_contexto;
|
|
end;
|
|
|
|
dbms_output.put_line('v_categoria: ' || v_categoria);
|
|
|
|
if v_categoria in ('cidades','estados','população','censo') then
|
|
|
|
v_pre_prompt_contexto := 'Filtros para nome de estados sempre devem ser com 2 caracteres em MAISUCULO. Exemplo: Minas Gerais como MG, Amazonas como AM, Pará como PA';
|
|
|
|
-- pode haver definicao de varios profiles, sendo que cada um estara associado a uma categoria especifica. Neste exemplo, herda da propria chamada
|
|
v_profile := 'PROF_CENSO_V3';
|
|
|
|
else
|
|
|
|
p_result := 'O prompt informado não faz referência aos dados que podem ser retornados. Contexto: ' || v_categoria;
|
|
|
|
end if;
|
|
|
|
dbms_output.put_line('v_profile: ' || v_profile);
|
|
|
|
--
|
|
-- deve ter havido uma categorizacao da questao com associacao a um profile do SelectAI
|
|
--
|
|
if v_profile is not null then
|
|
|
|
-- resposta do showsql para analise do comando
|
|
SELECT DBMS_CLOUD_AI.GENERATE(prompt => v_pre_prompt_geral || ' ' || v_pre_prompt_contexto || ' ' || p_prompt,
|
|
profile_name => v_profile,
|
|
action => 'showsql') response
|
|
INTO v_response;
|
|
|
|
if upper(v_response) like 'SELECT%' or upper(v_response) like 'WITH%' then
|
|
|
|
-- estimativa do numero de linahs retornadas
|
|
-- baseado em estatisticas das tabelas e indices
|
|
BEGIN
|
|
EXECUTE IMMEDIATE 'EXPLAIN PLAN SET STATEMENT_ID = ''plan_selectai'' FOR ' || v_response;
|
|
|
|
SELECT MAX(cardinality)
|
|
INTO v_estimated_rows
|
|
FROM plan_table
|
|
WHERE id = 0
|
|
AND STATEMENT_ID = 'plan_selectai';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
v_estimated_rows := 0;
|
|
END;
|
|
|
|
dbms_output.put_line('v_response: ' || v_response);
|
|
dbms_output.put_line('v_estimated_rows: ' || v_estimated_rows);
|
|
dbms_output.put_line('v_limit_rows: ' || v_limit_rows);
|
|
dbms_output.put_line('v_force_runsql: ' || v_force_runsql);
|
|
|
|
-- validacoes
|
|
-- if instr(upper(v_response),'WHERE') = 0 and instr(upper(v_response),'GROUP') = 0 then
|
|
|
|
--p_result := 'O prompt informado não tem nenhum tipo de filtro. Procure adicionar algum critério, como filtros por data, empresa, região, SKU, etc';
|
|
|
|
if v_estimated_rows > v_limit_rows then
|
|
|
|
--
|
|
-- colocar recurso pra trazer dados parcisi (ate X linhas )
|
|
--
|
|
|
|
if lower(p_action) = 'narrate' and v_force_runsql = 'Y' then
|
|
|
|
-- chamada principal do SelectAI
|
|
BEGIN
|
|
SELECT DBMS_CLOUD_AI.GENERATE(prompt => v_pre_prompt_geral || ' ' || v_pre_prompt_contexto || ' ' || p_prompt,
|
|
profile_name => v_profile,
|
|
action => 'runsql') response
|
|
INTO v_narrate;
|
|
EXCEPTION
|
|
WHEN NO_DATA_FOUND THEN
|
|
v_narrate := 'Desculpe, mas não há informações específicas sobre "' || p_prompt || '". Há regras de acesso definidas em seu perfil.';
|
|
END;
|
|
|
|
p_result := v_narrate;
|
|
|
|
else
|
|
|
|
p_result := 'Estimativa de linhas retornadas: ' || v_estimated_rows || '. Procure adicionar algum critério, como filtros por data, empresa, região, SKU, etc';
|
|
|
|
end if;
|
|
|
|
else
|
|
|
|
if p_action <> 'showsql' then
|
|
-- chamada principal do SelectAI
|
|
BEGIN
|
|
SELECT DBMS_CLOUD_AI.GENERATE(prompt => v_pre_prompt_geral || ' ' || v_pre_prompt_contexto || ' ' || p_prompt,
|
|
profile_name => v_profile,
|
|
action => p_action) response
|
|
INTO v_narrate;
|
|
|
|
/*
|
|
Feedback is available only on Oracle AI Database 26ai.
|
|
You can use it alongside the existing Select AI actions: runsql, showsql, and explainsql.
|
|
Ensure that your AI profile is configured for NL2SQL generation and not RAG.
|
|
*/
|
|
if p_action <> 'runsql' then
|
|
SELECT DBMS_CLOUD_AI.GENERATE(prompt => v_pre_prompt_geral || ' ' || v_pre_prompt_contexto || ' ' || p_prompt,
|
|
profile_name => v_profile,
|
|
action => 'runsql') response
|
|
INTO v_runsql;
|
|
end if;
|
|
|
|
EXCEPTION
|
|
WHEN NO_DATA_FOUND THEN
|
|
v_narrate := 'Desculpe, mas não há informações específicas sobre "' || p_prompt || '". Há regras de acesso definidas em seu perfil.';
|
|
END;
|
|
|
|
p_result := v_narrate;
|
|
|
|
else
|
|
|
|
p_result := v_response; -- sql ja gerado
|
|
|
|
|
|
end if;
|
|
|
|
|
|
end if;
|
|
|
|
else
|
|
|
|
p_result := 'Não foi possível gerar uma consulta para atender sua questão: ' || v_response;
|
|
|
|
end if; -- select
|
|
|
|
end if; -- v_profile
|
|
|
|
|
|
|
|
if p_result is not null then
|
|
|
|
--
|
|
-- log
|
|
--
|
|
insert into TB_26AI_CENSO_HISTORY(USERNAME,
|
|
DT,
|
|
PROMPT,
|
|
RESPONSE,
|
|
PROFILE,
|
|
NARRATE,
|
|
DT_START,
|
|
DT_END,
|
|
PROMPT_CHARS,
|
|
RESPONSE_CHARS
|
|
)
|
|
values( lower( p_app_user ) ,
|
|
sysdate ,
|
|
p_prompt ,
|
|
v_response ,
|
|
v_profile,
|
|
--case
|
|
-- when lower(p_action) = 'narrate' then
|
|
p_result,
|
|
-- end,
|
|
v_dt_start,
|
|
current_timestamp,
|
|
DBMS_LOB.GETLENGTH(p_prompt),
|
|
case
|
|
when lower(p_action) = 'narrate' then
|
|
DBMS_LOB.GETLENGTH(v_response) + DBMS_LOB.GETLENGTH(p_result)
|
|
else
|
|
DBMS_LOB.GETLENGTH(p_result)
|
|
end
|
|
) ;
|
|
commit;
|
|
end if; -- save do p_result
|
|
|
|
dbms_output.put_line('p_result: ' || p_result);
|
|
|
|
end;
|
|
/ |