@@ -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)
|
CREATE OR REPLACE FUNCTION sg_game_extract_mentions(p_question IN CLOB)
|
||||||
RETURN CLOB AUTHID DEFINER
|
RETURN CLOB AUTHID DEFINER
|
||||||
IS
|
IS
|
||||||
v_prompt CLOB;
|
v_prompt CLOB;
|
||||||
v_result CLOB;
|
v_result CLOB;
|
||||||
|
v_extract JSON_OBJECT_T;
|
||||||
BEGIN
|
BEGIN
|
||||||
v_prompt := 'Extract only game-name mentions from the user question. '
|
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) '
|
|| 'Return exactly one JSON object with keys game_mentions (array of strings) '
|
||||||
|| 'and scope_hint (GLOBAL, SINGLE_GAME, MULTI_GAME, ALL_GAMES, UNKNOWN). '
|
|| '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);
|
|| DBMS_LOB.SUBSTR(p_question, 4000, 1);
|
||||||
|
|
||||||
v_result := DBMS_CLOUD_AI.GENERATE(
|
v_result := DBMS_CLOUD_AI.GENERATE(
|
||||||
prompt => v_prompt,
|
prompt => v_prompt,
|
||||||
profile_name => 'SGMP_POC_OCI_GPT54MINI',
|
profile_name => 'SGMP_POC_OCI_COHERE_COMMAND',
|
||||||
action => 'chat'
|
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;
|
RETURN v_result;
|
||||||
END;
|
END;
|
||||||
/
|
/
|
||||||
|
|||||||
@@ -1,32 +1,57 @@
|
|||||||
-- OCI GenAI candidate selection. Vector distance only orders candidates; the
|
-- Vector-score candidate selection. The closest catalog vector is accepted
|
||||||
-- model may select one supplied game_key or explicitly reject all candidates.
|
-- only when it is within the customer-managed policy threshold.
|
||||||
CREATE OR REPLACE FUNCTION sg_game_match_candidate(
|
CREATE OR REPLACE FUNCTION sg_game_match_candidate(
|
||||||
p_mention IN CLOB,
|
p_mention IN CLOB,
|
||||||
p_candidates_json IN CLOB
|
p_candidates_json IN CLOB
|
||||||
) RETURN CLOB AUTHID DEFINER
|
) RETURN CLOB AUTHID DEFINER
|
||||||
IS
|
IS
|
||||||
v_prompt CLOB;
|
v_candidates JSON_ARRAY_T;
|
||||||
v_result CLOB;
|
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
|
BEGIN
|
||||||
v_prompt := 'Decide whether the extracted game-name mention refers to exactly '
|
SELECT number_value
|
||||||
|| 'one game in the supplied candidate array. Candidate vector distance is '
|
INTO v_threshold
|
||||||
|| 'retrieval evidence only and must not establish identity. Compare the '
|
FROM sg_game_scope_policy
|
||||||
|| 'mention with candidate gameName, aliases, gameId, and gamePrefix. '
|
WHERE policy_key = 'GAME_ALIAS_MAX_COSINE_DISTANCE'
|
||||||
|| 'If one candidate clearly identifies the same game, return exactly '
|
AND active_yn = 'Y';
|
||||||
|| '{"status":"MATCHED","game_key":"candidate key","reason":"short reason"}. '
|
|
||||||
|| 'If none clearly identifies the same game, return exactly '
|
v_candidates := JSON_ARRAY_T.parse(p_candidates_json);
|
||||||
|| '{"status":"UNMATCHED","game_key":null,"reason":"short reason"}. '
|
IF v_candidates.get_size = 0 THEN
|
||||||
|| 'Never invent a game_key and never return a key absent from the supplied '
|
v_result.put('status', 'UNMATCHED');
|
||||||
|| 'candidate array. Mention: '
|
v_result.put_null('game_key');
|
||||||
|| DBMS_LOB.SUBSTR(p_mention, 1000, 1)
|
v_result.put('reason', 'No active game catalog vector candidate was returned.');
|
||||||
|| ' Candidates: '
|
RETURN v_result.to_clob;
|
||||||
|| DBMS_LOB.SUBSTR(p_candidates_json, 12000, 1);
|
END IF;
|
||||||
v_result := DBMS_CLOUD_AI.GENERATE(
|
|
||||||
prompt => v_prompt,
|
v_candidate := TREAT(v_candidates.get(0) AS JSON_OBJECT_T);
|
||||||
profile_name => 'SGMP_POC_OCI_GPT54MINI',
|
v_distance := v_candidate.get_number('cosineDistance');
|
||||||
action => 'chat'
|
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') || '.'
|
||||||
);
|
);
|
||||||
RETURN v_result;
|
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;
|
END;
|
||||||
/
|
/
|
||||||
|
|
||||||
@@ -255,14 +280,14 @@ BEGIN
|
|||||||
|
|
||||||
IF v_matched_candidate IS NOT NULL THEN
|
IF v_matched_candidate IS NOT NULL THEN
|
||||||
v_mention_result.put('status', 'MATCHED');
|
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_mention_result.put('matchedGameKey', v_selected_key);
|
||||||
|
|
||||||
v_target := JSON_OBJECT_T.parse(v_matched_candidate.to_clob);
|
v_target := JSON_OBJECT_T.parse(v_matched_candidate.to_clob);
|
||||||
v_target.put('mention', v_mention);
|
v_target.put('mention', v_mention);
|
||||||
v_target.put('status', 'MATCHED');
|
v_target.put('status', 'MATCHED');
|
||||||
v_target.put('matchStatus', '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_target.put('reason', v_reason);
|
||||||
v_matched_count := v_matched_count + 1;
|
v_matched_count := v_matched_count + 1;
|
||||||
|
|
||||||
@@ -282,14 +307,7 @@ BEGIN
|
|||||||
END IF;
|
END IF;
|
||||||
ELSE
|
ELSE
|
||||||
v_mention_result.put('status', 'UNMATCHED');
|
v_mention_result.put('status', 'UNMATCHED');
|
||||||
IF v_decision_status = 'MATCHED' THEN
|
v_mention_result.put('reasonCode', 'VECTOR_SCORE_OVER_THRESHOLD');
|
||||||
v_mention_result.put(
|
|
||||||
'reasonCode',
|
|
||||||
'LLM_SELECTED_UNKNOWN_CANDIDATE'
|
|
||||||
);
|
|
||||||
ELSE
|
|
||||||
v_mention_result.put('reasonCode', 'LLM_NO_CANDIDATE_MATCH');
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
v_target := JSON_OBJECT_T();
|
v_target := JSON_OBJECT_T();
|
||||||
v_target.put('mention', v_mention);
|
v_target.put('mention', v_mention);
|
||||||
|
|||||||
@@ -67,3 +67,46 @@ credential, region, compartment, object list와 metadata instructions를 복제
|
|||||||
형식을 기록한다. 품질·지연시간 결과를 검토하기 전에는
|
형식을 기록한다. 품질·지연시간 결과를 검토하기 전에는
|
||||||
`sg_game_extract_mentions`와 `sg_game_match_candidate`의 운영 프로파일을 바꾸지
|
`sg_game_extract_mentions`와 `sg_game_match_candidate`의 운영 프로파일을 바꾸지
|
||||||
않는다.
|
않는다.
|
||||||
|
|
||||||
|
후보 모델 비교는 `sql/adb/114_sgmp_create_scope_chat_candidate_profiles.sql`과
|
||||||
|
`sql/adb/115_sgmp_scope_chat_candidate_benchmark.sql`으로 수행한다. 후보는
|
||||||
|
운영과 분리된 `SGMP_SCOPE_*` 프로파일로 생성한다. 각 모델은 `NONE`, `SINGLE`,
|
||||||
|
`MULTI`, `ALL` 질문에서 다음을 기록한다.
|
||||||
|
|
||||||
|
- 초 단위 Chat 응답시간
|
||||||
|
- 설명문·Markdown 없이 raw JSON만 반환했는지
|
||||||
|
- `scope_hint`와 `game_mentions` 수가 기대값과 일치하는지
|
||||||
|
- 생성 또는 Chat 호출 오류
|
||||||
|
|
||||||
|
## 게임명 추출과 벡터 점수 판정
|
||||||
|
|
||||||
|
게임명과 조회 범위 추출은 짧은 JSON Chat 작업이므로 운영 프로파일
|
||||||
|
`SGMP_POC_OCI_COHERE_COMMAND` (`cohere.command-latest`)을 사용한다.
|
||||||
|
게임 카탈로그의 `ALIASES_JSON`에는 게임명, 영문명, 약칭, 게임 ID, prefix와
|
||||||
|
운영 등록 별칭을 JSON 배열로 저장한다. 이 JSON 배열 전체를 게임당 하나의
|
||||||
|
임베딩으로 생성한다. 추출된 각 게임명은 해당 게임 벡터의 cosine distance와
|
||||||
|
`SG_GAME_SCOPE_POLICY`에 저장된 최대값을 비교해 `MATCHED` 또는 `UNMATCHED`로
|
||||||
|
판정한다. 후보 동일성 확인을 위한 별도 LLM 호출은 사용하지 않는다.
|
||||||
|
|
||||||
|
정책값은 코드가 아니라 DB 설정 테이블에 저장한다. 게임 카탈로그·임베딩 모델이
|
||||||
|
변경되면 운영자가 그 기준값을 조정할 수 있다. `NONE`/`SINGLE`/`MULTI`/`ALL`
|
||||||
|
계약, Few-shot NL2SQL 전달 형식, Text2SQL 프로파일은 유지한다.
|
||||||
|
|
||||||
|
### 검증 기준
|
||||||
|
|
||||||
|
- 추출 결과는 JSON 파싱 가능하고 `game_mentions`, `scope_hint`만 반환한다.
|
||||||
|
- `NONE`, `SINGLE`, `MULTI`, `ALL`에서 canonical `targets`,
|
||||||
|
`dataEligibleTargets`, `unresolvedTargets`가 유지된다.
|
||||||
|
- 동일성 판정을 위한 추가 OCI Chat 호출이 발생하지 않는다.
|
||||||
|
|
||||||
|
### 후보 프로파일 비교 결과
|
||||||
|
|
||||||
|
동일한 JSON 추출 프롬프트로 `NONE`, `SINGLE`, `MULTI`, `ALL`을 호출한 결과,
|
||||||
|
`cohere.command-latest`, `cohere.command-plus-latest`, `cohere.command-a-vision`은
|
||||||
|
기본 4건에서 모두 범위와 게임명 수를 맞췄다. `google.gemini-2.5-flash-lite`는
|
||||||
|
JSON 파싱이 일관되지 않았고, Grok과 Llama Maverick은 게임 미지정 또는 전체 게임
|
||||||
|
질의에서 범위 오류가 있었다.
|
||||||
|
|
||||||
|
게임명 추출은 짧은 단일 JSON 요청이므로 `cohere.command-latest`를 선택했다.
|
||||||
|
이 측정값은 ADB 내부 Chat 호출 시간이며, Portal의 MCP·ReAct·답변 합성 시간을
|
||||||
|
포함한 전체 체감시간과는 별도로 관리한다.
|
||||||
|
|||||||
@@ -105,9 +105,17 @@
|
|||||||
- 운영 registry에만 존재하는 게임도 카탈로그에 동기화하고 임베딩을 생성한다.
|
- 운영 registry에만 존재하는 게임도 카탈로그에 동기화하고 임베딩을 생성한다.
|
||||||
- 게임별 물리 객체가 없으면 컬럼은 `null`이다.
|
- 게임별 물리 객체가 없으면 컬럼은 `null`이다.
|
||||||
|
|
||||||
|
`SG_GAME_CATALOG.ALIASES_JSON`에는 게임명, 영문명, 약칭, 게임 ID, prefix와
|
||||||
|
운영 registry 별칭을 JSON 배열로 저장한다. 배열 전체를 게임당 하나의 벡터로
|
||||||
|
임베딩한다. `SG_GAME_SCOPE_POLICY.GAME_ALIAS_MAX_COSINE_DISTANCE`는 벡터 검색
|
||||||
|
점수의 신뢰도 기준이며, 후보·점수·원 질문은 `queryPlan`에 함께 보존해 다음
|
||||||
|
Select AI가 이름 근거를 보조 판단할 수 있게 한다. 별도 LLM 후보 동일성 판정은
|
||||||
|
사용하지 않는다.
|
||||||
|
|
||||||
`SG_GAME_CATALOG_SEARCH`는 물리 객체명을 후보 결과에 포함한다.
|
`SG_GAME_CATALOG_SEARCH`는 물리 객체명을 후보 결과에 포함한다.
|
||||||
`SG_GAME_QUERY_PLAN`은 추출된 mention마다 후보 선택 결과를 `targets`로 만들며,
|
`SG_GAME_QUERY_PLAN`은 추출된 mention마다 벡터 점수 기반 후보 결과를 `targets`와
|
||||||
`ALL`은 벡터 선택 없이 전체 카탈로그를 사용한다.
|
`mentionResults.candidateGames`로 만들며, `ALL`은 벡터 선택 없이 전체 카탈로그를
|
||||||
|
사용한다.
|
||||||
|
|
||||||
## 5. MCP와 NL2SQL 설계
|
## 5. MCP와 NL2SQL 설계
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import org.springframework.stereotype.Service;
|
|||||||
/**
|
/**
|
||||||
* MCP database gateway for the ADB-owned game query planning functions.
|
* MCP database gateway for the ADB-owned game query planning functions.
|
||||||
*
|
*
|
||||||
* <p>Game mention extraction, vector candidate retrieval, and OCI GenAI
|
* <p>Game mention extraction, JSON-alias vector retrieval, and score-based
|
||||||
* candidate selection run inside {@code SG_GAME_QUERY_PLAN}. Java validates
|
* scope resolution run inside {@code SG_GAME_QUERY_PLAN}. Java validates
|
||||||
* authentication and transports the structured JSON result without applying
|
* authentication and transports the structured JSON result without applying
|
||||||
* game-name matching rules.</p>
|
* game-name matching rules.</p>
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user