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; /