add selectai

This commit is contained in:
Fernando Melo
2026-05-28 14:38:15 -03:00
parent 440b1cfadb
commit 052eb23d54
10 changed files with 581 additions and 112 deletions

View File

@@ -19,23 +19,25 @@ END blob_to_clob;
/ /
set serveroutput on
set define off
CREATE OR REPLACE PROCEDURE CONVERT_MDFILES_TO_JSON AS CREATE OR REPLACE PROCEDURE CONVERT_MDFILES_TO_JSON AS
l_md_content CLOB; l_md_content CLOB;
l_prompt CLOB; l_prompt CLOB;
l_final_json CLOB; l_final_json CLOB;
-- CHR(10) agrega saltos de linea al prompt. -- CHR(10) agrega saltos de linea al prompt enviado al modelo.
BEGIN BEGIN
SELECT md_clob INTO l_md_content SELECT md_clob INTO l_md_content
FROM markdown_files WHERE STATUS = 'INGESTED' FROM markdown_files WHERE STATUS = 'INGESTED'
AND ROWNUM = 1
FOR UPDATE; FOR UPDATE;
-- ===================================================================== -- =====================================================================
-- Llamada unica: metadatos de la promocion y lista de comercios -- Llamada unica: genera todo el JSON estructurado desde el markdown.
-- ===================================================================== -- =====================================================================
l_prompt := l_prompt :=
'Abaixo está um documento em markdown de uma promoção do ueno bank (Paraguai), em espanhol.' || CHR(10) || 'Abaixo está um documento em markdown de uma promoção do ueno bank (Paraguai), em espanhol.' || CHR(10) ||
'Extraia os metadados da promoção e TODOS os comércios em formato JSON estruturado.' || CHR(10) || CHR(10) || 'Extraia todos os dados relevantes da promoção em formato JSON estruturado.' || CHR(10) || CHR(10) ||
'Retorne APENAS o objeto JSON abaixo (sem markdown fences, sem texto antes ou depois):' || CHR(10) || 'Retorne APENAS o objeto JSON abaixo (sem markdown fences, sem texto antes ou depois):' || CHR(10) ||
'{' || CHR(10) || '{' || CHR(10) ||
' "promocao": {' || CHR(10) || ' "promocao": {' || CHR(10) ||
@@ -51,7 +53,7 @@ BEGIN
' "ecommerce_permitido": true' || CHR(10) || ' "ecommerce_permitido": true' || CHR(10) ||
' },' || CHR(10) || ' },' || CHR(10) ||
' "beneficios_por_nivel": [' || CHR(10) || ' "beneficios_por_nivel": [' || CHR(10) ||
' {"nivel": "nivel 1", "percentual_reintegro": 0, "monto_compra_minimo_gs": 0, "reintegro_maximo_gs": 0}' || CHR(10) || ' {"nivel": "nivel N", "percentual_reintegro": 0, "monto_compra_minimo_gs": 0, "reintegro_maximo_gs": 0}' || CHR(10) ||
' ],' || CHR(10) || ' ],' || CHR(10) ||
' "cuotas": {' || CHR(10) || ' "cuotas": {' || CHR(10) ||
' "maximo_sin_intereses": 0,' || CHR(10) || ' "maximo_sin_intereses": 0,' || CHR(10) ||
@@ -69,17 +71,16 @@ BEGIN
'}' || CHR(10) || CHR(10) || '}' || CHR(10) || CHR(10) ||
'REGRAS:' || CHR(10) || 'REGRAS:' || CHR(10) ||
'- Datas no formato ISO YYYY-MM-DD.' || CHR(10) || '- Datas no formato ISO YYYY-MM-DD.' || CHR(10) ||
'- Campos booleanos devem usar true ou false.' || CHR(10) || '- Campos booleanos devem usar true ou false sem aspas.' || CHR(10) ||
'- Campos numericos devem usar numbers, sem aspas.' || CHR(10) || '- Campos numéricos devem usar números, sem aspas.' || CHR(10) ||
'- Valores monetários em guaranies como integers (remover "Gs.", pontos e espaços).' || CHR(10) || '- Valores monetários em guaranies como integers (remover "Gs.", pontos e espaços).' || CHR(10) ||
'- Se um campo não aparecer no documento, use null.' || CHR(10) || '- Se um campo não aparecer no documento, use null.' || CHR(10) ||
'- exclusoes: liste apenas os principais pontos de exclusão em frases curtas.' || CHR(10) || CHR(10) || '- exclusoes: liste apenas os principais pontos de exclusão em frases curtas.' || CHR(10) ||
'REGRAS PARA comercios:' || CHR(10) || '- comercios: extraia TODOS os comércios que aparecem literalmente no documento.' || CHR(10) ||
'- Extraia TODOS os comércios que aparecem literalmente no documento.' || CHR(10) || '- comercios.category: seção em negrito mais próxima (**Belleza...**, **Grupo Zavidoro...**, etc).' || CHR(10) ||
'- category: seção em negrito mais próxima (**Belleza...**, **Grupo Zavidoro...**, etc).' || CHR(10) || '- comercios.payment_processor: "upay" ou "bancard" conforme aparece ao lado do nome.' || CHR(10) ||
'- payment_processor: "upay" ou "bancard" conforme aparece ao lado do nome.' || CHR(10) || '- Preserve os nomes dos comércios exatamente como aparecem.' || CHR(10) ||
'- Preserve os nomes exatamente como aparecem.' || CHR(10) || '- Não invente comércios nem complete nomes ausentes.' || CHR(10) || CHR(10) ||
'- Não invente comércios, categorias ou processadores de pagamento.' || CHR(10) || CHR(10) ||
'DOCUMENTO:' || CHR(10) || l_md_content; 'DOCUMENTO:' || CHR(10) || l_md_content;
l_final_json := DBMS_CLOUD_AI.GENERATE( l_final_json := DBMS_CLOUD_AI.GENERATE(
@@ -87,15 +88,12 @@ BEGIN
profile_name => 'PROFILE_GPT-5.5', profile_name => 'PROFILE_GPT-5.5',
action => 'chat'); action => 'chat');
DBMS_OUTPUT.PUT_LINE('Resposta bruta do LLM: ' || LENGTH(l_final_json) || ' chars');
IF l_final_json IS NOT JSON THEN IF l_final_json IS NOT JSON THEN
RAISE_APPLICATION_ERROR(-20210, RAISE_APPLICATION_ERROR(-20210,
'LLM retornou JSON inválido. Últimos 200 chars: ' || SUBSTR(l_final_json, -200)); 'Modelo retornou JSON inválido. Últimos 200 chars: ' || SUBSTR(l_final_json, -200));
END IF; END IF;
-- Guarda el JSON estructurado generado por el modelo. -- Graba el JSON completo generado por el modelo.
INSERT INTO parsed_markdown_files (data) VALUES (l_final_json); INSERT INTO parsed_markdown_files (data) VALUES (l_final_json);
UPDATE markdown_files SET STATUS = 'LOADED'; UPDATE markdown_files SET STATUS = 'LOADED';

View File

@@ -35,38 +35,55 @@ FROM parsed_markdown_files;
-- merchants -- merchants
SELECT docs.category, SELECT docs.category,
docs.name docs.name,
docs.payment_processor
FROM parsed_markdown_files, FROM parsed_markdown_files,
JSON_TABLE(data, '$.comercios[*]' JSON_TABLE(data, '$.comercios[*]'
COLUMNS ( COLUMNS (
name VARCHAR2(4000) PATH '$.name', name VARCHAR2(4000) PATH '$.name',
category VARCHAR2(4000) PATH '$.category' category VARCHAR2(4000) PATH '$.category',
payment_processor VARCHAR2(20) PATH '$.payment_processor'
)) docs )) docs
ORDER BY category, name; ORDER BY category, name;
-- beneficios por nivel -- beneficios por nivel
SELECT docs.nivel, SELECT docs.nivel,
docs.percentual_reintegro, docs.percentual_reintegro,
docs.monto_compra_minimo_gs,
docs.reintegro_maximo_gs docs.reintegro_maximo_gs
FROM parsed_markdown_files, FROM parsed_markdown_files,
JSON_TABLE(data, '$.beneficios_por_nivel[*]' JSON_TABLE(data, '$.beneficios_por_nivel[*]'
COLUMNS ( COLUMNS (
nivel VARCHAR2(100) PATH '$.nivel', nivel VARCHAR2(20) PATH '$.nivel',
percentual_reintegro NUMBER PATH '$.percentual_reintegro', percentual_reintegro NUMBER PATH '$.percentual_reintegro',
monto_compra_minimo_gs NUMBER PATH '$.monto_compra_minimo_gs',
reintegro_maximo_gs NUMBER PATH '$.reintegro_maximo_gs' reintegro_maximo_gs NUMBER PATH '$.reintegro_maximo_gs'
)) docs )) docs
ORDER BY 1; ORDER BY 1;
-- Tabela de benefícios
SELECT nivel, percentual_reintegro || '%' AS pct,
TO_CHAR(monto_compra_minimo_gs, '999G999G999') AS monto_min,
TO_CHAR(reintegro_maximo_gs, '999G999G999') AS reint_max
FROM parsed_markdown_files t,
JSON_TABLE(t.data, '$.beneficios_por_nivel[*]'
COLUMNS (
nivel VARCHAR2(20) PATH '$.nivel',
percentual_reintegro NUMBER PATH '$.percentual_reintegro',
monto_compra_minimo_gs NUMBER PATH '$.monto_compra_minimo_gs',
reintegro_maximo_gs NUMBER PATH '$.reintegro_maximo_gs'
))
ORDER BY nivel DESC;
-- Comércios por categoria -- Comércios por categoria
SELECT category, COUNT(*) AS qtd SELECT category, payment_processor, COUNT(*) AS qtd
FROM parsed_markdown_files t, FROM parsed_markdown_files t,
JSON_TABLE(t.data, '$.comercios[*]' JSON_TABLE(t.data, '$.comercios[*]'
COLUMNS ( COLUMNS (
category VARCHAR2(200) PATH '$.category' category VARCHAR2(200) PATH '$.category',
payment_processor VARCHAR2(20) PATH '$.payment_processor'
)) ))
GROUP BY category GROUP BY category, payment_processor
ORDER BY qtd DESC; ORDER BY qtd DESC;
@@ -75,37 +92,9 @@ SELECT
docs.data.promocao.titulo.string() as titulo, docs.data.promocao.titulo.string() as titulo,
docs.data.promocao.vigencia.data_inicio || ' a ' || docs.data.promocao.vigencia.data_fim as vigencia, docs.data.promocao.vigencia.data_inicio || ' a ' || docs.data.promocao.vigencia.data_fim as vigencia,
docs.data.medios_pago.rede as rede, docs.data.medios_pago.rede as rede,
docs.data.medios_pago.qr_permitido as qr_permitido docs.data.medios_pago.qr_permitido as qr_permitido,
docs.data.cuotas.maximo_sin_intereses as max_sin_intereses,
docs.data.acreditacao_reintegro.prazo_dias_habiles as prazo_dias_habiles
FROM parsed_markdown_files docs; FROM parsed_markdown_files docs;
/* --------------------------------
DUALITY VIEW
-------------------------------- */
-- convert
DECLARE
schema_sql clob;
BEGIN
schema_sql := dbms_json_duality.infer_and_generate_schema(
json('{"tableNames" : [ "PARSED_MARKDOWN_FILES" ],
"viewNames" : [ "T" ],
"useFlexFields" : false,
"updatability" : true,
"sourceSchema" : "APP_USER"}')
);
execute immediate schema_sql;
dbms_json_duality.import(table_name => 'PARSED_MARKDOWN_FILES', view_name => 'T');
execute immediate 'rename T TO V_PARSED_MARKDOWN_FILES';
END;
/
SELECT * FROM T_ROOT;
SELECT * FROM T_BENEFICIOS_POR_NIVEL;
SELECT * FROM T_COMERCIOS;

View File

@@ -0,0 +1,46 @@
set serveroutput on
set long 999999
set linesize 200
set pagesize 5000
/* --------------------------------
DUALITY VIEW
-------------------------------- */
-- convert
DECLARE
schema_sql clob;
BEGIN
schema_sql := dbms_json_duality.infer_and_generate_schema(
json('{"tableNames" : [ "PARSED_MARKDOWN_FILES" ],
"viewNames" : [ "T" ],
"useFlexFields" : false,
"updatability" : true,
"sourceSchema" : "APP_USER"}')
);
execute immediate schema_sql;
dbms_json_duality.import(table_name => 'PARSED_MARKDOWN_FILES', view_name => 'T');
execute immediate 'rename T TO V_PARSED_MARKDOWN_FILES';
END;
/
SELECT * FROM T_ROOT;
SELECT * FROM T_COMERCIOS;
SELECT * FROM T_BENEFICIOS_POR_NIVEL;
SELECT * FROM T_ACREDITACAO_REINTEGRO;
SELECT * FROM T_CUOTAS;
SELECT * FROM T_MEDIOS_PAGO;
SELECT * FROM T_PROMOCAO;
SELECT * FROM T_VIGENCIA;
-- duality view collection
SELECT json_serialize(data returning clob pretty) AS json_data
FROM v_parsed_markdown_files;

View File

@@ -0,0 +1,43 @@
EXEC DBMS_CLOUD_AI.DROP_PROFILE('PROFILE_LLAMA_4_17B');
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'PROFILE_LLAMA_4_17B',
attributes => JSON_OBJECT(
'provider' VALUE 'oci',
'credential_name' VALUE 'OCI$RESOURCE_PRINCIPAL',
'model' VALUE 'meta.llama-4-maverick-17b-128e-instruct-fp8',
'region' VALUE 'us-chicago-1',
'oci_compartment_id' VALUE 'ocid1.compartment.oc1..aaaaaaaa33ogmhasyvcqzuvkrfzo5mavh3q2le7mgwmy74yzpyqi7byxgrlq',
'temperature' VALUE 0,
'max_tokens' VALUE 4096
));
END;
/
EXEC DBMS_CLOUD_AI.DROP_PROFILE('PROFILE_GPT-5.5');
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'PROFILE_GPT-5.5',
attributes => JSON_OBJECT(
'provider' VALUE 'oci',
'credential_name' VALUE 'OCI$RESOURCE_PRINCIPAL',
'model' VALUE 'openai.gpt-5.5',
'region' VALUE 'us-chicago-1',
'oci_compartment_id' VALUE 'ocid1.compartment.oc1..aaaaaaaa33ogmhasyvcqzuvkrfzo5mavh3q2le7mgwmy74yzpyqi7byxgrlq',
'temperature' VALUE 1,
'max_tokens' VALUE 8192
));
END;
/
set define off
var response CLOB
exec :response := DBMS_CLOUD_AI.GENERATE(prompt => 'Hola', profile_name => 'PROFILE_GPT-5.5', action => 'chat');
print :response

View File

@@ -1,22 +0,0 @@
EXEC DBMS_CLOUD_AI.DROP_PROFILE('PROFILE_GPT-5.5');
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'PROFILE_GPT-5.5',
attributes => JSON_OBJECT(
'provider' VALUE 'oci',
'credential_name' VALUE 'OCI$RESOURCE_PRINCIPAL',
'model' VALUE 'openai.gpt-5.5',
'region' VALUE 'us-chicago-1',
'oci_compartment_id' VALUE 'ocid1.compartment.oc1..aaaaaaaa33ogmhasyvcqzuvkrfzo5mavh3q2le7mgwmy74yzpyqi7byxgrlq',
'temperature' VALUE 1,
'max_tokens' VALUE 8192
));
END;
/
set define off
var response CLOB
exec :response := DBMS_CLOUD_AI.GENERATE(prompt => 'Olá', profile_name => 'PROFILE_GPT-5.5', action => 'chat');
print :response

View File

@@ -0,0 +1,297 @@
set define off
-- ============================================================================
-- Lab: Annotations para SELECT AI
-- ============================================================================
-- Este script documenta tablas relacionales generadas desde una JSON COLLECTION
-- TABLE y la JSON Relational Duality View que recompone el documento.
--
-- SELECT AI usa nombres, relaciones y metadatos para generar SQL. Las
-- annotations ayudan a explicar el significado de tablas y columnas con
-- lenguaje de negocio, sinonimos y pistas de uso.
--
-- Nota: para ejecutar el lab mas de una vez, cambie ADD por REPLACE en las
-- annotations que ya existan.
-- ============================================================================
-- ============================================================================
-- T_ROOT: tabla raiz del documento JSON
-- ============================================================================
ALTER TABLE t_root ANNOTATIONS (
ADD display_name 'Documento de promocion',
ADD business_description 'Tabla raiz de cada promocion extraida desde markdown. Une promocion, medios de pago, cuotas, reintegro, exclusiones y listas hijas.',
ADD select_ai_hint 'Use esta tabla como punto de partida cuando la pregunta sea sobre una promocion completa o sobre condiciones generales.',
ADD example_questions 'Que promociones existen? Cuales son las exclusiones? Que banco emite la promocion?'
);
ALTER TABLE t_root MODIFY "_id" ANNOTATIONS (
ADD display_name 'Identificador del documento',
ADD business_description 'Clave tecnica RAW que identifica un documento JSON de promocion.',
ADD select_ai_hint 'No mostrar al usuario final salvo que pida identificadores tecnicos.'
);
ALTER TABLE t_root MODIFY exclusoes ANNOTATIONS (
ADD display_name 'Exclusiones de la promocion',
ADD business_description 'Lista JSON con reglas, restricciones y condiciones que excluyen compras o situaciones del beneficio.',
ADD synonyms 'restricciones, condiciones, limitaciones, no aplica, fuera de promocion',
ADD select_ai_hint 'Cuando el usuario pregunte que no aplica o que esta excluido, consulte este campo JSON.'
);
ALTER TABLE t_root MODIFY titulo_t_promocao ANNOTATIONS (
ADD display_name 'Titulo de la promocion',
ADD business_description 'Referencia al titulo principal de la promocion.',
ADD relationship_hint 'Clave foranea hacia T_PROMOCAO.TITULO.'
);
ALTER TABLE t_root MODIFY emissor_t_medios_pago ANNOTATIONS (
ADD display_name 'Emisor del medio de pago',
ADD business_description 'Banco o entidad emisora usada para enlazar los medios de pago aceptados.',
ADD relationship_hint 'Clave foranea hacia T_MEDIOS_PAGO.EMISSOR.'
);
ALTER TABLE t_root MODIFY maximo_sin_intereses_t_cuotas ANNOTATIONS (
ADD display_name 'Maximo de cuotas sin intereses',
ADD business_description 'Valor usado para enlazar las condiciones de cuotas sin intereses.',
ADD unit 'cuotas',
ADD relationship_hint 'Clave foranea hacia T_CUOTAS.MAXIMO_SIN_INTERESES.'
);
ALTER TABLE t_root MODIFY prazo_dias_habiles_t_acreditacao_reintegro ANNOTATIONS (
ADD display_name 'Plazo de acreditacion del reintegro',
ADD business_description 'Cantidad de dias habiles usada para enlazar la regla de acreditacion del reintegro.',
ADD unit 'dias habiles',
ADD relationship_hint 'Clave foranea hacia T_ACREDITACAO_REINTEGRO.PRAZO_DIAS_HABILES.'
);
-- ============================================================================
-- T_PROMOCAO y T_VIGENCIA: datos principales de la promocion
-- ============================================================================
ALTER TABLE t_promocao ANNOTATIONS (
ADD display_name 'Promocion',
ADD business_description 'Datos principales de la oferta bancaria, incluyendo titulo y vigencia.',
ADD select_ai_hint 'Use esta tabla para responder preguntas sobre el nombre de la promocion y sus fechas.'
);
ALTER TABLE t_promocao MODIFY titulo ANNOTATIONS (
ADD display_name 'Titulo',
ADD business_description 'Nombre oficial de la promocion segun el documento markdown.',
ADD synonyms 'nombre de promocion, campana, oferta'
);
ALTER TABLE t_promocao MODIFY data_inicio_t_vigencia ANNOTATIONS (
ADD display_name 'Fecha de inicio',
ADD business_description 'Fecha inicial de vigencia enlazada con T_VIGENCIA.',
ADD relationship_hint 'Parte de la clave compuesta hacia T_VIGENCIA.'
);
ALTER TABLE t_promocao MODIFY data_fim_t_vigencia ANNOTATIONS (
ADD display_name 'Fecha de fin',
ADD business_description 'Fecha final de vigencia enlazada con T_VIGENCIA.',
ADD relationship_hint 'Parte de la clave compuesta hacia T_VIGENCIA.'
);
ALTER TABLE t_vigencia ANNOTATIONS (
ADD display_name 'Vigencia',
ADD business_description 'Rango de fechas en que la promocion esta activa.',
ADD example_questions 'La promocion sigue vigente? Cuando termina la promocion?'
);
ALTER TABLE t_vigencia MODIFY data_inicio ANNOTATIONS (
ADD display_name 'Inicio de vigencia',
ADD business_description 'Primer dia en que la promocion puede ser aplicada.',
ADD unit 'fecha'
);
ALTER TABLE t_vigencia MODIFY data_fim ANNOTATIONS (
ADD display_name 'Fin de vigencia',
ADD business_description 'Ultimo dia en que la promocion puede ser aplicada.',
ADD unit 'fecha'
);
-- ============================================================================
-- T_MEDIOS_PAGO: tarjetas, QR, ecommerce y billeteras
-- ============================================================================
ALTER TABLE t_medios_pago ANNOTATIONS (
ADD display_name 'Medios de pago',
ADD business_description 'Condiciones de pago aceptadas por la promocion, como tarjetas, red, QR, ecommerce y billeteras electronicas.',
ADD select_ai_hint 'Use esta tabla para preguntas sobre como pagar para recibir el beneficio.'
);
ALTER TABLE t_medios_pago MODIFY emissor ANNOTATIONS (
ADD display_name 'Emisor',
ADD business_description 'Entidad emisora aceptada para participar de la promocion.',
ADD synonyms 'banco, entidad, issuer'
);
ALTER TABLE t_medios_pago MODIFY rede ANNOTATIONS (
ADD display_name 'Red de tarjeta',
ADD business_description 'Red o marca de tarjeta aceptada por la promocion.',
ADD synonyms 'marca, red, network'
);
ALTER TABLE t_medios_pago MODIFY qr_permitido ANNOTATIONS (
ADD display_name 'QR permitido',
ADD business_description 'Indica si el pago por QR participa de la promocion.',
ADD select_ai_hint 'Campo booleano: true significa que QR esta permitido.'
);
ALTER TABLE t_medios_pago MODIFY ecommerce_permitido ANNOTATIONS (
ADD display_name 'Ecommerce permitido',
ADD business_description 'Indica si compras online o ecommerce participan de la promocion.',
ADD select_ai_hint 'Campo booleano: true significa que ecommerce esta permitido.'
);
ALTER TABLE t_medios_pago MODIFY tarjetas_aceitas ANNOTATIONS (
ADD display_name 'Tarjetas aceptadas',
ADD business_description 'Array JSON con las tarjetas o productos de tarjeta que califican.',
ADD synonyms 'tarjetas validas, tarjetas admitidas, tarjetas participantes',
ADD select_ai_hint 'Use funciones JSON cuando necesite filtrar por una tarjeta especifica.'
);
ALTER TABLE t_medios_pago MODIFY billeteras_electronicas ANNOTATIONS (
ADD display_name 'Billeteras electronicas',
ADD business_description 'Array JSON con billeteras digitales aceptadas por la promocion.',
ADD synonyms 'wallets, billeteras, monederos digitales',
ADD select_ai_hint 'Use funciones JSON cuando el usuario pregunte por una billetera especifica.'
);
-- ============================================================================
-- T_CUOTAS: informacion de financiamiento
-- ============================================================================
ALTER TABLE t_cuotas ANNOTATIONS (
ADD display_name 'Cuotas',
ADD business_description 'Reglas de financiamiento sin intereses para la promocion.',
ADD example_questions 'Cuantas cuotas sin intereses permite la promocion? El cliente debe pedir las cuotas?'
);
ALTER TABLE t_cuotas MODIFY maximo_sin_intereses ANNOTATIONS (
ADD display_name 'Maximo sin intereses',
ADD business_description 'Cantidad maxima de cuotas sin intereses permitidas.',
ADD unit 'cuotas'
);
ALTER TABLE t_cuotas MODIFY requer_solicitacao_cliente ANNOTATIONS (
ADD display_name 'Requiere solicitud del cliente',
ADD business_description 'Indica si el cliente debe solicitar expresamente el pago en cuotas.',
ADD select_ai_hint 'Campo booleano: true significa que el cliente debe solicitarlo.'
);
-- ============================================================================
-- T_ACREDITACAO_REINTEGRO: como se acredita el beneficio
-- ============================================================================
ALTER TABLE t_acreditacao_reintegro ANNOTATIONS (
ADD display_name 'Acreditacion del reintegro',
ADD business_description 'Reglas de acreditacion del reintegro: plazo, porcentaje acreditado y tipo de cuenta.',
ADD select_ai_hint 'Use esta tabla para preguntas sobre cuando y donde se acredita el reintegro.'
);
ALTER TABLE t_acreditacao_reintegro MODIFY prazo_dias_habiles ANNOTATIONS (
ADD display_name 'Plazo en dias habiles',
ADD business_description 'Cantidad de dias habiles hasta acreditar el reintegro.',
ADD unit 'dias habiles',
ADD synonyms 'plazo, demora, tiempo de acreditacion'
);
ALTER TABLE t_acreditacao_reintegro MODIFY percentual_creditado ANNOTATIONS (
ADD display_name 'Porcentaje acreditado',
ADD business_description 'Porcentaje del reintegro que sera acreditado al cliente.',
ADD unit 'porcentaje'
);
ALTER TABLE t_acreditacao_reintegro MODIFY tipo_conta ANNOTATIONS (
ADD display_name 'Tipo de cuenta',
ADD business_description 'Cuenta o producto donde se acredita el reintegro.',
ADD synonyms 'cuenta destino, producto, cuenta de acreditacion'
);
-- ============================================================================
-- T_COMERCIOS: comercios participantes
-- ============================================================================
ALTER TABLE t_comercios ANNOTATIONS (
ADD display_name 'Comercios participantes',
ADD business_description 'Lista de comercios que participan de la promocion, con categoria y procesador de pago.',
ADD select_ai_hint 'Use esta tabla para preguntas sobre donde comprar, rubros participantes o procesadores de pago.'
);
ALTER TABLE t_comercios MODIFY name ANNOTATIONS (
ADD display_name 'Nombre del comercio',
ADD business_description 'Nombre del comercio tal como aparece en el documento original.',
ADD synonyms 'tienda, local, merchant, comercio'
);
ALTER TABLE t_comercios MODIFY category ANNOTATIONS (
ADD display_name 'Categoria del comercio',
ADD business_description 'Rubro o seccion del markdown donde aparece el comercio.',
ADD synonyms 'rubro, segmento, categoria, grupo'
);
ALTER TABLE t_comercios MODIFY payment_processor ANNOTATIONS (
ADD display_name 'Procesador de pago',
ADD business_description 'Procesador asociado al comercio, por ejemplo upay o bancard.',
ADD synonyms 'procesadora, adquirente, payment processor'
);
ALTER TABLE t_comercios MODIFY "_id_t_root" ANNOTATIONS (
ADD display_name 'Documento de origen',
ADD business_description 'Clave tecnica que enlaza el comercio con el documento raiz de la promocion.',
ADD relationship_hint 'Clave foranea hacia T_ROOT."_id".'
);
-- ============================================================================
-- T_BENEFICIOS_POR_NIVEL: reglas de reintegro por nivel
-- ============================================================================
ALTER TABLE t_beneficios_por_nivel ANNOTATIONS (
ADD display_name 'Beneficios por nivel',
ADD business_description 'Lista de niveles de cliente y sus reglas de reintegro, compra minima y tope maximo.',
ADD select_ai_hint 'Use esta tabla para comparar beneficios entre niveles.'
);
ALTER TABLE t_beneficios_por_nivel MODIFY nivel ANNOTATIONS (
ADD display_name 'Nivel del cliente',
ADD business_description 'Nivel o segmento del cliente dentro de la promocion.',
ADD synonyms 'segmento, nivel, categoria de cliente'
);
ALTER TABLE t_beneficios_por_nivel MODIFY percentual_reintegro ANNOTATIONS (
ADD display_name 'Porcentaje de reintegro',
ADD business_description 'Porcentaje de reintegro ofrecido para el nivel.',
ADD unit 'porcentaje'
);
ALTER TABLE t_beneficios_por_nivel MODIFY monto_compra_minimo_gs ANNOTATIONS (
ADD display_name 'Compra minima',
ADD business_description 'Monto minimo de compra requerido para aplicar el beneficio.',
ADD unit 'guaranies'
);
ALTER TABLE t_beneficios_por_nivel MODIFY reintegro_maximo_gs ANNOTATIONS (
ADD display_name 'Tope maximo de reintegro',
ADD business_description 'Monto maximo de reintegro que puede recibir el cliente en guaranies.',
ADD unit 'guaranies',
ADD synonyms 'tope, limite, maximo de devolucion'
);
ALTER TABLE t_beneficios_por_nivel MODIFY "_id_t_root" ANNOTATIONS (
ADD display_name 'Documento de origen',
ADD business_description 'Clave tecnica que enlaza el beneficio por nivel con el documento raiz de la promocion.',
ADD relationship_hint 'Clave foranea hacia T_ROOT."_id".'
);
-- ============================================================================
-- V_PARSED_MARKDOWN_FILES: vista JSON Relational Duality
-- ============================================================================
ALTER VIEW v_parsed_markdown_files ANNOTATIONS (
ADD display_name 'Documento JSON de promocion',
ADD business_description 'JSON Relational Duality View que presenta las tablas normalizadas como un unico documento JSON consultable y actualizable.',
ADD select_ai_hint 'Use esta vista cuando el usuario pregunte por el documento completo o quiera ver la estructura JSON de la promocion.',
ADD example_questions 'Muestre el JSON completo de la promocion. Que comercios aparecen dentro del documento? Que condiciones tiene la promocion?'
);

123
lab/selectai/03_select.sql Normal file
View File

@@ -0,0 +1,123 @@
set serveroutput on
set long 9999999
set pagesize 1000
set linesize 200
-- ============================================================================
-- Profile especializado para el lab de SELECT AI con annotations
-- ============================================================================
-- Este profile limita el contexto de SELECT AI a las tres tablas del ejercicio.
-- La opcion annotations=true envia las annotations de tablas y columnas al modelo.
-- enforce_object_list=true evita que el modelo use otras tablas accesibles.
BEGIN
DBMS_CLOUD_AI.DROP_PROFILE(
profile_name => 'PROFILE_GPT_5_5_SELECTAI',
force => TRUE
);
END;
/
BEGIN
DBMS_CLOUD_AI.CREATE_PROFILE(
profile_name => 'PROFILE_GPT_5_5_SELECTAI',
attributes => JSON_OBJECT(
'provider' VALUE 'oci',
'credential_name' VALUE 'OCI$RESOURCE_PRINCIPAL',
'model' VALUE 'openai.gpt-5.5',
'region' VALUE 'us-chicago-1',
'oci_compartment_id' VALUE 'ocid1.compartment.oc1..aaaaaaaa33ogmhasyvcqzuvkrfzo5mavh3q2le7mgwmy74yzpyqi7byxgrlq',
'temperature' VALUE 1,
'max_tokens' VALUE 8192,
'annotations' VALUE 'true',
'object_list_mode' VALUE 'all',
'enforce_object_list' VALUE 'true',
'object_list' VALUE JSON_ARRAY(
JSON_OBJECT('owner' VALUE USER, 'name' VALUE 'T_ROOT'),
JSON_OBJECT('owner' VALUE USER, 'name' VALUE 'T_COMERCIOS'),
JSON_OBJECT('owner' VALUE USER, 'name' VALUE 'T_BENEFICIOS_POR_NIVEL')
)
),
description => 'Profile para SELECT AI usando annotations y solamente T_ROOT, T_COMERCIOS y T_BENEFICIOS_POR_NIVEL.'
);
END;
/
-- ============================================================================
-- Lab: Consultas SELECT AI usando annotations
-- ============================================================================
-- Este script contiene ejemplos didacticos de SELECT AI.
--
-- La accion SHOWSQL muestra el SQL que el modelo genera. Esto ayuda a ver como
-- las annotations de display_name, business_description, synonyms y
-- relationship_hint guian la seleccion de tablas, columnas y joins.
-- SELECT AI modes:
-- showsql: cuando quieres obtener el SQL generado, revisarlo, auditarlo, registrarlo en logs o ejecutarlo después con control propio.
-- runsql: cuando aceptas que la base de datos genere y ejecute la consulta directamente.
-- narrate: cuando quieres una respuesta en lenguaje natural basada en el resultado.
-- explainsql: cuando quieres explicar el SQL generado.
-- chat: conversación general con el modelo, no necesariamente sobre SQL.
-- ============================================================================
EXEC DBMS_CLOUD_AI.SET_PROFILE('PROFILE_GPT_5_5_SELECTAI');
-- ============================================================================
-- Ejemplo 1: sinonimos de comercios y categorias
-- ============================================================================
-- Las annotations indican que T_COMERCIOS representa "Comercios participantes"
-- y que NAME tambien puede ser entendido como tienda, local, merchant o comercio.
-- El prompt usa lenguaje de negocio, no nombres tecnicos de columnas.
-- select ai
SELECT AI runsql Muestra los locales participantes por rubro, indicando el comercio, la categoria y la procesadora de pago.;
SELECT JSON_SERIALIZE(
DBMS_CLOUD_AI.GENERATE(
prompt => 'Muestra los locales participantes por rubro, indicando el comercio, la categoria y la procesadora de pago.',
profile_name => 'PROFILE_GPT_5_5_SELECTAI',
action => 'runsql'
) returning CLOB pretty);
-- dbms_cloud_ai
DECLARE
l_response CLOB;
BEGIN
l_response := DBMS_CLOUD_AI.GENERATE(
prompt => 'Muestra los locales participantes por rubro, indicando el comercio, la categoria y la procesadora de pago.',
profile_name => 'PROFILE_GPT_5_5_SELECTAI',
action => 'showsql'
);
DBMS_OUTPUT.PUT_LINE(DBMS_LOB.SUBSTR(l_response, 4000, 1));
END;
/
-- ============================================================================
-- Ejemplo 2: beneficio por segmento, compra minima y tope
-- ============================================================================
-- Las annotations explican que "nivel" tambien puede significar segmento o
-- categoria de cliente, y que "tope" es el reintegro maximo en guaranies.
-- El prompt usa esos sinonimos para mostrar como SELECT AI entiende el negocio.
SELECT AI runsql
Para cada segmento de cliente, lista el porcentaje de reintegro,
la compra minima requerida y el tope maximo de devolucion en guaranies.
;
-- ============================================================================
-- Ejemplo 3: pregunta de negocio cruzando comercios y niveles
-- ============================================================================
-- Este ejemplo combina tres tablas. Las relationship_hint de T_ROOT, T_COMERCIOS
-- y T_BENEFICIOS_POR_NIVEL ayudan al modelo a entender que ambas listas hijas
-- pertenecen al mismo documento de promocion.
SELECT AI runsql
Combine los comercios participantes con los beneficios por nivel de cliente del mismo documento.
Muestra rubro, comercio, procesadora, nivel del cliente, porcentaje de reintegro, compra minima
y limite maximo de devolucion.
;

View File

@@ -27,7 +27,7 @@ DECLARE
l_clob CLOB; l_clob CLOB;
l_blob BLOB; l_blob BLOB;
l_uri VARCHAR2(1000) := 'https://objectstorage.us-chicago-1.oraclecloud.com/n/idi1o0a010nx/b/md-processed/o/'; l_uri VARCHAR2(1000) := 'https://objectstorage.us-chicago-1.oraclecloud.com/n/idi1o0a010nx/b/md-processed/o/';
l_filename VARCHAR2(500) := 'Banco Atlas - Beneficios Estaciones de Servicio.md'; l_filename VARCHAR2(500) := 'Promocion-Reintegro-30-de-reintegro-en-comercios-adheridos-12-cuotas-sin-interese-_-DIC-2025v1.md';
BEGIN BEGIN
l_clob := blob_to_clob(DBMS_CLOUD.GET_OBJECT(credential_name => 'OCI$RESOURCE_PRINCIPAL', object_uri => l_uri || l_filename)); l_clob := blob_to_clob(DBMS_CLOUD.GET_OBJECT(credential_name => 'OCI$RESOURCE_PRINCIPAL', object_uri => l_uri || l_filename));

View File

@@ -13,7 +13,7 @@ update markdown_files
commit; commit;
select md_clob, embedding from markdown_files; select substr(md_clob, 50, 60) as md_preview, embedding from markdown_files;
@@ -23,7 +23,7 @@ select
VECTOR_DISTANCE(embedding, VECTOR_DISTANCE(embedding,
VECTOR_EMBEDDING( VECTOR_EMBEDDING(
MULTILINGUAL_E5_BASE MULTILINGUAL_E5_BASE
USING 'estaciones de servicio' as DATA) USING 'tarjeta de crédito' as DATA)
) as distance ) as distance
from markdown_files; from markdown_files;
@@ -68,7 +68,6 @@ FROM markdown_files m,
) )
) et; ) et;
commit;
-- ver los chunks generados -- ver los chunks generados
select doc_id, file_name, chunk_id, chunk_text select doc_id, file_name, chunk_id, chunk_text
@@ -83,7 +82,7 @@ select
chunk_id, chunk_id,
VECTOR_DISTANCE(embedding, VECTOR_DISTANCE(embedding,
VECTOR_EMBEDDING(MULTILINGUAL_E5_BASE VECTOR_EMBEDDING(MULTILINGUAL_E5_BASE
USING 'estaciones de servicio' as DATA USING 'tarjeta de crédito' as DATA
) )
) as distance, ) as distance,
chunk_text chunk_text

View File

@@ -1,6 +1,6 @@
set long 999999 set long 999999
set pages 1000 set pagesize 1000
set lines 200 set linesize 200
/* -------------------------------- /* --------------------------------
@@ -9,8 +9,6 @@ HNSW INDEX
-------------------------------- */ -------------------------------- */
-- crear indice HNSW para busquedas vectoriales aproximadas -- crear indice HNSW para busquedas vectoriales aproximadas
DROP INDEX IF EXISTS markdown_chunks_hnsw_idx;
CREATE VECTOR INDEX markdown_chunks_hnsw_idx CREATE VECTOR INDEX markdown_chunks_hnsw_idx
ON markdown_chunks (embedding) ON markdown_chunks (embedding)
ORGANIZATION INMEMORY NEIGHBOR GRAPH ORGANIZATION INMEMORY NEIGHBOR GRAPH
@@ -22,6 +20,7 @@ PARAMETERS (
EFCONSTRUCTION 200 EFCONSTRUCTION 200
); );
DROP INDEX IF EXISTS markdown_chunks_hnsw_idx;
/* -------------------------------- /* --------------------------------
@@ -30,8 +29,6 @@ IVF INDEX
-------------------------------- */ -------------------------------- */
-- crear indice IVF con DOC_ID incluido para filtros por documento -- crear indice IVF con DOC_ID incluido para filtros por documento
DROP INDEX IF EXISTS markdown_chunks_ivf_idx;
CREATE VECTOR INDEX markdown_chunks_ivf_idx CREATE VECTOR INDEX markdown_chunks_ivf_idx
ON markdown_chunks (embedding) ON markdown_chunks (embedding)
INCLUDE (doc_id) INCLUDE (doc_id)
@@ -44,7 +41,7 @@ PARAMETERS (
MIN_VECTORS_PER_PARTITION 1 MIN_VECTORS_PER_PARTITION 1
); );
DROP INDEX IF EXISTS markdown_chunks_ivf_idx;
/* -------------------------------- /* --------------------------------
TEXT INDEX + VECTOR SEARCH TEXT INDEX + VECTOR SEARCH
@@ -63,24 +60,22 @@ select
SCORE(1) as text_score, SCORE(1) as text_score,
VECTOR_DISTANCE(embedding, VECTOR_DISTANCE(embedding,
VECTOR_EMBEDDING(MULTILINGUAL_E5_BASE VECTOR_EMBEDDING(MULTILINGUAL_E5_BASE
USING 'estaciones de servicio' as DATA USING 'Sucursales adheridas' as DATA
) )
) as vector_distance, ) as vector_distance,
chunk_text chunk_text
from markdown_chunks from markdown_chunks
where contains(chunk_text, 'Beneficio where contains(chunk_text, '(Mastercard ACCUM NEAR((Dúo,Clásica),1))', 1) > 0
AND FUZZY(Benefcio)
AND ABOUT(estaciones)
AND (Visa accum Mastercard)
AND (Clásica OR Oro)
AND (crédito NOT débito)
AND NEAR((POS, Infonet), 5)
AND NEAR((App, Premmia, Petrobras), 1)
AND (consumo AND personal)', 1) > 0
order by vector_distance order by vector_distance
fetch first 3 rows only; fetch first 3 rows only;
-- Promoción
-- FUZZY(Promción)
-- ABOUT(Promoción)
-- NEAR((tarjetas, físicas, billeteras, electrónicas), 5)
-- Black OR Albirroja
-- BENDITA ACCUM STYLE
-- (Mastercard ACCUM NEAR((Dúo,Clásica),1))
/* -------------------------------- /* --------------------------------
@@ -105,12 +100,13 @@ SELECT JSON_SERIALIZE(
JSON('{ JSON('{
"hybrid_index_name" : "markdown_chunks_hybrid_idx", "hybrid_index_name" : "markdown_chunks_hybrid_idx",
"search_fusion" : "UNION", "search_fusion" : "UNION",
"search_scorer" : "rsf",
"vector" : { "vector" : {
"search_text" : "beneficios en estaciones de servicio", "search_text" : "Vigencia",
"search_mode" : "CHUNK" "search_mode" : "CHUNK"
}, },
"text" : { "text" : {
"contains" : "estaciones AND Petrobras" "contains" : "Shopping NEAR San NEAR Lorenzo"
}, },
"return" : { "return" : {
"values" : [ "values" : [
@@ -135,8 +131,8 @@ FROM
'hybrid_index_name' VALUE 'markdown_chunks_hybrid_idx', 'hybrid_index_name' VALUE 'markdown_chunks_hybrid_idx',
'search_fusion' VALUE 'INTERSECT', 'search_fusion' VALUE 'INTERSECT',
'search_scorer' VALUE 'rsf', 'search_scorer' VALUE 'rsf',
'vector' VALUE json_object('search_text' VALUE 'beneficios en estaciones de servicio'), 'vector' VALUE json_object('search_text' VALUE 'COMERCIOS ADHERIDOS'),
'text' VALUE json_object('contains' VALUE 'estaciones AND Petrobras'), 'text' VALUE json_object('contains' VALUE 'BENDITA ACCUM STYLE'),
'return' VALUE json_object( 'return' VALUE json_object(
'values' VALUE json_array('rowid', 'score', 'vector_score', 'vector_rank', 'text_score', 'text_rank', 'chunk_text'), 'values' VALUE json_array('rowid', 'score', 'vector_score', 'vector_rank', 'text_score', 'text_rank', 'chunk_text'),
'topN' VALUE 3 'topN' VALUE 3