From 022ae7f9d22e7aa5e39db0db568879d0334965ec Mon Sep 17 00:00:00 2001 From: devmrko Date: Thu, 30 Jul 2026 16:41:28 +0900 Subject: [PATCH] refs #739: document DB-owned task and few-shot policy --- ...mp_std25_weekly_au_evaluation_criteria.sql | 29 +++++ ...standard_au_optional_std_user_criteria.sql | 40 ++++++ ...sgmp_independent_au_comparison_pattern.sql | 90 +++++++++++++ .../adb/132_sgmp_country_daily_au_pattern.sql | 58 +++++++++ .../133_sgmp_restore_czn06_exact_fewshot.sql | 17 +++ .../135_sgmp_semantic_fewshot_retrieval.sql | 69 ++++++++++ .../adb/136_sgmp_fewshot_neighbor_margin.sql | 87 +++++++++++++ ..._restore_czn08_crystal_average_fewshot.sql | 16 +++ ...8_sgmp_tighten_fewshot_neighbor_margin.sql | 11 ++ ...tore_czn05_country_business_au_fewshot.sql | 15 +++ .../adb/140_sgmp_hybrid_fewshot_reranking.sql | 95 ++++++++++++++ ...sgmp_restore_czn13_ether_usage_fewshot.sql | 15 +++ ...mp_restore_czn16_product_buyer_fewshot.sql | 15 +++ .../735-sgmp-fewshot-nl2sql-mcp/README.md | 25 +++- .../README.md | 41 ++++++ docs/smilegate-mcp-current-operation-guide.md | 118 ++++++++++++++++++ 16 files changed, 737 insertions(+), 4 deletions(-) create mode 100644 database/adb/129_sgmp_std25_weekly_au_evaluation_criteria.sql create mode 100644 database/adb/130_sgmp_czn02_standard_au_optional_std_user_criteria.sql create mode 100644 database/adb/131_sgmp_independent_au_comparison_pattern.sql create mode 100644 database/adb/132_sgmp_country_daily_au_pattern.sql create mode 100644 database/adb/133_sgmp_restore_czn06_exact_fewshot.sql create mode 100644 database/adb/135_sgmp_semantic_fewshot_retrieval.sql create mode 100644 database/adb/136_sgmp_fewshot_neighbor_margin.sql create mode 100644 database/adb/137_sgmp_restore_czn08_crystal_average_fewshot.sql create mode 100644 database/adb/138_sgmp_tighten_fewshot_neighbor_margin.sql create mode 100644 database/adb/139_sgmp_restore_czn05_country_business_au_fewshot.sql create mode 100644 database/adb/140_sgmp_hybrid_fewshot_reranking.sql create mode 100644 database/adb/141_sgmp_restore_czn13_ether_usage_fewshot.sql create mode 100644 database/adb/142_sgmp_restore_czn16_product_buyer_fewshot.sql create mode 100644 docs/design/741-db-owned-execution-task-contract/README.md create mode 100644 docs/smilegate-mcp-current-operation-guide.md diff --git a/database/adb/129_sgmp_std25_weekly_au_evaluation_criteria.sql b/database/adb/129_sgmp_std25_weekly_au_evaluation_criteria.sql new file mode 100644 index 0000000..407a789 --- /dev/null +++ b/database/adb/129_sgmp_std25_weekly_au_evaluation_criteria.sql @@ -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'; diff --git a/database/adb/130_sgmp_czn02_standard_au_optional_std_user_criteria.sql b/database/adb/130_sgmp_czn02_standard_au_optional_std_user_criteria.sql new file mode 100644 index 0000000..5bc2ae9 --- /dev/null +++ b/database/adb/130_sgmp_czn02_standard_au_optional_std_user_criteria.sql @@ -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'; diff --git a/database/adb/131_sgmp_independent_au_comparison_pattern.sql b/database/adb/131_sgmp_independent_au_comparison_pattern.sql new file mode 100644 index 0000000..3268bfc --- /dev/null +++ b/database/adb/131_sgmp_independent_au_comparison_pattern.sql @@ -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 u' || CHR(10) + || ' WHERE u."BASE_DT" = ' || 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 b' || CHR(10) + || ' WHERE b."BASE_DT" = ' || 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 u' || CHR(10) + || ' WHERE u."BASE_DT" = ' || 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 b' || CHR(10) + || ' WHERE b."BASE_DT" = ' || 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; + +/ diff --git a/database/adb/132_sgmp_country_daily_au_pattern.sql b/database/adb/132_sgmp_country_daily_au_pattern.sql new file mode 100644 index 0000000..0579589 --- /dev/null +++ b/database/adb/132_sgmp_country_daily_au_pattern.sql @@ -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 u' || CHR(10) + || 'LEFT JOIN c' || CHR(10) + || ' ON c."COUNTRY_2CHAR_CD" = u."LAST_CONN_COUNTRY_CD"' || CHR(10) + || 'WHERE u."BASE_DT" = ' || 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; + +/ diff --git a/database/adb/133_sgmp_restore_czn06_exact_fewshot.sql b/database/adb/133_sgmp_restore_czn06_exact_fewshot.sql new file mode 100644 index 0000000..f212bc4 --- /dev/null +++ b/database/adb/133_sgmp_restore_czn06_exact_fewshot.sql @@ -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 diff --git a/database/adb/135_sgmp_semantic_fewshot_retrieval.sql b/database/adb/135_sgmp_semantic_fewshot_retrieval.sql new file mode 100644 index 0000000..a91d3a1 --- /dev/null +++ b/database/adb/135_sgmp_semantic_fewshot_retrieval.sql @@ -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 diff --git a/database/adb/136_sgmp_fewshot_neighbor_margin.sql b/database/adb/136_sgmp_fewshot_neighbor_margin.sql new file mode 100644 index 0000000..3a0c2d3 --- /dev/null +++ b/database/adb/136_sgmp_fewshot_neighbor_margin.sql @@ -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 diff --git a/database/adb/137_sgmp_restore_czn08_crystal_average_fewshot.sql b/database/adb/137_sgmp_restore_czn08_crystal_average_fewshot.sql new file mode 100644 index 0000000..97344b8 --- /dev/null +++ b/database/adb/137_sgmp_restore_czn08_crystal_average_fewshot.sql @@ -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 diff --git a/database/adb/138_sgmp_tighten_fewshot_neighbor_margin.sql b/database/adb/138_sgmp_tighten_fewshot_neighbor_margin.sql new file mode 100644 index 0000000..9ee6eba --- /dev/null +++ b/database/adb/138_sgmp_tighten_fewshot_neighbor_margin.sql @@ -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 diff --git a/database/adb/139_sgmp_restore_czn05_country_business_au_fewshot.sql b/database/adb/139_sgmp_restore_czn05_country_business_au_fewshot.sql new file mode 100644 index 0000000..f32c5cb --- /dev/null +++ b/database/adb/139_sgmp_restore_czn05_country_business_au_fewshot.sql @@ -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 diff --git a/database/adb/140_sgmp_hybrid_fewshot_reranking.sql b/database/adb/140_sgmp_hybrid_fewshot_reranking.sql new file mode 100644 index 0000000..51fffdc --- /dev/null +++ b/database/adb/140_sgmp_hybrid_fewshot_reranking.sql @@ -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 diff --git a/database/adb/141_sgmp_restore_czn13_ether_usage_fewshot.sql b/database/adb/141_sgmp_restore_czn13_ether_usage_fewshot.sql new file mode 100644 index 0000000..4c99c64 --- /dev/null +++ b/database/adb/141_sgmp_restore_czn13_ether_usage_fewshot.sql @@ -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 diff --git a/database/adb/142_sgmp_restore_czn16_product_buyer_fewshot.sql b/database/adb/142_sgmp_restore_czn16_product_buyer_fewshot.sql new file mode 100644 index 0000000..378cba7 --- /dev/null +++ b/database/adb/142_sgmp_restore_czn16_product_buyer_fewshot.sql @@ -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 diff --git a/docs/design/735-sgmp-fewshot-nl2sql-mcp/README.md b/docs/design/735-sgmp-fewshot-nl2sql-mcp/README.md index 7a255e2..c17b8f0 100644 --- a/docs/design/735-sgmp-fewshot-nl2sql-mcp/README.md +++ b/docs/design/735-sgmp-fewshot-nl2sql-mcp/README.md @@ -1,11 +1,13 @@ -# SGMP Few-shot NL2SQL MCP (#735) +# Redmine #735 · SGMP Few-shot NL2SQL MCP > 상태: Approved > 구현: `McpSseService`, `SelectAiService`, `McpProperties` ## 목적 -기존 Select AI Text2SQL 경로와 분리된 검증용 MCP tool을 제공한다. 질문과 유사한 검토 완료 예제를 벡터 검색하고, 그 SQL 패턴을 prompt에 참고자료로 넣은 뒤 `SHOWSQL`로 생성한 SQL을 읽기 전용으로 실행한다. +기존 Select AI Text2SQL 경로와 분리된 MCP tool을 제공한다. 질문과 의미가 유사한 검토 +완료 예제를 DB에서 검색하고, 승인 SQL 구조를 참고자료로 넣은 뒤 `SHOWSQL`로 생성한 SQL을 +읽기 전용으로 실행한다. ## MCP 계약 @@ -13,11 +15,26 @@ - **입력**: `prompt` (최대 4,000자) - **출력**: 벡터 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`만 허용한다. 3. DDL, DML, PL/SQL, 시스템 객체, 잠금 구문, 다중 문장은 차단한다. 4. JDBC read-only 트랜잭션과 30초 query timeout, 최대 100행 제한을 적용한다. diff --git a/docs/design/741-db-owned-execution-task-contract/README.md b/docs/design/741-db-owned-execution-task-contract/README.md new file mode 100644 index 0000000..17344b1 --- /dev/null +++ b/docs/design/741-db-owned-execution-task-contract/README.md @@ -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이어야 한다. diff --git a/docs/smilegate-mcp-current-operation-guide.md b/docs/smilegate-mcp-current-operation-guide.md new file mode 100644 index 0000000..d79f132 --- /dev/null +++ b/docs/smilegate-mcp-current-operation-guide.md @@ -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 +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을 보완한다.