@@ -0,0 +1,90 @@
|
||||
-- Creates non-operational OCI profiles for game-scope extraction benchmarks.
|
||||
-- Every profile inherits the active GPT profile's OCI credential, region,
|
||||
-- object list, and metadata. Only model is varied.
|
||||
|
||||
DECLARE
|
||||
v_credential_name VARCHAR2(128);
|
||||
v_region VARCHAR2(128);
|
||||
v_compartment_id VARCHAR2(4000);
|
||||
v_attributes CLOB;
|
||||
v_attribute_json JSON_OBJECT_T;
|
||||
v_exists PLS_INTEGER;
|
||||
|
||||
PROCEDURE create_candidate(
|
||||
p_profile_name IN VARCHAR2,
|
||||
p_model IN VARCHAR2
|
||||
) IS
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_exists
|
||||
FROM user_cloud_ai_profiles
|
||||
WHERE profile_name = p_profile_name;
|
||||
|
||||
IF v_exists > 0 THEN
|
||||
DBMS_CLOUD_AI.DROP_PROFILE(profile_name => p_profile_name, force => TRUE);
|
||||
END IF;
|
||||
|
||||
v_attribute_json := JSON_OBJECT_T();
|
||||
v_attribute_json.put('provider', 'oci');
|
||||
v_attribute_json.put('credential_name', v_credential_name);
|
||||
v_attribute_json.put('model', p_model);
|
||||
v_attribute_json.put('region', v_region);
|
||||
v_attribute_json.put('oci_compartment_id', v_compartment_id);
|
||||
v_attributes := v_attribute_json.to_clob;
|
||||
|
||||
DBMS_CLOUD_AI.CREATE_PROFILE(
|
||||
profile_name => p_profile_name,
|
||||
attributes => v_attributes,
|
||||
description => 'Non-operational Smilegate game-scope benchmark profile'
|
||||
);
|
||||
|
||||
FOR source_attribute IN (
|
||||
SELECT attribute_name, attribute_value
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_GPT54MINI'
|
||||
AND attribute_name NOT IN (
|
||||
'credential_name', 'model', 'provider', 'provider_endpoint',
|
||||
'region', 'oci_compartment_id', 'oci_endpoint_id',
|
||||
'oci_apiformat', 'oci_runtimetype'
|
||||
)
|
||||
) LOOP
|
||||
DBMS_CLOUD_AI.SET_ATTRIBUTE(
|
||||
profile_name => p_profile_name,
|
||||
attribute_name => source_attribute.attribute_name,
|
||||
attribute_value => source_attribute.attribute_value
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
BEGIN
|
||||
SELECT DBMS_LOB.SUBSTR(attribute_value, 128, 1)
|
||||
INTO v_credential_name
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_GPT54MINI'
|
||||
AND attribute_name = 'credential_name';
|
||||
|
||||
SELECT DBMS_LOB.SUBSTR(attribute_value, 128, 1)
|
||||
INTO v_region
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_GPT54MINI'
|
||||
AND attribute_name = 'region';
|
||||
|
||||
SELECT DBMS_LOB.SUBSTR(attribute_value, 4000, 1)
|
||||
INTO v_compartment_id
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_GPT54MINI'
|
||||
AND attribute_name = 'oci_compartment_id';
|
||||
|
||||
create_candidate('SGMP_SCOPE_COHERE_VISION', 'cohere.command-a-vision');
|
||||
create_candidate('SGMP_SCOPE_COHERE_COMMAND', 'cohere.command-latest');
|
||||
create_candidate('SGMP_SCOPE_COHERE_PLUS', 'cohere.command-plus-latest');
|
||||
create_candidate('SGMP_SCOPE_GEMINI_FLASH', 'google.gemini-2.5-flash-lite');
|
||||
create_candidate('SGMP_SCOPE_LLAMA_MAV', 'meta.llama-4-maverick-17b-128e-instruct-fp8');
|
||||
create_candidate('SGMP_SCOPE_GROK_NONR', 'xai.grok-4.20-non-reasoning');
|
||||
END;
|
||||
/
|
||||
|
||||
SELECT profile_name, attribute_value AS model
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name LIKE 'SGMP_SCOPE_%'
|
||||
AND attribute_name = 'model'
|
||||
ORDER BY profile_name;
|
||||
112
database/adb/115_sgmp_scope_chat_candidate_benchmark.sql
Normal file
112
database/adb/115_sgmp_scope_chat_candidate_benchmark.sql
Normal file
@@ -0,0 +1,112 @@
|
||||
-- Read-only benchmark for candidate game-scope extraction profiles.
|
||||
-- Korean test questions use UTF-8 base64 reconstruction for SQLcl safety.
|
||||
|
||||
set serveroutput on size unlimited
|
||||
prompt SG_SCOPE_PROFILE_BENCHMARK_LOADED
|
||||
|
||||
DECLARE
|
||||
TYPE t_case IS RECORD (
|
||||
case_name VARCHAR2(12),
|
||||
question CLOB,
|
||||
expected_scope VARCHAR2(20),
|
||||
expected_mentions PLS_INTEGER
|
||||
);
|
||||
TYPE t_cases IS TABLE OF t_case INDEX BY PLS_INTEGER;
|
||||
v_cases t_cases;
|
||||
v_prompt_prefix CLOB :=
|
||||
'Extract only game-name mentions from the user question. '
|
||||
|| 'Metrics, acronyms, dates, filters, and database object or column names are not game names unless they are themselves an explicit game title. '
|
||||
|| 'When a title-like noun directly qualifies a game data request such as user master, character, sales, AU, NRU, server, or game log, preserve that noun as a game-name mention even when it is not in a catalog. '
|
||||
|| 'Do not discard an unknown title merely because it cannot be resolved. General scope words such as common, overall, all, total, or every are not game-name mentions unless they are part of an explicit title. '
|
||||
|| 'Return exactly one JSON object with keys game_mentions (array of strings) '
|
||||
|| 'and scope_hint (GLOBAL, SINGLE_GAME, MULTI_GAME, ALL_GAMES, UNKNOWN). '
|
||||
|| 'Do not resolve names to IDs and do not generate SQL. '
|
||||
|| 'Return raw JSON only: no prose, no Markdown, and no code fence. Question: ';
|
||||
v_result CLOB;
|
||||
v_json JSON_OBJECT_T;
|
||||
v_started PLS_INTEGER;
|
||||
v_elapsed NUMBER;
|
||||
v_scope VARCHAR2(20);
|
||||
v_mentions PLS_INTEGER;
|
||||
v_raw_json VARCHAR2(5);
|
||||
|
||||
PROCEDURE run_case(
|
||||
p_profile_name IN VARCHAR2,
|
||||
p_case t_case
|
||||
) IS
|
||||
BEGIN
|
||||
v_started := DBMS_UTILITY.GET_TIME;
|
||||
v_result := DBMS_CLOUD_AI.GENERATE(
|
||||
prompt => v_prompt_prefix || p_case.question,
|
||||
profile_name => p_profile_name,
|
||||
action => 'chat'
|
||||
);
|
||||
v_elapsed := (DBMS_UTILITY.GET_TIME - v_started) / 100;
|
||||
v_json := JSON_OBJECT_T.parse(v_result);
|
||||
v_raw_json := 'TRUE';
|
||||
v_scope := v_json.get_string('scope_hint');
|
||||
v_mentions := v_json.get_array('game_mentions').get_size;
|
||||
DBMS_OUTPUT.PUT_LINE(
|
||||
p_profile_name || '|' || p_case.case_name
|
||||
|| '|seconds=' || TO_CHAR(v_elapsed, 'FM9990D00')
|
||||
|| '|raw_json=' || v_raw_json
|
||||
|| '|scope=' || NVL(v_scope, 'NULL')
|
||||
|| '|mentions=' || v_mentions
|
||||
|| '|expected=' || p_case.expected_scope || '/' || p_case.expected_mentions
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
DBMS_OUTPUT.PUT_LINE(
|
||||
p_profile_name || '|' || p_case.case_name
|
||||
|| '|seconds=' || TO_CHAR(v_elapsed, 'FM9990D00')
|
||||
|| '|ERROR=' || SQLCODE || '|' || SUBSTR(SQLERRM, 1, 180)
|
||||
);
|
||||
END;
|
||||
BEGIN
|
||||
DBMS_OUTPUT.PUT_LINE('benchmark_started');
|
||||
v_cases(1).case_name := 'NONE';
|
||||
v_cases(1).question := utl_i18n.raw_to_char(utl_encode.base64_decode(
|
||||
utl_raw.cast_to_raw('7KCE7LK0IOunpOy2nCDslYzroKTspJgu')), 'AL32UTF8');
|
||||
v_cases(1).expected_scope := 'GLOBAL';
|
||||
v_cases(1).expected_mentions := 0;
|
||||
|
||||
v_cases(2).case_name := 'SINGLE';
|
||||
v_cases(2).question := utl_i18n.raw_to_char(utl_encode.base64_decode(
|
||||
utl_raw.cast_to_raw('7Lm07KCc64KYIOy1nOyLoCBBVSDslYzroKTspJgu')), 'AL32UTF8');
|
||||
v_cases(2).expected_scope := 'SINGLE_GAME';
|
||||
v_cases(2).expected_mentions := 1;
|
||||
|
||||
v_cases(3).case_name := 'MULTI';
|
||||
v_cases(3).question := utl_i18n.raw_to_char(utl_encode.base64_decode(
|
||||
utl_raw.cast_to_raw('66Gc65Oc64KY7J247J20656RIOy5tOygnOuCmOydmCAyMDI264WEIDfsm5QgMTXsnbwgQVXrpbwg6rCB6rCBIOyVjOugpOykmC4=')), 'AL32UTF8');
|
||||
v_cases(3).expected_scope := 'MULTI_GAME';
|
||||
v_cases(3).expected_mentions := 2;
|
||||
|
||||
v_cases(4).case_name := 'ALL';
|
||||
v_cases(4).question := utl_i18n.raw_to_char(utl_encode.base64_decode(
|
||||
utl_raw.cast_to_raw('7KCE7LK0IOqyjOyehOydmCDrp6Tstpwg7JWM66Ck7KSYLg==')), 'AL32UTF8');
|
||||
v_cases(4).expected_scope := 'ALL_GAMES';
|
||||
v_cases(4).expected_mentions := 0;
|
||||
|
||||
FOR profile_row IN (
|
||||
SELECT profile_name
|
||||
FROM user_cloud_ai_profiles
|
||||
WHERE profile_name IN (
|
||||
'SGMP_POC_OCI_GPT54MINI',
|
||||
'SGMP_SCOPE_COHERE_VISION',
|
||||
'SGMP_SCOPE_COHERE_COMMAND',
|
||||
'SGMP_SCOPE_COHERE_PLUS',
|
||||
'SGMP_SCOPE_GEMINI_FLASH',
|
||||
'SGMP_SCOPE_LLAMA_MAV',
|
||||
'SGMP_SCOPE_GROK_NONR'
|
||||
)
|
||||
ORDER BY profile_name
|
||||
) LOOP
|
||||
DBMS_OUTPUT.PUT_LINE('profile=' || profile_row.profile_name);
|
||||
FOR i IN 1 .. 4 LOOP
|
||||
run_case(profile_row.profile_name, v_cases(i));
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END;
|
||||
/
|
||||
prompt SG_SCOPE_PROFILE_BENCHMARK_COMPLETED
|
||||
122
database/adb/116_sg_game_catalog_alias_embeddings.sql
Normal file
122
database/adb/116_sg_game_catalog_alias_embeddings.sql
Normal file
@@ -0,0 +1,122 @@
|
||||
-- Store all game-name variants as one JSON array per game and embed that JSON
|
||||
-- as the canonical game-search vector. No customer game name is hardcoded.
|
||||
|
||||
DECLARE
|
||||
v_column_count PLS_INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_column_count
|
||||
FROM user_tab_columns
|
||||
WHERE table_name = 'SG_GAME_CATALOG'
|
||||
AND column_name = 'ALIASES_JSON';
|
||||
|
||||
IF v_column_count = 0 THEN
|
||||
EXECUTE IMMEDIATE 'ALTER TABLE sg_game_catalog ADD (aliases_json CLOB)';
|
||||
END IF;
|
||||
END;
|
||||
/
|
||||
|
||||
UPDATE sg_game_catalog
|
||||
SET aliases_json = '[]'
|
||||
WHERE aliases_json IS NULL;
|
||||
/
|
||||
|
||||
DECLARE
|
||||
v_constraint_count PLS_INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_constraint_count
|
||||
FROM user_constraints
|
||||
WHERE table_name = 'SG_GAME_CATALOG'
|
||||
AND constraint_name = 'SG_GAME_CATALOG_ALIASES_JS_CK';
|
||||
|
||||
IF v_constraint_count = 0 THEN
|
||||
EXECUTE IMMEDIATE
|
||||
'ALTER TABLE sg_game_catalog ADD CONSTRAINT sg_game_catalog_aliases_js_ck '
|
||||
|| 'CHECK (aliases_json IS JSON)';
|
||||
END IF;
|
||||
END;
|
||||
/
|
||||
|
||||
MERGE INTO sg_game_catalog c
|
||||
USING (
|
||||
WITH source_alias AS (
|
||||
SELECT game_id AS game_key, game_nm AS alias_name
|
||||
FROM comn_game_alias_bas
|
||||
WHERE use_yn = 'Y' AND game_nm IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT game_id, game_alias_nm
|
||||
FROM comn_game_alias_bas
|
||||
WHERE use_yn = 'Y' AND game_alias_nm IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT game_id, game_id
|
||||
FROM comn_game_alias_bas
|
||||
WHERE use_yn = 'Y' AND game_id IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT game_id, game_prefix
|
||||
FROM comn_game_alias_bas
|
||||
WHERE use_yn = 'Y' AND game_prefix IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT game_key, display_name
|
||||
FROM sg_game_scope_registry
|
||||
WHERE active_yn = 'Y' AND display_name IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT game_key, game_alias
|
||||
FROM sg_game_scope_registry
|
||||
WHERE active_yn = 'Y' AND game_alias IS NOT NULL
|
||||
),
|
||||
deduplicated_alias AS (
|
||||
SELECT game_key, alias_name
|
||||
FROM source_alias
|
||||
WHERE TRIM(alias_name) IS NOT NULL
|
||||
GROUP BY game_key, alias_name
|
||||
)
|
||||
SELECT game_key,
|
||||
JSON_ARRAYAGG(alias_name ORDER BY alias_name RETURNING CLOB) AS aliases_json
|
||||
FROM deduplicated_alias
|
||||
GROUP BY game_key
|
||||
) s
|
||||
ON (c.game_key = s.game_key)
|
||||
WHEN MATCHED THEN UPDATE SET
|
||||
c.aliases_json = s.aliases_json,
|
||||
c.updated_at = SYSTIMESTAMP;
|
||||
/
|
||||
|
||||
-- A game has one canonical vector made from its complete JSON alias array.
|
||||
UPDATE sg_game_catalog c
|
||||
SET c.embedding = DBMS_VECTOR.UTL_TO_EMBEDDING(
|
||||
c.aliases_json,
|
||||
JSON(sg_qa_vector_params('search_document'))
|
||||
),
|
||||
c.updated_at = SYSTIMESTAMP
|
||||
WHERE c.active_yn = 'Y';
|
||||
/
|
||||
|
||||
COMMENT ON COLUMN sg_game_catalog.aliases_json IS
|
||||
'Canonical JSON string array of every game-name variant used as the embedding input.';
|
||||
COMMENT ON COLUMN sg_game_catalog.embedding IS
|
||||
'One vector per game, generated from the complete aliases_json array.';
|
||||
/
|
||||
|
||||
CREATE OR REPLACE FUNCTION sg_game_catalog_search(
|
||||
p_question IN CLOB,
|
||||
p_top_k IN PLS_INTEGER DEFAULT 5
|
||||
) RETURN SYS_REFCURSOR AUTHID DEFINER IS
|
||||
v_query VECTOR;
|
||||
v_result SYS_REFCURSOR;
|
||||
BEGIN
|
||||
v_query := DBMS_VECTOR.UTL_TO_EMBEDDING(
|
||||
p_question,
|
||||
JSON(sg_qa_vector_params('search_query'))
|
||||
);
|
||||
OPEN v_result FOR
|
||||
SELECT game_key, game_id, game_prefix, game_nm, game_alias_nm,
|
||||
user_master_object_name,
|
||||
VECTOR_DISTANCE(embedding, v_query, COSINE) AS cosine_distance
|
||||
FROM sg_game_catalog
|
||||
WHERE active_yn = 'Y' AND embedding IS NOT NULL
|
||||
ORDER BY VECTOR_DISTANCE(embedding, v_query, COSINE), priority, game_key
|
||||
FETCH FIRST LEAST(GREATEST(NVL(p_top_k, 5), 1), 20) ROWS ONLY;
|
||||
RETURN v_result;
|
||||
END;
|
||||
/
|
||||
44
database/adb/117_sg_game_scope_policy.sql
Normal file
44
database/adb/117_sg_game_scope_policy.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- Customer-managed score policy for vector-only game identity resolution.
|
||||
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE q'[
|
||||
CREATE TABLE sg_game_scope_policy (
|
||||
policy_key VARCHAR2(128) PRIMARY KEY,
|
||||
number_value NUMBER,
|
||||
text_value VARCHAR2(4000),
|
||||
description VARCHAR2(1000) NOT NULL,
|
||||
active_yn CHAR(1) DEFAULT 'Y' NOT NULL,
|
||||
updated_at TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
CONSTRAINT sg_game_scope_policy_active_ck CHECK (active_yn IN ('Y', 'N'))
|
||||
)]';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
IF SQLCODE != -955 THEN RAISE; END IF;
|
||||
END;
|
||||
/
|
||||
|
||||
MERGE INTO sg_game_scope_policy t
|
||||
USING (
|
||||
SELECT 'GAME_ALIAS_MAX_COSINE_DISTANCE' AS policy_key,
|
||||
0.500000 AS number_value,
|
||||
CAST(NULL AS VARCHAR2(4000)) AS text_value,
|
||||
'Maximum cosine distance for accepting the closest independently embedded game alias.' AS description
|
||||
FROM dual
|
||||
) s
|
||||
ON (t.policy_key = s.policy_key)
|
||||
WHEN MATCHED THEN UPDATE SET
|
||||
t.description = s.description,
|
||||
t.active_yn = 'Y',
|
||||
t.updated_at = SYSTIMESTAMP
|
||||
WHEN NOT MATCHED THEN INSERT (
|
||||
policy_key, number_value, text_value, description, active_yn
|
||||
) VALUES (
|
||||
s.policy_key, s.number_value, s.text_value, s.description, 'Y'
|
||||
);
|
||||
/
|
||||
|
||||
COMMENT ON TABLE sg_game_scope_policy IS
|
||||
'Customer-managed game scope policy values; changing a value requires no application deployment.';
|
||||
COMMENT ON COLUMN sg_game_scope_policy.number_value IS
|
||||
'Numeric policy value. GAME_ALIAS_MAX_COSINE_DISTANCE applies to the closest alias vector.';
|
||||
/
|
||||
78
database/adb/118_sgmp_scope_command_profile.sql
Normal file
78
database/adb/118_sgmp_scope_command_profile.sql
Normal file
@@ -0,0 +1,78 @@
|
||||
-- Operational OCI Cohere profile for short game-name and scope extraction.
|
||||
-- It inherits the active GPT profile's OCI credential, region, and metadata.
|
||||
|
||||
DECLARE
|
||||
v_credential_name VARCHAR2(128);
|
||||
v_region VARCHAR2(128);
|
||||
v_compartment_id VARCHAR2(4000);
|
||||
v_attributes CLOB;
|
||||
v_attribute_json JSON_OBJECT_T;
|
||||
v_exists PLS_INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_exists
|
||||
FROM user_cloud_ai_profiles
|
||||
WHERE profile_name = 'SGMP_POC_OCI_COHERE_COMMAND';
|
||||
|
||||
IF v_exists > 0 THEN
|
||||
DBMS_CLOUD_AI.DROP_PROFILE(
|
||||
profile_name => 'SGMP_POC_OCI_COHERE_COMMAND',
|
||||
force => TRUE
|
||||
);
|
||||
END IF;
|
||||
|
||||
SELECT DBMS_LOB.SUBSTR(attribute_value, 128, 1)
|
||||
INTO v_credential_name
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_GPT54MINI'
|
||||
AND attribute_name = 'credential_name';
|
||||
|
||||
SELECT DBMS_LOB.SUBSTR(attribute_value, 128, 1)
|
||||
INTO v_region
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_GPT54MINI'
|
||||
AND attribute_name = 'region';
|
||||
|
||||
SELECT DBMS_LOB.SUBSTR(attribute_value, 4000, 1)
|
||||
INTO v_compartment_id
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_GPT54MINI'
|
||||
AND attribute_name = 'oci_compartment_id';
|
||||
|
||||
v_attribute_json := JSON_OBJECT_T();
|
||||
v_attribute_json.put('provider', 'oci');
|
||||
v_attribute_json.put('credential_name', v_credential_name);
|
||||
v_attribute_json.put('model', 'cohere.command-latest');
|
||||
v_attribute_json.put('region', v_region);
|
||||
v_attribute_json.put('oci_compartment_id', v_compartment_id);
|
||||
v_attributes := v_attribute_json.to_clob;
|
||||
|
||||
DBMS_CLOUD_AI.CREATE_PROFILE(
|
||||
profile_name => 'SGMP_POC_OCI_COHERE_COMMAND',
|
||||
attributes => v_attributes,
|
||||
description => 'Smilegate operational OCI Cohere Command profile for game scope extraction'
|
||||
);
|
||||
|
||||
FOR source_attribute IN (
|
||||
SELECT attribute_name, attribute_value
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_GPT54MINI'
|
||||
AND attribute_name NOT IN (
|
||||
'credential_name', 'model', 'provider', 'provider_endpoint',
|
||||
'region', 'oci_compartment_id', 'oci_endpoint_id',
|
||||
'oci_apiformat', 'oci_runtimetype'
|
||||
)
|
||||
) LOOP
|
||||
DBMS_CLOUD_AI.SET_ATTRIBUTE(
|
||||
profile_name => 'SGMP_POC_OCI_COHERE_COMMAND',
|
||||
attribute_name => source_attribute.attribute_name,
|
||||
attribute_value => source_attribute.attribute_value
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
/
|
||||
|
||||
SELECT profile_name, attribute_value AS model
|
||||
FROM user_cloud_ai_profile_attributes
|
||||
WHERE profile_name = 'SGMP_POC_OCI_COHERE_COMMAND'
|
||||
AND attribute_name = 'model';
|
||||
@@ -1,20 +1,34 @@
|
||||
-- OCI GenAI chat extraction. This function returns JSON only; it never chooses tables.
|
||||
-- One short OCI Chat call extracts game-name mentions. Identity resolution is
|
||||
-- performed later by catalog vector score, not by another LLM call.
|
||||
CREATE OR REPLACE FUNCTION sg_game_extract_mentions(p_question IN CLOB)
|
||||
RETURN CLOB AUTHID DEFINER
|
||||
IS
|
||||
v_prompt CLOB;
|
||||
v_result CLOB;
|
||||
v_extract JSON_OBJECT_T;
|
||||
BEGIN
|
||||
v_prompt := 'Extract only game-name mentions from the user question. '
|
||||
|| 'Metrics, acronyms, dates, filters, and database object or column names are not game names unless they are themselves an explicit game title. '
|
||||
|| 'When a title-like noun directly qualifies a game data request such as user master, character, sales, AU, NRU, server, or game log, preserve that noun as a game-name mention even when it is not in a catalog. '
|
||||
|| 'Do not discard an unknown title merely because it cannot be resolved. General scope words such as common, overall, all, total, or every are not game-name mentions unless they are part of an explicit title. '
|
||||
|| 'Return exactly one JSON object with keys game_mentions (array of strings) '
|
||||
|| 'and scope_hint (GLOBAL, SINGLE_GAME, MULTI_GAME, ALL_GAMES, UNKNOWN). '
|
||||
|| 'Do not resolve names to IDs and do not generate SQL. Question: '
|
||||
|| 'Do not resolve names to IDs and do not generate SQL. '
|
||||
|| 'Return raw JSON only: no prose, no Markdown, and no code fence. Question: '
|
||||
|| DBMS_LOB.SUBSTR(p_question, 4000, 1);
|
||||
|
||||
v_result := DBMS_CLOUD_AI.GENERATE(
|
||||
prompt => v_prompt,
|
||||
profile_name => 'SGMP_POC_OCI_GPT54MINI',
|
||||
profile_name => 'SGMP_POC_OCI_COHERE_COMMAND',
|
||||
action => 'chat'
|
||||
);
|
||||
v_extract := JSON_OBJECT_T.parse(v_result);
|
||||
IF NOT v_extract.has('game_mentions') OR NOT v_extract.has('scope_hint') THEN
|
||||
RAISE_APPLICATION_ERROR(
|
||||
-20091,
|
||||
'Game mention extraction must return game_mentions and scope_hint JSON keys.'
|
||||
);
|
||||
END IF;
|
||||
RETURN v_result;
|
||||
END;
|
||||
/
|
||||
|
||||
@@ -1,32 +1,57 @@
|
||||
-- OCI GenAI candidate selection. Vector distance only orders candidates; the
|
||||
-- model may select one supplied game_key or explicitly reject all candidates.
|
||||
-- Vector-score candidate selection. The closest catalog vector is accepted
|
||||
-- only when it is within the customer-managed policy threshold.
|
||||
CREATE OR REPLACE FUNCTION sg_game_match_candidate(
|
||||
p_mention IN CLOB,
|
||||
p_candidates_json IN CLOB
|
||||
) RETURN CLOB AUTHID DEFINER
|
||||
IS
|
||||
v_prompt CLOB;
|
||||
v_result CLOB;
|
||||
v_candidates JSON_ARRAY_T;
|
||||
v_candidate JSON_OBJECT_T;
|
||||
v_result JSON_OBJECT_T := JSON_OBJECT_T();
|
||||
v_threshold NUMBER;
|
||||
v_distance NUMBER;
|
||||
v_game_key VARCHAR2(128);
|
||||
BEGIN
|
||||
v_prompt := 'Decide whether the extracted game-name mention refers to exactly '
|
||||
|| 'one game in the supplied candidate array. Candidate vector distance is '
|
||||
|| 'retrieval evidence only and must not establish identity. Compare the '
|
||||
|| 'mention with candidate gameName, aliases, gameId, and gamePrefix. '
|
||||
|| 'If one candidate clearly identifies the same game, return exactly '
|
||||
|| '{"status":"MATCHED","game_key":"candidate key","reason":"short reason"}. '
|
||||
|| 'If none clearly identifies the same game, return exactly '
|
||||
|| '{"status":"UNMATCHED","game_key":null,"reason":"short reason"}. '
|
||||
|| 'Never invent a game_key and never return a key absent from the supplied '
|
||||
|| 'candidate array. Mention: '
|
||||
|| DBMS_LOB.SUBSTR(p_mention, 1000, 1)
|
||||
|| ' Candidates: '
|
||||
|| DBMS_LOB.SUBSTR(p_candidates_json, 12000, 1);
|
||||
v_result := DBMS_CLOUD_AI.GENERATE(
|
||||
prompt => v_prompt,
|
||||
profile_name => 'SGMP_POC_OCI_GPT54MINI',
|
||||
action => 'chat'
|
||||
);
|
||||
RETURN v_result;
|
||||
SELECT number_value
|
||||
INTO v_threshold
|
||||
FROM sg_game_scope_policy
|
||||
WHERE policy_key = 'GAME_ALIAS_MAX_COSINE_DISTANCE'
|
||||
AND active_yn = 'Y';
|
||||
|
||||
v_candidates := JSON_ARRAY_T.parse(p_candidates_json);
|
||||
IF v_candidates.get_size = 0 THEN
|
||||
v_result.put('status', 'UNMATCHED');
|
||||
v_result.put_null('game_key');
|
||||
v_result.put('reason', 'No active game catalog vector candidate was returned.');
|
||||
RETURN v_result.to_clob;
|
||||
END IF;
|
||||
|
||||
v_candidate := TREAT(v_candidates.get(0) AS JSON_OBJECT_T);
|
||||
v_distance := v_candidate.get_number('cosineDistance');
|
||||
v_game_key := v_candidate.get_string('gameKey');
|
||||
|
||||
IF v_distance <= v_threshold THEN
|
||||
v_result.put('status', 'MATCHED');
|
||||
v_result.put('game_key', v_game_key);
|
||||
v_result.put(
|
||||
'reason',
|
||||
'Closest catalog vector distance '
|
||||
|| TO_CHAR(v_distance, 'FM0D000000')
|
||||
|| ' is within configured maximum '
|
||||
|| TO_CHAR(v_threshold, 'FM0D000000') || '.'
|
||||
);
|
||||
ELSE
|
||||
v_result.put('status', 'UNMATCHED');
|
||||
v_result.put_null('game_key');
|
||||
v_result.put(
|
||||
'reason',
|
||||
'Closest catalog vector distance '
|
||||
|| TO_CHAR(v_distance, 'FM0D000000')
|
||||
|| ' exceeds configured maximum '
|
||||
|| TO_CHAR(v_threshold, 'FM0D000000') || '.'
|
||||
);
|
||||
END IF;
|
||||
RETURN v_result.to_clob;
|
||||
END;
|
||||
/
|
||||
|
||||
@@ -255,14 +280,14 @@ BEGIN
|
||||
|
||||
IF v_matched_candidate IS NOT NULL THEN
|
||||
v_mention_result.put('status', 'MATCHED');
|
||||
v_mention_result.put('reasonCode', 'LLM_CANDIDATE_MATCH');
|
||||
v_mention_result.put('reasonCode', 'VECTOR_SCORE_MATCH');
|
||||
v_mention_result.put('matchedGameKey', v_selected_key);
|
||||
|
||||
v_target := JSON_OBJECT_T.parse(v_matched_candidate.to_clob);
|
||||
v_target.put('mention', v_mention);
|
||||
v_target.put('status', 'MATCHED');
|
||||
v_target.put('matchStatus', 'MATCHED');
|
||||
v_target.put('reasonCode', 'LLM_CANDIDATE_MATCH');
|
||||
v_target.put('reasonCode', 'VECTOR_SCORE_MATCH');
|
||||
v_target.put('reason', v_reason);
|
||||
v_matched_count := v_matched_count + 1;
|
||||
|
||||
@@ -282,14 +307,7 @@ BEGIN
|
||||
END IF;
|
||||
ELSE
|
||||
v_mention_result.put('status', 'UNMATCHED');
|
||||
IF v_decision_status = 'MATCHED' THEN
|
||||
v_mention_result.put(
|
||||
'reasonCode',
|
||||
'LLM_SELECTED_UNKNOWN_CANDIDATE'
|
||||
);
|
||||
ELSE
|
||||
v_mention_result.put('reasonCode', 'LLM_NO_CANDIDATE_MATCH');
|
||||
END IF;
|
||||
v_mention_result.put('reasonCode', 'VECTOR_SCORE_OVER_THRESHOLD');
|
||||
|
||||
v_target := JSON_OBJECT_T();
|
||||
v_target.put('mention', v_mention);
|
||||
|
||||
Reference in New Issue
Block a user