refs #739: document DB-owned task and few-shot policy

This commit is contained in:
devmrko
2026-07-30 16:41:28 +09:00
parent 7bf8199343
commit 022ae7f9d2
16 changed files with 737 additions and 4 deletions

View File

@@ -0,0 +1,29 @@
-- STD-25 is a period AU metric, not the daily AU_FLAG metric.
-- Keep the evaluation evidence explicit so the LLM judge accepts the valid
-- weekly result shape produced by the NL2SQL tool.
UPDATE sg_ai_qa_question
SET expected_focus = 'Weekly AU: use one as-of snapshot (BASE_DT=2026-07-15), '
|| 'count DISTINCT GUID whose LAST_CONN_DT is in the inclusive seven-day window '
|| '(2026-07-09 through 2026-07-15), with STD_USER_YN=''Y'' and EXPT_USER_YN=''N''. '
|| 'This is one aggregate result, not daily rows. Do not substitute daily AU_FLAG=1 for the period definition.',
baseline_sql = TO_CLOB('SELECT COUNT(DISTINCT u."GUID") AS "RECENT_7DAY_AU"' || CHR(10)
|| 'FROM "SGMP_POC"."CZN_COMN_USER_MST" u' || CHR(10)
|| 'WHERE u."BASE_DT" = DATE ''2026-07-15''' || CHR(10)
|| ' AND u."LAST_CONN_DT" BETWEEN DATE ''2026-07-09'' AND DATE ''2026-07-15''' || CHR(10)
|| ' AND u."STD_USER_YN" = ''Y''' || CHR(10)
|| ' AND u."EXPT_USER_YN" = ''N'''),
baseline_answer = 'RECENT_7DAY_AU=0',
evaluation_rule_json = '{"required_sql_terms":["CZN_COMN_USER_MST","LAST_CONN_DT","STD_USER_YN","EXPT_USER_YN","COUNT"],"recommended_sql_terms":["BASE_DT"],"forbidden_sql_terms":["AU_FLAG"],"required_result_shape":"SINGLE_AGGREGATE"}',
updated_at = SYSTIMESTAMP
WHERE question_code = 'STD-25';
/
COMMIT;
/
SELECT question_code, expected_focus, baseline_sql, baseline_answer, evaluation_rule_json
FROM sg_ai_qa_question
WHERE question_code = 'STD-25';

View File

@@ -0,0 +1,40 @@
-- CZN-02 customer sample marks STD_USER_YN='Y' as optional for daily
-- standard-AU reporting. It must not turn an otherwise correct AU query into
-- a failure merely because the condition is present.
UPDATE sg_ai_qa_question
SET expected_focus = 'Daily standard AU: COUNT(DISTINCT GUID) from CZN_COMN_USER_MST '
|| 'for BASE_DT=2026-07-15 with AU_FLAG=1 and EXPT_USER_YN=''N''. '
|| 'STD_USER_YN=''Y'' is an allowed optional cohort filter in the customer sample; '
|| 'its presence or absence is not a contradiction to this baseline.',
evaluation_rule_json = '{"required_sql_terms":["CZN_COMN_USER_MST","AU_FLAG","EXPT_USER_YN","COUNT"],"recommended_sql_terms":["BASE_DT","STD_USER_YN"],"optional_sql_terms":["STD_USER_YN"]}',
updated_at = SYSTIMESTAMP
WHERE question_code = 'CZN-02';
/
UPDATE sg_qa_vector_example
SET answer_text = 'Expected focus: daily standard AU uses CZN_COMN_USER_MST, BASE_DT=2026-07-15, '
|| 'AU_FLAG=1 and EXPT_USER_YN=''N''. The customer sample permits STD_USER_YN=''Y'' '
|| 'as an optional standard-user cohort filter; do not treat its presence as a conflicting condition. '
|| 'Historical answer: STD_AU_COUNT=0',
inspection_note = 'Customer sample permits optional STD_USER_YN filtering for daily standard AU; AU_FLAG and excluded-user filtering remain mandatory.',
verified_at = SYSTIMESTAMP,
verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'CUSTOMER_QA_BENCHMARK'
AND source_case_id = 'CZN-02'
AND reference_status = 'APPROVED';
/
COMMIT;
/
SELECT q.question_code, q.expected_focus, q.evaluation_rule_json,
e.example_id, e.answer_text
FROM sg_ai_qa_question q
LEFT JOIN sg_qa_vector_example e
ON e.source_type = 'CUSTOMER_QA_BENCHMARK'
AND e.source_case_id = q.question_code
WHERE q.question_code = 'CZN-02';

View File

@@ -0,0 +1,90 @@
-- Reusable SINGLE-scope pattern: two AU populations must be aggregated
-- independently before comparison. A user-master LEFT JOIN may erase valid
-- business-user rows and must not define the business population.
UPDATE sg_ai_qa_question
SET expected_focus = 'Compare standard AU and business AU as two independent single-row aggregates for the same as-of date. '
|| 'Standard AU uses the resolved game user master with AU_FLAG=1 and EXPT_USER_YN=''N''. '
|| 'Business AU uses the resolved game business-user fact with BIZ_AU_FLAG=1 and EXPT_USER_YN=''N''. '
|| 'Do not make the business count depend on a LEFT JOIN from the user-master population. '
|| 'STD_USER_YN=''Y'' is an allowed optional cohort filter, not a reason to reject the result.',
evaluation_rule_json = '{"required_sql_terms":["CZN_COMN_USER_MST","CZN_CUSTOM_BIZ_USER_TXN","AU_FLAG","BIZ_AU_FLAG","EXPT_USER_YN","COUNT"],"recommended_sql_terms":["BASE_DT","STD_USER_YN"],"optional_sql_terms":["STD_USER_YN"],"required_result_shape":"SINGLE_COMPARISON"}',
updated_at = SYSTIMESTAMP
WHERE question_code = 'CZN-03';
/
DECLARE
v_input CLOB;
v_embedding VECTOR;
v_exists NUMBER;
BEGIN
v_input := TO_CLOB('Question pattern: compare daily standard active users and business active users for one resolved game and one business date.')
|| CHR(10) || 'Question pattern Korean: 한 게임의 기준일 일간 표준 AU와 사업 AU를 비교해줘.'
|| CHR(10) || 'Logical object role: USER_BUSINESS_AU_COMPARISON'
|| CHR(10) || 'Required result shape: one row with two independent aggregate metrics.'
|| CHR(10) || 'Business population must be aggregated independently; a LEFT JOIN from the user-master population may not define it.';
v_embedding := DBMS_VECTOR.UTL_TO_EMBEDDING(
v_input, JSON(sg_qa_vector_params('search_document'))
);
SELECT COUNT(*) INTO v_exists
FROM sg_qa_vector_example
WHERE source_type = 'POLICY_TEMPLATE'
AND source_case_id = 'STD_BIZ_AU_COMPARE';
IF v_exists = 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 (
'한 게임의 기준일 일간 표준 AU와 사업 AU를 비교해줘.',
TO_CLOB('SELECT' || CHR(10)
|| ' (SELECT COUNT(DISTINCT u."GUID")' || CHR(10)
|| ' FROM <RESOLVED_GAME_USER_MASTER> u' || CHR(10)
|| ' WHERE u."BASE_DT" = <BUSINESS_DATE>' || CHR(10)
|| ' AND u."AU_FLAG" = 1' || CHR(10)
|| ' AND u."EXPT_USER_YN" = ''N'') AS "STANDARD_AU_COUNT",' || CHR(10)
|| ' (SELECT COUNT(DISTINCT b."GUID")' || CHR(10)
|| ' FROM <RESOLVED_GAME_BUSINESS_USER> b' || CHR(10)
|| ' WHERE b."BASE_DT" = <BUSINESS_DATE>' || CHR(10)
|| ' AND b."BIZ_AU_FLAG" = 1' || CHR(10)
|| ' AND b."EXPT_USER_YN" = ''N'') AS "BUSINESS_AU_COUNT"' || CHR(10)
|| 'FROM DUAL'),
'Applicable metric reference: for this daily AU comparison, the standard metric must use AU_FLAG=1 and EXPT_USER_YN=''N''; do not replace it with LAST_CONN_DT period logic or STD_USER_YN alone. The business metric must use BIZ_AU_FLAG=1 and EXPT_USER_YN=''N''. Produce one row from two independent aggregate subqueries, and do not count business users through a LEFT JOIN from the standard-user population. STD_USER_YN may be added only as an optional cohort filter.',
v_input, v_embedding, 'cohere.embed-v4.0',
'APPROVED', 'SQL_TEMPLATE', 'SINGLE', 'USER_BUSINESS_AU_COMPARISON',
'VERIFIED',
'Reusable comparison pattern with logical placeholders only; no customer game, date, result, or physical object is embedded.',
SYSTIMESTAMP, 'SGMP_POC_METADATA_REVIEW',
'STD_BIZ_AU_COMPARE', 'POLICY_TEMPLATE'
);
ELSE
UPDATE sg_qa_vector_example
SET question = '한 게임의 기준일 일간 표준 AU와 사업 AU를 비교해줘.',
answer_sql = TO_CLOB('SELECT' || CHR(10)
|| ' (SELECT COUNT(DISTINCT u."GUID")' || CHR(10)
|| ' FROM <RESOLVED_GAME_USER_MASTER> u' || CHR(10)
|| ' WHERE u."BASE_DT" = <BUSINESS_DATE>' || CHR(10)
|| ' AND u."AU_FLAG" = 1' || CHR(10)
|| ' AND u."EXPT_USER_YN" = ''N'') AS "STANDARD_AU_COUNT",' || CHR(10)
|| ' (SELECT COUNT(DISTINCT b."GUID")' || CHR(10)
|| ' FROM <RESOLVED_GAME_BUSINESS_USER> b' || CHR(10)
|| ' WHERE b."BASE_DT" = <BUSINESS_DATE>' || CHR(10)
|| ' AND b."BIZ_AU_FLAG" = 1' || CHR(10)
|| ' AND b."EXPT_USER_YN" = ''N'') AS "BUSINESS_AU_COUNT"' || CHR(10)
|| 'FROM DUAL'),
answer_text = 'Applicable metric reference: for this daily AU comparison, the standard metric must use AU_FLAG=1 and EXPT_USER_YN=''N''; do not replace it with LAST_CONN_DT period logic or STD_USER_YN alone. The business metric must use BIZ_AU_FLAG=1 and EXPT_USER_YN=''N''. Produce one row from two independent aggregate subqueries, and do not count business users through a LEFT JOIN from the standard-user population. STD_USER_YN may be added only as an optional cohort filter.',
embedding_input = v_input,
embedding = v_embedding,
reference_status = 'APPROVED', inspection_status = 'VERIFIED',
verified_at = SYSTIMESTAMP, verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'POLICY_TEMPLATE'
AND source_case_id = 'STD_BIZ_AU_COMPARE';
END IF;
COMMIT;
END;
/

View File

@@ -0,0 +1,58 @@
-- Reusable SINGLE-scope pattern for country-grouped daily AU.
UPDATE sg_ai_qa_question
SET expected_focus = 'Country-grouped daily standard AU uses the resolved game user master with BASE_DT, AU_FLAG=1 and EXPT_USER_YN=''N''. '
|| 'Group by LAST_CONN_COUNTRY_CD and use the approved country dimension only for display/classification. '
|| 'STD_USER_YN is optional and cannot replace AU_FLAG for the daily metric.',
evaluation_rule_json = '{"required_sql_terms":["CZN_COMN_USER_MST","AU_FLAG","EXPT_USER_YN","LAST_CONN_COUNTRY_CD","COUNT"],"recommended_sql_terms":["BASE_DT","COMN_COUNTRY_BAS","STD_USER_YN"],"optional_sql_terms":["STD_USER_YN"],"required_result_shape":"COUNTRY_GROUPED"}',
updated_at = SYSTIMESTAMP
WHERE question_code = 'CZN-06';
/
DECLARE
v_input CLOB;
v_embedding VECTOR;
v_exists NUMBER;
BEGIN
v_input := TO_CLOB('Question pattern: show daily active-user counts by country for one resolved game and one business date.')
|| CHR(10) || 'Question pattern Korean: 한 게임의 기준일 주요 국가별 표준 AU 수를 알려줘.'
|| CHR(10) || 'Logical object role: COUNTRY_GROUPED_DAILY_AU'
|| CHR(10) || 'Required metric: AU_FLAG=1 and excluded-user filtering; group by the last connection country.';
v_embedding := DBMS_VECTOR.UTL_TO_EMBEDDING(v_input, JSON(sg_qa_vector_params('search_document')));
SELECT COUNT(*) INTO v_exists FROM sg_qa_vector_example
WHERE source_type = 'POLICY_TEMPLATE' AND source_case_id = 'COUNTRY_DAILY_AU';
IF v_exists = 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 (
'한 게임의 기준일 주요 국가별 표준 AU 수를 알려줘.',
TO_CLOB('SELECT u."LAST_CONN_COUNTRY_CD" AS "COUNTRY_CD",' || CHR(10)
|| ' c."COUNTRY_KR_NM" AS "COUNTRY_NAME",' || CHR(10)
|| ' COUNT(DISTINCT u."GUID") AS "STANDARD_AU_COUNT"' || CHR(10)
|| 'FROM <RESOLVED_GAME_USER_MASTER> u' || CHR(10)
|| 'LEFT JOIN <APPROVED_COUNTRY_DIMENSION> c' || CHR(10)
|| ' ON c."COUNTRY_2CHAR_CD" = u."LAST_CONN_COUNTRY_CD"' || CHR(10)
|| 'WHERE u."BASE_DT" = <BUSINESS_DATE>' || CHR(10)
|| ' AND u."AU_FLAG" = 1' || CHR(10)
|| ' AND u."EXPT_USER_YN" = ''N''' || CHR(10)
|| 'GROUP BY u."LAST_CONN_COUNTRY_CD", c."COUNTRY_KR_NM"'),
'Applicable metric reference: country-grouped daily AU must use AU_FLAG=1 and EXPT_USER_YN=''N''; do not replace AU_FLAG with STD_USER_YN alone. Group by LAST_CONN_COUNTRY_CD. Use an approved country dimension for country display or a current approved major-country classification when the request requires it.',
v_input, v_embedding, 'cohere.embed-v4.0',
'APPROVED', 'SQL_TEMPLATE', 'SINGLE', 'COUNTRY_GROUPED_DAILY_AU',
'VERIFIED', 'Reusable country-grouped daily-AU pattern; no customer game, date, result, or physical object is embedded.',
SYSTIMESTAMP, 'SGMP_POC_METADATA_REVIEW', 'COUNTRY_DAILY_AU', 'POLICY_TEMPLATE'
);
ELSE
UPDATE sg_qa_vector_example
SET embedding_input = v_input, embedding = v_embedding,
reference_status = 'APPROVED', inspection_status = 'VERIFIED',
verified_at = SYSTIMESTAMP, verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'POLICY_TEMPLATE' AND source_case_id = 'COUNTRY_DAILY_AU';
END IF;
COMMIT;
END;
/

View File

@@ -0,0 +1,17 @@
-- CZN-06 is a verified, exact customer question/SQL pair. It must be a
-- runtime Few-shot when approved; RETIRED is the DB switch that excludes it.
-- This is a single-game reference, so keep the retrieval scope explicit.
UPDATE sg_qa_vector_example
SET reference_status = 'APPROVED',
reference_kind = 'SQL_TEMPLATE',
target_type = 'SINGLE',
object_role = 'COUNTRY_GROUPED_DAILY_AU',
inspection_status = 'VERIFIED',
inspection_note = 'Verified exact CZN-06 Few-shot restored for runtime retrieval. Daily country AU requires AU_FLAG=1 and EXPT_USER_YN=''N''; STD_USER_YN alone is insufficient.',
verified_at = SYSTIMESTAMP,
verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'CUSTOMER_QA_BENCHMARK'
AND source_case_id = 'CZN-06'
/
COMMIT

View File

@@ -0,0 +1,69 @@
-- Runtime Few-shots remain semantic vector retrieval. Customer examples are
-- governed by their DB approval state, not restricted to exact text matches.
UPDATE sg_qa_vector_example
SET reference_status = 'APPROVED',
reference_kind = 'SQL_TEMPLATE',
target_type = 'SINGLE',
object_role = 'GAME_GOODS_HOLDINGS',
inspection_status = 'VERIFIED',
inspection_note = 'Verified CZN-07 semantic Few-shot. Use goods holdings, crystal dimension, RU_FLAG=1, excluded-user filter, nonzero holdings, and daily grouping.',
verified_at = SYSTIMESTAMP,
verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'CUSTOMER_QA_BENCHMARK'
AND source_case_id = 'CZN-07'
/
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')));
v_max_cosine_distance NUMBER;
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;
SELECT number_value INTO v_max_cosine_distance
FROM sg_game_scope_policy
WHERE policy_key = 'QA_VECTOR_MAX_COSINE_DISTANCE'
AND active_yn = 'Y'
AND number_value IS NOT NULL;
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,
cosine_distance
FROM (
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 inspection_status = 'VERIFIED'
AND answer_sql IS NOT NULL
AND (source_type = 'POLICY_TEMPLATE'
OR NOT REGEXP_LIKE(answer_sql, '<[A-Z][A-Z0-9_]*>', 'i'))
AND (target_type = 'ANY' OR v_target_type = 'ANY' OR target_type = v_target_type)
)
WHERE cosine_distance <= v_max_cosine_distance
ORDER BY cosine_distance, example_id
FETCH FIRST p_top_k ROWS ONLY;
RETURN v_results;
END;
/
COMMIT

View File

@@ -0,0 +1,87 @@
-- Keep semantic vector retrieval, but do not inject weak trailing neighbours
-- when a materially stronger example has already been found.
MERGE INTO sg_game_scope_policy t
USING (
SELECT 'QA_VECTOR_NEIGHBOR_DISTANCE_MARGIN' AS policy_key,
0.120000 AS number_value,
'Maximum additional cosine distance from the best runtime Few-shot candidate.' AS description
FROM dual
) s
ON (t.policy_key = s.policy_key)
WHEN MATCHED THEN UPDATE SET
t.number_value = s.number_value,
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, NULL, s.description, 'Y'
)
/
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')));
v_max_cosine_distance NUMBER;
v_neighbor_margin NUMBER;
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;
SELECT number_value INTO v_max_cosine_distance
FROM sg_game_scope_policy
WHERE policy_key = 'QA_VECTOR_MAX_COSINE_DISTANCE'
AND active_yn = 'Y'
AND number_value IS NOT NULL;
SELECT number_value INTO v_neighbor_margin
FROM sg_game_scope_policy
WHERE policy_key = 'QA_VECTOR_NEIGHBOR_DISTANCE_MARGIN'
AND active_yn = 'Y'
AND number_value IS NOT NULL;
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,
cosine_distance
FROM (
SELECT c.*,
MIN(c.cosine_distance) OVER () AS best_cosine_distance
FROM (
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 inspection_status = 'VERIFIED'
AND answer_sql IS NOT NULL
AND (source_type = 'POLICY_TEMPLATE'
OR NOT REGEXP_LIKE(answer_sql, '<[A-Z][A-Z0-9_]*>', 'i'))
AND (target_type = 'ANY' OR v_target_type = 'ANY' OR target_type = v_target_type)
) c
WHERE c.cosine_distance <= v_max_cosine_distance
)
WHERE cosine_distance <= best_cosine_distance + v_neighbor_margin
ORDER BY cosine_distance, example_id
FETCH FIRST p_top_k ROWS ONLY;
RETURN v_results;
END;
/
COMMIT

View File

@@ -0,0 +1,16 @@
-- CZN-08 is the reviewed semantic reference for daily standard-AU crystal
-- total and per-user average holdings.
UPDATE sg_qa_vector_example
SET reference_status = 'APPROVED',
reference_kind = 'SQL_TEMPLATE',
target_type = 'SINGLE',
object_role = 'GAME_GOODS_HOLDINGS',
inspection_status = 'VERIFIED',
inspection_note = 'Verified CZN-08 Few-shot. Standard-AU crystal holdings require AU_FLAG=1, excluded-user filtering, and per-user average as SUM(HAVE_CNT) / COUNT(DISTINCT GUID), not AVG(HAVE_CNT).',
verified_at = SYSTIMESTAMP,
verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'CUSTOMER_QA_BENCHMARK'
AND source_case_id = 'CZN-08'
/
COMMIT

View File

@@ -0,0 +1,11 @@
-- Calibrate the semantic-neighbour window using reviewed CZN patterns:
-- retain close paraphrases, exclude adjacent metric shapes.
UPDATE sg_game_scope_policy
SET number_value = 0.100000,
description = 'Maximum additional cosine distance from the best runtime Few-shot candidate.',
active_yn = 'Y',
updated_at = SYSTIMESTAMP
WHERE policy_key = 'QA_VECTOR_NEIGHBOR_DISTANCE_MARGIN'
/
COMMIT

View File

@@ -0,0 +1,15 @@
-- CZN-05 is the reviewed reference for country-grouped business AU.
UPDATE sg_qa_vector_example
SET reference_status = 'APPROVED',
reference_kind = 'SQL_TEMPLATE',
target_type = 'SINGLE',
object_role = 'COUNTRY_GROUPED_BUSINESS_AU',
inspection_status = 'VERIFIED',
inspection_note = 'Verified CZN-05 Few-shot. Join business-user data to user master on GUID and BASE_DT before grouping by user country; filter BIZ_AU_FLAG=1 and EXPT_USER_YN=''N''.',
verified_at = SYSTIMESTAMP,
verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'CUSTOMER_QA_BENCHMARK'
AND source_case_id = 'CZN-05'
/
COMMIT

View File

@@ -0,0 +1,95 @@
-- Hybrid retrieval remains database-driven: dense vector similarity handles
-- paraphrases, while lexical similarity distinguishes decisive request terms.
MERGE INTO sg_game_scope_policy t
USING (
SELECT 'QA_VECTOR_LEXICAL_WEIGHT' AS policy_key, 0.350000 AS number_value,
'Weight of normalized lexical question similarity in runtime Few-shot reranking.' AS description
FROM dual
UNION ALL
SELECT 'QA_VECTOR_HYBRID_SCORE_MARGIN', 0.050000,
'Maximum hybrid-score difference from the best runtime Few-shot candidate.'
FROM dual
) s
ON (t.policy_key = s.policy_key)
WHEN MATCHED THEN UPDATE SET
t.number_value = s.number_value,
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, NULL, s.description, 'Y'
)
/
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')));
v_max_cosine_distance NUMBER;
v_lexical_weight NUMBER;
v_hybrid_margin NUMBER;
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;
SELECT number_value INTO v_max_cosine_distance FROM sg_game_scope_policy
WHERE policy_key = 'QA_VECTOR_MAX_COSINE_DISTANCE' AND active_yn = 'Y';
SELECT number_value INTO v_lexical_weight FROM sg_game_scope_policy
WHERE policy_key = 'QA_VECTOR_LEXICAL_WEIGHT' AND active_yn = 'Y';
SELECT number_value INTO v_hybrid_margin FROM sg_game_scope_policy
WHERE policy_key = 'QA_VECTOR_HYBRID_SCORE_MARGIN' AND active_yn = 'Y';
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,
cosine_distance
FROM (
SELECT s.*,
MAX(s.hybrid_score) OVER () AS best_hybrid_score
FROM (
SELECT c.*,
((1 - v_lexical_weight) * (1 - c.cosine_distance)
+ v_lexical_weight * c.lexical_similarity) AS hybrid_score
FROM (
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,
UTL_MATCH.JARO_WINKLER_SIMILARITY(
DBMS_LOB.SUBSTR(question, 4000, 1),
DBMS_LOB.SUBSTR(p_question, 4000, 1)
) / 100 AS lexical_similarity
FROM sg_qa_vector_example
WHERE reference_status = 'APPROVED'
AND inspection_status = 'VERIFIED'
AND answer_sql IS NOT NULL
AND (source_type = 'POLICY_TEMPLATE'
OR NOT REGEXP_LIKE(answer_sql, '<[A-Z][A-Z0-9_]*>', 'i'))
AND (target_type = 'ANY' OR v_target_type = 'ANY' OR target_type = v_target_type)
) c
WHERE c.cosine_distance <= v_max_cosine_distance
) s
)
WHERE hybrid_score >= best_hybrid_score - v_hybrid_margin
ORDER BY hybrid_score DESC, cosine_distance, example_id
FETCH FIRST p_top_k ROWS ONLY;
RETURN v_results;
END;
/
COMMIT

View File

@@ -0,0 +1,15 @@
-- CZN-13 is the reviewed reference for Ether usage and distinct users.
UPDATE sg_qa_vector_example
SET reference_status = 'APPROVED',
reference_kind = 'SQL_TEMPLATE',
target_type = 'SINGLE',
object_role = 'GAME_GOODS_CHANGE',
inspection_status = 'VERIFIED',
inspection_note = 'Verified CZN-13 Few-shot. Ether usage requires goods-change data joined to the goods dimension and user master by GUID and BASE_DT, CHANGE_TYPE_CD=''USE'', active Ether dimension, excluded-user filter, and GOODS_CHANGE_CNT aggregation.',
verified_at = SYSTIMESTAMP,
verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'CUSTOMER_QA_BENCHMARK'
AND source_case_id = 'CZN-13'
/
COMMIT

View File

@@ -0,0 +1,15 @@
-- CZN-16 is the reviewed reference for purchasers of a named package.
UPDATE sg_qa_vector_example
SET reference_status = 'APPROVED',
reference_kind = 'SQL_TEMPLATE',
target_type = 'SINGLE',
object_role = 'SALES_PRODUCT_PURCHASER',
inspection_status = 'VERIFIED',
inspection_note = 'Verified CZN-16 Few-shot. Join sales transactions to product display by GAME_ID and PRODUCT_ID, filter the resolved package name and excluded users, and use the payment business date when counting distinct purchasers.',
verified_at = SYSTIMESTAMP,
verified_by = 'SGMP_POC_METADATA_REVIEW'
WHERE source_type = 'CUSTOMER_QA_BENCHMARK'
AND source_case_id = 'CZN-16'
/
COMMIT

View File

@@ -1,11 +1,13 @@
# SGMP Few-shot NL2SQL MCP (#735) # Redmine #735 · SGMP Few-shot NL2SQL MCP
> 상태: Approved > 상태: Approved
> 구현: `McpSseService`, `SelectAiService`, `McpProperties` > 구현: `McpSseService`, `SelectAiService`, `McpProperties`
## 목적 ## 목적
기존 Select AI Text2SQL 경로와 분리된 검증용 MCP tool을 제공한다. 질문과 유사한 검토 완료 예제를 벡터 검색하고, 그 SQL 패턴을 prompt에 참고자료로 넣은 뒤 `SHOWSQL`로 생성한 SQL을 읽기 전용으로 실행한다. 기존 Select AI Text2SQL 경로와 분리된 MCP tool을 제공한다. 질문과 의미가 유사한 검토
완료 예제를 DB에서 검색하고, 승인 SQL 구조를 참고자료로 넣은 뒤 `SHOWSQL`로 생성한 SQL을
읽기 전용으로 실행한다.
## MCP 계약 ## MCP 계약
@@ -13,11 +15,26 @@
- **입력**: `prompt` (최대 4,000자) - **입력**: `prompt` (최대 4,000자)
- **출력**: 벡터 Few-shot 예제(질문, SQL, 모델, cosine distance), 생성 SQL, 실행 상태, 행 수, 최대 100건 결과 - **출력**: 벡터 Few-shot 예제(질문, SQL, 모델, cosine distance), 생성 SQL, 실행 상태, 행 수, 최대 100건 결과
Few-shot SQL은 실행하지 않는다. 현재 메타데이터·alias 정책을 우선하고, Select AI가 새로 생성한 SQL만 read-only 검증 후 실행한다. Few-shot SQL은 실행하지 않는다. 현재 메타데이터·alias 정책을 우선하고, Select AI가 새로
생성한 SQL만 read-only 검증 후 실행한다.
## DB 검색·승인 기준
- `sg_qa_vector_example``reference_status='APPROVED'`,
`inspection_status='VERIFIED'`, SQL 존재 예제만 후보가 된다.
- `RETIRED`는 동일 질문이라도 후보에서 제외된다. 리타이어 여부는 DB 컬럼으로 저장하며
Java가 별도 목록으로 판단하지 않는다.
- 벡터 cosine distance가 `QA_VECTOR_MAX_COSINE_DISTANCE` 이하여야 한다.
- 후보 순위는 벡터 유사도와 질문 문자열 유사도를 DB에서 결합한다. 정책값
`QA_VECTOR_LEXICAL_WEIGHT=0.35`, `QA_VECTOR_HYBRID_SCORE_MARGIN=0.05`
유사한 후속 질문에는 참고 범위를 남기되, 핵심 요청어가 다른 예제가 최상위가 되는 문제를
줄인다.
- 고객 기준 예제는 개별 승인 상태로 관리한다. 같은 문장만 허용하는 방식으로 제한하지 않으며,
승인된 SQL 구조가 새 질문의 참고 근거가 될 수 있다.
## 안전 규칙 ## 안전 규칙
1. 벡터 검색 실패는 `UNAVAILABLE` 상태로 남기고 기존 정책 prompt로 폴백한다. 1. 검색 후보가 없으면 `NO_MATCH`로 기록하고 일반 Select AI 생성은 계속한다.
2. 생성 결과는 단일 `SELECT` 또는 `WITH`만 허용한다. 2. 생성 결과는 단일 `SELECT` 또는 `WITH`만 허용한다.
3. DDL, DML, PL/SQL, 시스템 객체, 잠금 구문, 다중 문장은 차단한다. 3. DDL, DML, PL/SQL, 시스템 객체, 잠금 구문, 다중 문장은 차단한다.
4. JDBC read-only 트랜잭션과 30초 query timeout, 최대 100행 제한을 적용한다. 4. JDBC read-only 트랜잭션과 30초 query timeout, 최대 100행 제한을 적용한다.

View File

@@ -0,0 +1,41 @@
# Redmine #739 · DB 소유 게임별 실행 작업 계약
## 목표
복수 게임 질문에서 Portal과 `smilegate_fewshot_nl2sql`은 게임명을 재해석하거나 원 질문을
수정하지 않는다. ADB의 `sg_game_query_plan`이 DB 카탈로그·OCI Chat 결과를 바탕으로 실행
가능한 작업을 만들고, 호출자는 그 작업을 그대로 실행한다.
## 계약
`executionTasks``QUERY` 작업은 다음 값을 모두 가진다.
- `action`: `QUERY`
- `workerTool`: 호출할 worker 도구명
- `workerArguments.prompt`: 원 질문의 지표·날짜·필터·결과 모양을 보존한 해당 target 전용 질의
- `workerArguments.scopeGameKey`: DB가 확정한 game key
- `workerArguments.queryPlan`: 해당 target 하나만 담긴 `SINGLE` plan
- `fewShotArguments.question`: 보조 진단 도구가 필요할 때만 사용할 같은 target 전용 질의
`REPORT_UNAVAILABLE` 작업은 worker 인자를 갖지 않으며 최종 응답 항목으로만 사용한다.
## 책임 분리
| 구성요소 | 책임 |
| --- | --- |
| `sg_game_query_plan` | 게임 식별, 데이터 가능 여부, target별 자연어 작업 생성, 단일 target plan 생성 |
| Portal 오케스트레이터 | 작업 순회, worker 호출, 결과 합성, task 완료 판정 |
| `fewshot_preflight` | reasoning에서 필요할 때만 후보 적합성을 확인하는 보조 진단 도구 |
| `smilegate_fewshot_nl2sql` | 전달된 단일 작업으로 DB Few-shot 검색·승인 판정 후 SQL 생성 및 읽기 전용 실행 |
## 안전 규칙
- planner가 생성한 target 전용 작업 질의에는 다른 계획 target의 실제 mention이 포함되면 실패 처리한다. 호출자가 문자열을 제거하거나 보정하지 않는다.
- worker는 `queryPlan`이 단일 target인지 검증만 하며, 전체 plan에서 target을 추출하거나 원 질문을 재작성하지 않는다.
- worker의 Few-shot 검색은 DB가 발급한 `workerArguments.prompt`로 수행한다. preflight 결과는 선택적으로만 전달할 수 있으며 worker 실행의 조건이 아니다.
- Java는 지표별 SQL 조건, 결과 모양, Few-shot 활성화/리타이어, 후보 유사도에 관여하지 않는다.
이 정책은 DB 메타데이터와 `sg_qa_vector_search`에서 관리한다.
## 검증
STD-11에서 Bubblyz는 `REPORT_UNAVAILABLE`, 카제나는 `QUERY` 한 건이 되어야 한다. 카제나 worker에 전달되는 prompt·plan·생성 SQL 어디에도 Bubblyz가 없어야 하며, 결과는 2026-07-15 매출 227681이어야 한다.

View File

@@ -0,0 +1,118 @@
# Smilegate Data & AI PoC — 현재 MCP 도구 운영 안내
## 1. 현재 기본 실행 경로
모든 게임 데이터 질문은 먼저 `oracle.select_ai.game_query_plan`을 호출한다.
```text
일반 데이터 분석 질문
game_query_plan → smilegate_fewshot_nl2sql
게임별·일반 분석 질문
game_query_plan → executionTasks → smilegate_fewshot_nl2sql
```
`game_query_plan`은 게임 카탈로그, 별칭, 벡터 검색 결과를 사용하여 질의 범위를 `NONE`,
`SINGLE`, `MULTI`, `ALL` 중 하나로 판정하고 `executionTasks`를 발급한다. 다음 worker에는
원 질문을 보존한 DB 작업 인자와 `GAME QUERY REFERENCE`를 전달한다. Portal은 이를 보정하지
않고 task 종료 상태를 합산한다.
## 2. 기본 실행 MCP 도구
### `oracle.select_ai.game_query_plan`
- 모든 게임 데이터 질의의 필수 첫 단계다.
- OCI GenAI Chat으로 질문에서 게임명 후보를 식별하고 게임 카탈로그 벡터 검색 결과를 검증한다.
- `targetType`, `supportedGames`, `dataEligibleTargets`, 미매칭 대상을 반환한다.
- 게임명·prefix·물리 테이블을 Java나 포털 코드에서 하드코딩하지 않는다.
### `oracle.select_ai.smilegate_fewshot_nl2sql`
- AU 외 일반 분석 질의의 주 실행 도구다.
- 현재 질문으로 QA 벡터 저장소에서 승인·검증된 유사 Few-shot 예제를 찾는다.
- 선택된 Few-shot 예제의 질문·승인 SQL·논리 객체 역할을 현재 질문 및 `queryPlan`과 함께 Select AI 프롬프트에 추가한다.
- ADB `DBMS_CLOUD_AI.GENERATE(..., 'showsql')`로 SQL을 생성한다.
- 생성 SQL을 읽기 전용으로 검증한 후 실행한다.
- Few-shot 근거, 생성 SQL, 실행 결과, 행 수를 반환한다.
- 예제 승인·리타이어·유사도 기준은 DB가 관리한다. Java/Portal은 지표 조건이나
검색 결과를 보정하지 않는다.
### `oracle.select_ai.game_daily_au_lookup`
- 고정 정의 AU를 별도로 점검할 때 쓰는 전용 도구다. 고객 질의의 기본 실행 경로는
`executionTasks`가 지정한 worker이며, 일반적으로 `smilegate_fewshot_nl2sql`이다.
- 입력은 `queryPlan`과 선택적인 `baseDate(YYYY-MM-DD)`다.
- `queryPlan`의 게임 키를 `SG_GAME_CATALOG`에서 다시 검증하고 `USER_MASTER_OBJECT_NAME`을 동적으로 선택한다.
- 선택된 사용자 마스터 객체에 대해 아래 정의로 AU를 집계한다.
```sql
SELECT COUNT(DISTINCT GUID) AS AU_COUNT
FROM <catalog_user_master_object>
WHERE BASE_DT = :baseDate
AND AU_FLAG = 1
AND EXPT_USER_YN = 'N'
```
- 게임명·별칭·prefix·물리 테이블명을 입력값이나 코드에서 직접 사용하지 않는다.
- `SINGLE`, `MULTI`, `ALL` 계획의 각 대상에 대해 결과를 반환한다.
## 3. 등록돼 있으나 기본 경로에서 직접 실행하지 않는 도구
### `oracle.select_ai.qa_vector_search`
- 유사 질문과 승인 SQL 예제를 직접 확인하는 관리자·점검용 도구다.
- 일반 질의에서는 `smilegate_fewshot_nl2sql`이 내부적으로 Few-shot 검색을 수행하므로 별도 호출하지 않는다.
### `oracle.select_ai.qa_vector_store`
- 검토 완료한 질문·읽기 전용 SQL·검토 메모를 Few-shot 벡터 지식으로 저장하는 관리자 도구다.
- 사용자 질의 실행 중에는 호출하지 않는다.
### `oracle.select_ai.game_catalog_resolve`
- 게임명 후보의 벡터 검색 결과를 독립적으로 점검하는 진단 도구다.
- 정상 흐름에서는 `game_query_plan` 내부의 게임 식별 과정이 이 역할을 수행한다.
### `oracle.select_ai.game_scope_resolve`
- 과거 게임 범위와 별칭 매칭을 점검하기 위한 보조 도구다.
- 현재 기본 판정 기준은 `game_query_plan`이므로 정상 경로에는 넣지 않는다.
### `oracle.select_ai.smilegate_game_text2sql`
- Few-shot을 붙이지 않은 기본 Select AI 결과를 비교·점검하는 보조 Text2SQL 도구다.
- 고객용 기본 분석 경로는 `smilegate_fewshot_nl2sql`이다.
### `oracle.select_ai.smilegate_game_showprompt`
- SQL 생성에 전달된 최종 Select AI 프롬프트를 확인하는 진단 도구다.
- 테이블 comment, 컬럼 annotation, 제약조건, 게임 범위 계획, Few-shot 근거가 프롬프트에 반영됐는지 점검한다.
- SQL을 실행하지 않는다.
### `oracle.select_ai.fewshot_preflight`
- Few-shot 후보의 적합성을 별도로 확인할 때 사용하는 점검 도구다.
- 현재 `smilegate.cloud-handson.com` 포털의 기본 질문 allowlist와 기본 실행 경로에는 직접 넣지 않는다.
## 4. 실제 검증 결과
| 항목 | 확인 결과 |
|---|---|
| 질문 | 카제나의 2026-07-15 AU |
| 게임 범위 | `SINGLE / SUPPORTED` |
| 게임 키 | `STOVE_CHAOSZERO` |
| 카탈로그 선택 객체 | `CZN_COMN_USER_MST` |
| AU 실행 상태 | `GAME_AU_LOOKUP / READY` |
| 기준일 | `2026-07-15` |
| 반환 AU | `0` |
이 검증에서 테이블명은 코드에 고정하지 않았으며, 게임 계획 결과와 `SG_GAME_CATALOG` 메타데이터를 통해 선택됐다.
## 5. 운영 원칙
1. 게임 범위 판단과 실행 작업 생성은 항상 `game_query_plan`이 담당한다.
2. Portal은 DB task를 실행·합성하며, 질문·SQL·결과를 보정하지 않는다.
3. 분석성 질의는 승인·검증된 Few-shot 기반 Select AI로 처리한다.
4. `RETIRED` 예제는 검색하지 않으며, 승인/리타이어는 DB에 저장한다.
5. 정답지 SQL을 런타임에 실행하지 않는다. Select AI가 새로 만든 읽기 전용 SQL만 실행한다.
6. 문제 발생 시 SHOWPROMPT, 생성 SQL, 실행 결과, 판정 이력을 근거로 metadata·Few-shot을 보완한다.