359 lines
15 KiB
MySQL
359 lines
15 KiB
MySQL
-- Generate one reusable, question-specific Few-shot pattern per customer QA
|
|
-- benchmark without promoting the benchmark answer itself. Game identity is
|
|
-- deliberately not inferred here: sg_game_query_plan owns that through OCI
|
|
-- GenAI chat + the current game catalog.
|
|
|
|
CREATE OR REPLACE FUNCTION sg_qa_genai_generalize_pattern(
|
|
p_question IN CLOB,
|
|
p_answer_sql IN CLOB,
|
|
p_target_type IN VARCHAR2
|
|
) RETURN CLOB AUTHID DEFINER
|
|
IS
|
|
v_prompt CLOB;
|
|
v_result CLOB;
|
|
BEGIN
|
|
v_prompt :=
|
|
'Create one reusable, question-specific Few-shot SQL pattern from the source example. '
|
|
|| 'This is training guidance, never an answer key. Return exactly these tagged sections and nothing else: '
|
|
|| '[[PATTERN_QUESTION]], [[STRUCTURAL_SQL_PATTERN]], [[OBJECT_ROLE]], [[TARGET_TYPE]], '
|
|
|| '[[APPLICABILITY]], [[END]]. '
|
|
|| 'Preserve only the query intent and structural operations such as aggregation, joins, '
|
|
|| 'grouping, ordering, date semantics, and filters. Replace every game name, alias, game ID, '
|
|
|| 'schema name, physical object name, column name, literal date, literal number, user ID, '
|
|
|| 'currency amount, and expected output with semantic placeholders such as <GAME_SCOPE>, '
|
|
|| '<LOGICAL_FACT>, <LOGICAL_DIMENSION>, <METRIC>, <AS_OF_DATE>, <FILTER>, and <GROUPING>. '
|
|
|| 'In STRUCTURAL_SQL_PATTERN, every non-SQL identifier must be an angle-bracket placeholder: '
|
|
|| 'do not retain any source column, alias, table, schema, literal, code, or business value. '
|
|
|| 'Do not include executable SQL. Do not include a game name or a customer answer. '
|
|
|| 'The current game scope is supplied separately at runtime by a database OCI GenAI chat '
|
|
|| 'resolver, therefore never choose or imply a game. The TARGET_TYPE section must be one of NONE, '
|
|
|| 'SINGLE, MULTI, ALL, ANY and must describe applicability, not a game identity. '
|
|
|| 'Source target type from the current resolver: ' || NVL(p_target_type, 'ANY') || CHR(10)
|
|
|| 'Source question:' || CHR(10) || DBMS_LOB.SUBSTR(p_question, 4000, 1) || CHR(10)
|
|
|| 'Source SQL (structure only; do not copy identifiers or values):' || CHR(10)
|
|
|| DBMS_LOB.SUBSTR(p_answer_sql, 12000, 1);
|
|
v_result := DBMS_CLOUD_AI.GENERATE(
|
|
prompt => v_prompt,
|
|
profile_name => 'SGMP_POC_OCI_GPT54MINI',
|
|
action => 'chat'
|
|
);
|
|
RETURN v_result;
|
|
END;
|
|
/
|
|
|
|
CREATE OR REPLACE FUNCTION sg_qa_genai_validate_pattern(
|
|
p_pattern_json IN CLOB
|
|
) RETURN CLOB AUTHID DEFINER
|
|
IS
|
|
v_prompt CLOB;
|
|
v_result CLOB;
|
|
BEGIN
|
|
v_prompt :=
|
|
'Inspect only concrete-answer leakage in this reusable Few-shot pattern. Return exactly '
|
|
|| '[[CONCRETE_LEAKAGE]] YES or NO, then [[REASON]] and a short reason, then [[END]]. '
|
|
|| 'Return YES only when a customer answer, concrete game identity, physical schema/table/column '
|
|
|| 'identifier, literal date, literal business result, or executable SQL against a real object remains. '
|
|
|| 'Return NO when all such references are semantic angle-bracket placeholders. A pseudo-SQL pattern '
|
|
|| 'using SELECT/FROM/JOIN/GROUP BY, generic game-scope checks, EXISTS, UNION, or equality with '
|
|
|| 'angle-bracket placeholders is not concrete leakage and must return NO. Do not judge usefulness or '
|
|
|| 'completeness; classify leakage only. '
|
|
|| 'Candidate:' || CHR(10) || DBMS_LOB.SUBSTR(p_pattern_json, 16000, 1);
|
|
v_result := DBMS_CLOUD_AI.GENERATE(
|
|
prompt => v_prompt,
|
|
profile_name => 'SGMP_POC_OCI_GPT54MINI',
|
|
action => 'chat'
|
|
);
|
|
RETURN v_result;
|
|
END;
|
|
/
|
|
|
|
CREATE OR REPLACE FUNCTION sg_qa_generate_generalized_patterns
|
|
RETURN NUMBER AUTHID DEFINER
|
|
IS
|
|
PRAGMA AUTONOMOUS_TRANSACTION;
|
|
v_plan_raw CLOB;
|
|
v_plan JSON_OBJECT_T;
|
|
v_target_type VARCHAR2(16);
|
|
v_pattern_raw CLOB;
|
|
v_validation_raw CLOB;
|
|
v_status VARCHAR2(16);
|
|
v_validation_note CLOB;
|
|
v_question CLOB;
|
|
v_sql_pattern CLOB;
|
|
v_answer_text CLOB;
|
|
v_object_role VARCHAR2(64);
|
|
v_embedding_input CLOB;
|
|
v_embedding VECTOR;
|
|
v_count NUMBER := 0;
|
|
|
|
FUNCTION parse_json_result(p_value CLOB) RETURN JSON_OBJECT_T IS
|
|
v_text CLOB := TRIM(p_value);
|
|
BEGIN
|
|
IF DBMS_LOB.SUBSTR(v_text, 7, 1) = '```json' THEN
|
|
v_text := REGEXP_REPLACE(v_text, '^```json[[:space:]]*', '');
|
|
v_text := REGEXP_REPLACE(v_text, '[[:space:]]*```[[:space:]]*$', '');
|
|
ELSIF DBMS_LOB.SUBSTR(v_text, 3, 1) = '```' THEN
|
|
v_text := REGEXP_REPLACE(v_text, '^```[[:space:]]*', '');
|
|
v_text := REGEXP_REPLACE(v_text, '[[:space:]]*```[[:space:]]*$', '');
|
|
END IF;
|
|
RETURN JSON_OBJECT_T.parse(v_text);
|
|
END;
|
|
|
|
FUNCTION section_value(
|
|
p_raw IN CLOB, p_start_tag IN VARCHAR2, p_end_tag IN VARCHAR2
|
|
) RETURN CLOB IS
|
|
v_start PLS_INTEGER;
|
|
v_end PLS_INTEGER;
|
|
BEGIN
|
|
v_start := DBMS_LOB.INSTR(p_raw, p_start_tag, 1, 1);
|
|
IF v_start = 0 THEN
|
|
RAISE_APPLICATION_ERROR(-20071, 'OCI GenAI response is missing ' || p_start_tag);
|
|
END IF;
|
|
v_start := v_start + LENGTH(p_start_tag);
|
|
v_end := DBMS_LOB.INSTR(p_raw, p_end_tag, v_start, 1);
|
|
IF v_end = 0 OR v_end <= v_start THEN
|
|
RAISE_APPLICATION_ERROR(-20072, 'OCI GenAI response is missing ' || p_end_tag);
|
|
END IF;
|
|
RETURN TRIM(DBMS_LOB.SUBSTR(p_raw, LEAST(v_end - v_start, 32767), v_start));
|
|
END;
|
|
|
|
PROCEDURE upsert_pattern(
|
|
p_case_id IN VARCHAR2,
|
|
p_status IN VARCHAR2,
|
|
p_note IN CLOB
|
|
) IS
|
|
BEGIN
|
|
UPDATE sg_qa_vector_example
|
|
SET question = v_question,
|
|
answer_sql = v_sql_pattern,
|
|
answer_text = v_answer_text,
|
|
embedding_input = v_embedding_input,
|
|
embedding = v_embedding,
|
|
embedding_model = 'cohere.embed-v4.0',
|
|
reference_status = p_status,
|
|
reference_kind = 'SQL_PATTERN',
|
|
target_type = v_target_type,
|
|
object_role = v_object_role,
|
|
inspection_status = CASE WHEN p_status = 'APPROVED' THEN 'GENAI_VERIFIED' ELSE 'GENAI_REJECTED' END,
|
|
inspection_note = p_note,
|
|
verified_at = SYSTIMESTAMP,
|
|
verified_by = 'SGMP_POC_OCI_GENAI_PATTERN'
|
|
WHERE source_type = 'GENERALIZED_QUESTION_PATTERN'
|
|
AND source_case_id = 'PAT-' || p_case_id;
|
|
|
|
IF SQL%ROWCOUNT = 0 THEN
|
|
INSERT INTO sg_qa_vector_example (
|
|
question, answer_sql, answer_text, embedding_input, embedding, embedding_model,
|
|
reference_status, reference_kind, target_type, object_role, inspection_status,
|
|
inspection_note, verified_at, verified_by, source_case_id, source_type
|
|
) VALUES (
|
|
v_question, v_sql_pattern, v_answer_text, v_embedding_input, v_embedding, 'cohere.embed-v4.0',
|
|
p_status, 'SQL_PATTERN', v_target_type, v_object_role,
|
|
CASE WHEN p_status = 'APPROVED' THEN 'GENAI_VERIFIED' ELSE 'GENAI_REJECTED' END,
|
|
p_note, SYSTIMESTAMP, 'SGMP_POC_OCI_GENAI_PATTERN',
|
|
'PAT-' || p_case_id, 'GENERALIZED_QUESTION_PATTERN'
|
|
);
|
|
END IF;
|
|
END;
|
|
BEGIN
|
|
FOR source_row IN (
|
|
SELECT source.source_case_id, source.question, source.answer_sql
|
|
FROM sg_qa_vector_example source
|
|
WHERE source.source_type = 'CUSTOMER_QA_BENCHMARK'
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM sg_qa_vector_example pattern
|
|
WHERE pattern.source_type = 'GENERALIZED_QUESTION_PATTERN'
|
|
AND pattern.source_case_id = 'PAT-' || source.source_case_id
|
|
AND pattern.reference_status = 'APPROVED'
|
|
)
|
|
ORDER BY source_case_id
|
|
) LOOP
|
|
BEGIN
|
|
-- The target category comes from the existing OCI GenAI game resolver;
|
|
-- no alias, prefix, table, or name is transformed in this migration.
|
|
v_plan_raw := sg_game_query_plan(source_row.question, 5);
|
|
v_plan := parse_json_result(v_plan_raw);
|
|
v_target_type := UPPER(NVL(v_plan.get_string('targetType'), 'ANY'));
|
|
IF v_target_type NOT IN ('NONE', 'SINGLE', 'MULTI', 'ALL') THEN
|
|
v_target_type := 'ANY';
|
|
END IF;
|
|
|
|
v_pattern_raw := sg_qa_genai_generalize_pattern(
|
|
source_row.question, source_row.answer_sql, v_target_type
|
|
);
|
|
v_question := section_value(v_pattern_raw, '[[PATTERN_QUESTION]]', '[[STRUCTURAL_SQL_PATTERN]]');
|
|
v_sql_pattern := section_value(v_pattern_raw, '[[STRUCTURAL_SQL_PATTERN]]', '[[OBJECT_ROLE]]');
|
|
v_object_role := SUBSTR(section_value(v_pattern_raw, '[[OBJECT_ROLE]]', '[[TARGET_TYPE]]'), 1, 64);
|
|
IF section_value(v_pattern_raw, '[[TARGET_TYPE]]', '[[APPLICABILITY]]')
|
|
IN ('NONE', 'SINGLE', 'MULTI', 'ALL', 'ANY') THEN
|
|
v_target_type := section_value(v_pattern_raw, '[[TARGET_TYPE]]', '[[APPLICABILITY]]');
|
|
END IF;
|
|
v_answer_text := TO_CLOB('Generalized, question-specific structural pattern. '
|
|
|| 'Current game scope must be supplied only by sg_game_query_plan. Applicability: ')
|
|
|| section_value(v_pattern_raw, '[[APPLICABILITY]]', '[[END]]');
|
|
v_embedding_input := TO_CLOB('Question-specific generalized Few-shot pattern:' || CHR(10))
|
|
|| v_question || CHR(10) || 'Logical role: ' || v_object_role || CHR(10)
|
|
|| 'Structural SQL pattern:' || CHR(10) || v_sql_pattern;
|
|
v_embedding := DBMS_VECTOR.UTL_TO_EMBEDDING(
|
|
v_embedding_input, JSON(sg_qa_vector_params('search_document'))
|
|
);
|
|
v_validation_raw := sg_qa_genai_validate_pattern(v_pattern_raw);
|
|
v_status := CASE
|
|
WHEN REGEXP_SUBSTR(
|
|
UPPER(section_value(v_validation_raw, '[[CONCRETE_LEAKAGE]]', '[[REASON]]')),
|
|
'[A-Z]+'
|
|
) = 'NO'
|
|
THEN 'APPROVE'
|
|
ELSE 'REJECT'
|
|
END;
|
|
v_validation_note := section_value(v_validation_raw, '[[REASON]]', '[[END]]');
|
|
|
|
IF v_status = 'APPROVE' THEN
|
|
upsert_pattern(source_row.source_case_id, 'APPROVED',
|
|
'ADB OCI GenAI generated and independently validated a generalized pattern. '
|
|
|| 'The original customer QA remains evaluation-only. ' || v_validation_note);
|
|
v_count := v_count + 1;
|
|
ELSE
|
|
upsert_pattern(source_row.source_case_id, 'DRAFT',
|
|
'ADB OCI GenAI rejected the generalized pattern: ' || v_validation_note);
|
|
END IF;
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
-- Persist an auditable non-runtime draft and continue with the other
|
|
-- customer questions; one malformed LLM response must not block all 47.
|
|
v_question := source_row.question;
|
|
v_sql_pattern := TO_CLOB('<PATTERN_GENERATION_FAILED>');
|
|
v_answer_text := TO_CLOB('No runtime Few-shot pattern: OCI GenAI generalization failed.');
|
|
v_object_role := 'UNSPECIFIED';
|
|
v_target_type := 'ANY';
|
|
v_embedding_input := TO_CLOB('Failed generalized pattern: ') || source_row.question;
|
|
v_embedding := DBMS_VECTOR.UTL_TO_EMBEDDING(
|
|
v_embedding_input, JSON(sg_qa_vector_params('search_document'))
|
|
);
|
|
upsert_pattern(source_row.source_case_id, 'DRAFT',
|
|
'OCI GenAI pattern generation error: ' || SQLERRM);
|
|
END;
|
|
END LOOP;
|
|
COMMIT;
|
|
RETURN v_count;
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
ROLLBACK;
|
|
RAISE;
|
|
END;
|
|
/
|
|
|
|
-- Re-run only the independent OCI Chat safety review after its policy changes.
|
|
-- It never reads a customer benchmark and never changes the generated pattern.
|
|
CREATE OR REPLACE FUNCTION sg_qa_revalidate_generalized_patterns
|
|
RETURN NUMBER AUTHID DEFINER
|
|
IS
|
|
PRAGMA AUTONOMOUS_TRANSACTION;
|
|
v_raw CLOB;
|
|
v_status VARCHAR2(16);
|
|
v_reason CLOB;
|
|
v_start PLS_INTEGER;
|
|
v_end PLS_INTEGER;
|
|
v_count NUMBER := 0;
|
|
|
|
FUNCTION section_value(
|
|
p_raw IN CLOB, p_start_tag IN VARCHAR2, p_end_tag IN VARCHAR2
|
|
) RETURN CLOB IS
|
|
v_from PLS_INTEGER;
|
|
v_to PLS_INTEGER;
|
|
BEGIN
|
|
v_from := DBMS_LOB.INSTR(p_raw, p_start_tag, 1, 1);
|
|
IF v_from = 0 THEN RAISE_APPLICATION_ERROR(-20073, 'Missing ' || p_start_tag); END IF;
|
|
v_from := v_from + LENGTH(p_start_tag);
|
|
v_to := DBMS_LOB.INSTR(p_raw, p_end_tag, v_from, 1);
|
|
IF v_to = 0 OR v_to <= v_from THEN RAISE_APPLICATION_ERROR(-20074, 'Missing ' || p_end_tag); END IF;
|
|
RETURN TRIM(DBMS_LOB.SUBSTR(p_raw, LEAST(v_to - v_from, 32767), v_from));
|
|
END;
|
|
BEGIN
|
|
FOR item IN (
|
|
SELECT example_id, question, answer_sql, answer_text
|
|
FROM sg_qa_vector_example
|
|
WHERE source_type = 'GENERALIZED_QUESTION_PATTERN'
|
|
ORDER BY source_case_id
|
|
) LOOP
|
|
BEGIN
|
|
v_raw := sg_qa_genai_validate_pattern(
|
|
TO_CLOB('[[PATTERN_QUESTION]]') || item.question
|
|
|| TO_CLOB(CHR(10) || '[[STRUCTURAL_SQL_PATTERN]]') || item.answer_sql
|
|
|| TO_CLOB(CHR(10) || '[[APPLICABILITY]]') || item.answer_text || CHR(10) || '[[END]]'
|
|
);
|
|
v_status := CASE
|
|
WHEN REGEXP_SUBSTR(
|
|
UPPER(section_value(v_raw, '[[CONCRETE_LEAKAGE]]', '[[REASON]]')),
|
|
'[A-Z]+'
|
|
) = 'NO'
|
|
THEN 'APPROVE'
|
|
ELSE 'REJECT'
|
|
END;
|
|
v_reason := section_value(v_raw, '[[REASON]]', '[[END]]');
|
|
UPDATE sg_qa_vector_example
|
|
SET reference_status = CASE WHEN v_status = 'APPROVE' THEN 'APPROVED' ELSE 'DRAFT' END,
|
|
inspection_status = CASE WHEN v_status = 'APPROVE' THEN 'GENAI_VERIFIED' ELSE 'GENAI_REJECTED' END,
|
|
inspection_note = 'ADB OCI GenAI independent revalidation: ' || v_reason,
|
|
verified_at = SYSTIMESTAMP,
|
|
verified_by = 'SGMP_POC_OCI_GENAI_PATTERN'
|
|
WHERE example_id = item.example_id;
|
|
IF v_status = 'APPROVE' THEN v_count := v_count + 1; END IF;
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
v_reason := TO_CLOB('OCI GenAI revalidation error: ' || SQLERRM);
|
|
UPDATE sg_qa_vector_example
|
|
SET reference_status = 'DRAFT',
|
|
inspection_status = 'GENAI_REJECTED',
|
|
inspection_note = v_reason,
|
|
verified_at = SYSTIMESTAMP,
|
|
verified_by = 'SGMP_POC_OCI_GENAI_PATTERN'
|
|
WHERE example_id = item.example_id;
|
|
END;
|
|
END LOOP;
|
|
COMMIT;
|
|
RETURN v_count;
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
ROLLBACK;
|
|
RAISE;
|
|
END;
|
|
/
|
|
|
|
-- Production retrieval accepts only independently generalized patterns or
|
|
-- policy templates. Customer QA benchmarks remain evaluation-only forever.
|
|
CREATE OR REPLACE FUNCTION sg_qa_vector_search(
|
|
p_question IN CLOB,
|
|
p_top_k IN PLS_INTEGER DEFAULT 3,
|
|
p_target_type IN VARCHAR2 DEFAULT 'ANY'
|
|
) RETURN SYS_REFCURSOR AUTHID DEFINER
|
|
IS
|
|
v_query_vector VECTOR;
|
|
v_results SYS_REFCURSOR;
|
|
v_target_type VARCHAR2(16) := UPPER(TRIM(NVL(p_target_type, 'ANY')));
|
|
BEGIN
|
|
IF p_question IS NULL THEN RAISE_APPLICATION_ERROR(-20003, 'question is required.'); END IF;
|
|
IF p_top_k IS NULL OR p_top_k < 1 OR p_top_k > 20 THEN
|
|
RAISE_APPLICATION_ERROR(-20004, 'top_k must be between 1 and 20.');
|
|
END IF;
|
|
IF v_target_type NOT IN ('NONE', 'SINGLE', 'MULTI', 'ALL', 'ANY') THEN
|
|
RAISE_APPLICATION_ERROR(-20005, 'invalid target type.');
|
|
END IF;
|
|
v_query_vector := DBMS_VECTOR.UTL_TO_EMBEDDING(
|
|
p_question, JSON(sg_qa_vector_params('search_query'))
|
|
);
|
|
OPEN v_results FOR
|
|
SELECT example_id, question, answer_sql, answer_text, embedding_model,
|
|
reference_kind, target_type, object_role, source_case_id, source_type,
|
|
vector_distance(embedding, v_query_vector, COSINE) AS cosine_distance
|
|
FROM sg_qa_vector_example
|
|
WHERE reference_status = 'APPROVED'
|
|
AND source_type IN ('GENERALIZED_QUESTION_PATTERN', 'POLICY_TEMPLATE')
|
|
AND (target_type = 'ANY' OR v_target_type = 'ANY' OR target_type = v_target_type)
|
|
ORDER BY vector_distance(embedding, v_query_vector, COSINE), example_id
|
|
FETCH FIRST p_top_k ROWS ONLY;
|
|
RETURN v_results;
|
|
END;
|
|
/
|