-- Vector-score candidate selection. The closest catalog vector is accepted -- only when it is within the customer-managed policy threshold. CREATE OR REPLACE FUNCTION sg_game_match_candidate( p_mention IN CLOB, p_candidates_json IN CLOB ) RETURN CLOB AUTHID DEFINER IS v_candidates JSON_ARRAY_T; v_candidate JSON_OBJECT_T; v_result JSON_OBJECT_T := JSON_OBJECT_T(); v_threshold NUMBER; v_distance NUMBER; v_game_key VARCHAR2(128); BEGIN SELECT number_value INTO v_threshold FROM sg_game_scope_policy WHERE policy_key = 'GAME_ALIAS_MAX_COSINE_DISTANCE' AND active_yn = 'Y'; v_candidates := JSON_ARRAY_T.parse(p_candidates_json); IF v_candidates.get_size = 0 THEN v_result.put('status', 'UNMATCHED'); v_result.put_null('game_key'); v_result.put('reason', 'No active game catalog vector candidate was returned.'); RETURN v_result.to_clob; END IF; v_candidate := TREAT(v_candidates.get(0) AS JSON_OBJECT_T); v_distance := v_candidate.get_number('cosineDistance'); v_game_key := v_candidate.get_string('gameKey'); IF v_distance <= v_threshold THEN v_result.put('status', 'MATCHED'); v_result.put('game_key', v_game_key); v_result.put( 'reason', 'Closest catalog vector distance ' || TO_CHAR(v_distance, 'FM0D000000') || ' is within configured maximum ' || TO_CHAR(v_threshold, 'FM0D000000') || '.' ); ELSE v_result.put('status', 'UNMATCHED'); v_result.put_null('game_key'); v_result.put( 'reason', 'Closest catalog vector distance ' || TO_CHAR(v_distance, 'FM0D000000') || ' exceeds configured maximum ' || TO_CHAR(v_threshold, 'FM0D000000') || '.' ); END IF; RETURN v_result.to_clob; END; / -- A game can be known to the scope registry while not being an active alias -- source for a common fact query. Keep that availability separate from a -- role-specific physical-object availability such as a user-master table. CREATE OR REPLACE FUNCTION sg_game_fact_scope_status( p_game_id IN VARCHAR2 ) RETURN VARCHAR2 AUTHID DEFINER IS v_exists NUMBER; BEGIN SELECT COUNT(*) INTO v_exists FROM SGMP_POC.comn_game_alias_bas WHERE use_yn = 'Y' AND game_id = p_game_id; RETURN CASE WHEN v_exists > 0 THEN 'ACTIVE_ALIAS' ELSE 'REGISTRY_ONLY' END; END; / -- Complete game query planning inside ADB. The MCP server only invokes this -- function and returns its structured result. CREATE OR REPLACE FUNCTION sg_game_query_plan( p_question IN CLOB, p_top_k IN PLS_INTEGER DEFAULT 5 ) RETURN CLOB AUTHID DEFINER IS TYPE t_seen_game_keys IS TABLE OF BOOLEAN INDEX BY VARCHAR2(128); v_extract_raw CLOB; v_extract JSON_OBJECT_T; v_mentions JSON_ARRAY_T; v_scope_type VARCHAR2(30); v_target_type VARCHAR2(20); v_targets JSON_ARRAY_T := JSON_ARRAY_T(); v_supported JSON_ARRAY_T := JSON_ARRAY_T(); v_data_eligible JSON_ARRAY_T := JSON_ARRAY_T(); v_data_ineligible JSON_ARRAY_T := JSON_ARRAY_T(); v_unresolved JSON_ARRAY_T := JSON_ARRAY_T(); v_unmatched JSON_ARRAY_T := JSON_ARRAY_T(); v_mention_results JSON_ARRAY_T := JSON_ARRAY_T(); v_execution_tasks JSON_ARRAY_T := JSON_ARRAY_T(); v_seen_game_keys t_seen_game_keys; v_result JSON_OBJECT_T := JSON_OBJECT_T(); v_reference_summary JSON_OBJECT_T := JSON_OBJECT_T(); v_select_ai_reference CLOB; v_plan_status VARCHAR2(30); v_matched_count PLS_INTEGER := 0; v_data_eligible_count PLS_INTEGER := 0; v_unmatched_count PLS_INTEGER := 0; v_mention VARCHAR2(1000); v_candidates JSON_ARRAY_T; v_candidate JSON_OBJECT_T; v_decision_raw CLOB; v_decision JSON_OBJECT_T; v_decision_status VARCHAR2(30); v_selected_key VARCHAR2(128); v_reason VARCHAR2(4000); v_matched_candidate JSON_OBJECT_T; v_mention_result JSON_OBJECT_T; v_target JSON_OBJECT_T; v_unmatched_item JSON_OBJECT_T; v_execution_task JSON_OBJECT_T; v_worker_arguments JSON_OBJECT_T; v_fewshot_arguments JSON_OBJECT_T; v_task_plan JSON_OBJECT_T; v_task_reference JSON_OBJECT_T; v_task_targets JSON_ARRAY_T; v_excluded_mentions JSON_ARRAY_T; v_task_question CLOB; v_cursor SYS_REFCURSOR; v_game_key VARCHAR2(128); v_game_id VARCHAR2(128); v_game_prefix VARCHAR2(128); v_game_nm VARCHAR2(512); v_game_alias_nm VARCHAR2(512); v_user_master_object_name VARCHAR2(128); v_fact_scope_status VARCHAR2(30); v_cosine_distance NUMBER; BEGIN v_extract_raw := sg_game_extract_mentions(p_question); v_extract := JSON_OBJECT_T.parse(v_extract_raw); v_mentions := v_extract.get_array('game_mentions'); v_scope_type := NVL(v_extract.get_string('scope_hint'), 'UNKNOWN'); IF v_scope_type = 'ALL_GAMES' THEN v_target_type := 'ALL'; ELSIF v_mentions.get_size = 0 THEN v_target_type := 'NONE'; ELSIF v_mentions.get_size = 1 THEN v_target_type := 'SINGLE'; ELSE v_target_type := 'MULTI'; END IF; IF v_target_type = 'ALL' THEN FOR game_row IN ( SELECT game_key, game_id, game_prefix, game_nm, game_alias_nm, user_master_object_name FROM SGMP_POC.sg_game_catalog WHERE active_yn = 'Y' ORDER BY priority, game_key ) LOOP v_target := JSON_OBJECT_T(); v_target.put_null('mention'); v_target.put('status', 'CATALOG'); v_target.put('matchStatus', 'MATCHED'); v_target.put('gameKey', game_row.game_key); v_target.put('gameId', game_row.game_id); v_target.put('gamePrefix', game_row.game_prefix); v_target.put('gameName', game_row.game_nm); v_target.put('aliases', game_row.game_alias_nm); v_target.put( 'userMasterObjectName', game_row.user_master_object_name ); v_target.put( 'objectStatus', CASE WHEN game_row.user_master_object_name IS NULL THEN 'UNAVAILABLE' ELSE 'AVAILABLE' END ); v_fact_scope_status := sg_game_fact_scope_status(game_row.game_id); v_target.put('factScopeStatus', v_fact_scope_status); v_target.put( 'dataStatus', CASE WHEN v_fact_scope_status = 'ACTIVE_ALIAS' THEN 'AVAILABLE' ELSE 'UNAVAILABLE' END ); v_target.put_null('cosineDistance'); v_target.put('reasonCode', 'ALL_GAMES_CATALOG'); v_target.put('reason', 'Active game returned from the database catalog.'); v_targets.append(v_target); v_matched_count := v_matched_count + 1; IF game_row.user_master_object_name IS NOT NULL THEN v_supported.append(v_target); END IF; IF v_fact_scope_status = 'ACTIVE_ALIAS' THEN v_data_eligible.append(v_target); v_data_eligible_count := v_data_eligible_count + 1; ELSE v_data_ineligible.append(v_target); v_unresolved.append(v_target); END IF; END LOOP; ELSIF v_target_type = 'NONE' THEN v_target := JSON_OBJECT_T(); v_target.put_null('mention'); v_target.put('status', 'NONE'); v_target.put('matchStatus', 'NOT_APPLICABLE'); v_target.put_null('gameKey'); v_target.put_null('gameId'); v_target.put_null('gamePrefix'); v_target.put_null('gameName'); v_target.put_null('aliases'); v_target.put_null('userMasterObjectName'); v_target.put('objectStatus', 'NOT_APPLICABLE'); v_target.put('factScopeStatus', 'NOT_APPLICABLE'); v_target.put('dataStatus', 'NOT_APPLICABLE'); v_target.put_null('cosineDistance'); v_target.put('reasonCode', 'NO_GAME_TARGET'); v_target.put( 'reason', 'The question does not select a particular game.' ); v_targets.append(v_target); ELSE FOR i IN 0 .. v_mentions.get_size - 1 LOOP v_mention := v_mentions.get_string(i); v_candidates := JSON_ARRAY_T(); v_cursor := SGMP_POC.sg_game_catalog_search( v_mention, LEAST(GREATEST(NVL(p_top_k, 5), 1), 20) ); LOOP FETCH v_cursor INTO v_game_key, v_game_id, v_game_prefix, v_game_nm, v_game_alias_nm, v_user_master_object_name, v_cosine_distance; EXIT WHEN v_cursor%NOTFOUND; v_candidate := JSON_OBJECT_T(); v_candidate.put('gameKey', v_game_key); v_candidate.put('gameId', v_game_id); v_candidate.put('gamePrefix', v_game_prefix); v_candidate.put('gameName', v_game_nm); v_candidate.put('aliases', v_game_alias_nm); v_candidate.put( 'userMasterObjectName', v_user_master_object_name ); v_candidate.put( 'objectStatus', CASE WHEN v_user_master_object_name IS NULL THEN 'UNAVAILABLE' ELSE 'AVAILABLE' END ); v_candidate.put( 'factScopeStatus', sg_game_fact_scope_status(v_game_id) ); v_candidate.put('cosineDistance', v_cosine_distance); v_candidates.append(v_candidate); END LOOP; CLOSE v_cursor; v_decision_raw := sg_game_match_candidate( v_mention, v_candidates.to_clob ); v_decision := JSON_OBJECT_T.parse(v_decision_raw); v_decision_status := UPPER( NVL(v_decision.get_string('status'), 'UNMATCHED') ); v_selected_key := v_decision.get_string('game_key'); v_reason := v_decision.get_string('reason'); v_matched_candidate := NULL; IF v_decision_status = 'MATCHED' AND v_selected_key IS NOT NULL THEN FOR j IN 0 .. v_candidates.get_size - 1 LOOP v_candidate := TREAT(v_candidates.get(j) AS JSON_OBJECT_T); IF v_candidate.get_string('gameKey') = v_selected_key THEN v_matched_candidate := v_candidate; EXIT; END IF; END LOOP; END IF; v_mention_result := JSON_OBJECT_T(); v_mention_result.put('mention', v_mention); v_mention_result.put('candidateGames', v_candidates); v_mention_result.put('reason', v_reason); IF v_matched_candidate IS NOT NULL THEN v_mention_result.put('status', 'MATCHED'); v_mention_result.put('reasonCode', 'VECTOR_SCORE_MATCH'); v_mention_result.put('matchedGameKey', v_selected_key); v_target := JSON_OBJECT_T.parse(v_matched_candidate.to_clob); v_target.put('mention', v_mention); v_target.put('status', 'MATCHED'); v_target.put('matchStatus', 'MATCHED'); v_target.put('reasonCode', 'VECTOR_SCORE_MATCH'); v_target.put('reason', v_reason); v_matched_count := v_matched_count + 1; IF v_matched_candidate.get_string('factScopeStatus') = 'ACTIVE_ALIAS' THEN v_target.put('dataStatus', 'AVAILABLE'); v_data_eligible.append(v_target); v_data_eligible_count := v_data_eligible_count + 1; ELSE v_target.put('dataStatus', 'UNAVAILABLE'); v_data_ineligible.append(v_target); v_unresolved.append(v_target); END IF; v_targets.append(v_target); IF NOT v_seen_game_keys.EXISTS(v_selected_key) THEN v_supported.append(v_target); v_seen_game_keys(v_selected_key) := TRUE; END IF; ELSE v_mention_result.put('status', 'UNMATCHED'); v_mention_result.put('reasonCode', 'VECTOR_SCORE_OVER_THRESHOLD'); v_target := JSON_OBJECT_T(); v_target.put('mention', v_mention); v_target.put('status', 'UNMATCHED'); v_target.put('matchStatus', 'UNMATCHED'); v_target.put_null('gameKey'); v_target.put_null('gameId'); v_target.put_null('gamePrefix'); v_target.put_null('gameName'); v_target.put_null('aliases'); v_target.put_null('userMasterObjectName'); v_target.put('objectStatus', 'UNAVAILABLE'); v_target.put('factScopeStatus', 'UNAVAILABLE'); v_target.put('dataStatus', 'UNAVAILABLE'); v_target.put_null('cosineDistance'); v_target.put( 'reasonCode', v_mention_result.get_string('reasonCode') ); v_target.put('reason', v_reason); v_targets.append(v_target); v_unmatched_item := JSON_OBJECT_T(); v_unmatched_item.put('mention', v_mention); v_unmatched_item.put( 'reasonCode', v_mention_result.get_string('reasonCode') ); v_unmatched_item.put('reason', v_reason); v_unmatched.append(v_unmatched_item); v_unresolved.append(v_target); v_unmatched_count := v_unmatched_count + 1; END IF; v_mention_results.append(v_mention_result); END LOOP; END IF; IF v_target_type = 'NONE' THEN v_plan_status := 'NO_TARGET'; ELSIF v_matched_count = 0 THEN v_plan_status := 'UNMATCHED'; ELSIF v_data_eligible_count = 0 THEN v_plan_status := 'UNAVAILABLE'; ELSIF v_unmatched_count > 0 THEN v_plan_status := 'PARTIAL'; ELSE v_plan_status := 'SUPPORTED'; END IF; -- This is the sole Select AI handoff contract. It is deliberately generic: -- game values and physical objects come only from the database lookup above. v_reference_summary.put('targetType', v_target_type); v_reference_summary.put('status', v_plan_status); v_reference_summary.put('gameTargets', v_supported); v_select_ai_reference := '[GAME QUERY REFERENCE]' || CHR(10) || CHR(10) || v_reference_summary.to_clob() || CHR(10) || CHR(10) || 'Field meanings:' || CHR(10) || CHR(10) || '* targetType:' || CHR(10) || ' * NONE: No game was resolved.' || CHR(10) || ' * SINGLE: Exactly one game was resolved.' || CHR(10) || ' * MULTI: Multiple specific games were resolved.' || CHR(10) || ' * ALL: The query applies to all supported games.' || CHR(10) || '* status: The result of game-target resolution.' || CHR(10) || '* gameTargets: The exact games resolved by the DB lookup. Each item may include:' || CHR(10) || ' * gameKey: Canonical game identifier.' || CHR(10) || ' * gamePrefix: Prefix used for game-scoped objects.' || CHR(10) || ' * userMasterObjectName: Resolved user-master object for that game.' || CHR(10) || CHR(10) || 'Object-selection guidance:' || CHR(10) || CHR(10) || '* Use only the targets listed in gameTargets.' || CHR(10) || '* For NONE, use a game-neutral common object when it directly answers the question.' || CHR(10) || '* If no suitable common object exists, state that a game name is required.' || CHR(10) || '* Do not infer an unlisted game or game-scoped object.'; v_result.put('contractVersion', '2.0'); v_result.put('targetType', v_target_type); v_result.put('scopeType', v_target_type); v_result.put('selectAiReference', v_select_ai_reference); v_result.put('extractScopeHint', v_scope_type); v_result.put('status', v_plan_status); -- `gameTargets` is the public downstream contract. The detailed `targets` -- collection remains diagnostic evidence only for the MCP response. v_result.put('gameTargets', v_supported); v_result.put('targets', v_targets); v_result.put('matchedGames', v_supported); v_result.put('mentionResults', v_mention_results); v_result.put('supportedGames', v_supported); v_result.put('dataEligibleTargets', v_data_eligible); v_result.put('unresolvedTargets', v_unresolved); v_result.put('dataIneligibleTargets', v_data_ineligible); v_result.put('unmatchedGames', v_unmatched); -- Dynamic ReAct work items. Every game identity and availability state comes -- from the catalog lookup above; clients must not infer their own targets. FOR i IN 0 .. v_targets.get_size - 1 LOOP v_target := TREAT(v_targets.get(i) AS JSON_OBJECT_T); IF v_target.get_string('gameKey') IS NOT NULL THEN v_execution_task := JSON_OBJECT_T(); v_execution_task.put( 'action', CASE WHEN v_target.get_string('dataStatus') = 'AVAILABLE' AND v_target.get_string('userMasterObjectName') IS NOT NULL THEN 'QUERY' ELSE 'REPORT_UNAVAILABLE' END ); v_execution_task.put('scopeGameKey', v_target.get_string('gameKey')); v_execution_task.put('target', v_target); IF v_execution_task.get_string('action') = 'QUERY' THEN v_excluded_mentions := JSON_ARRAY_T(); FOR j IN 0 .. v_targets.get_size - 1 LOOP v_candidate := TREAT(v_targets.get(j) AS JSON_OBJECT_T); IF v_candidate.get_string('gameKey') <> v_target.get_string('gameKey') AND v_candidate.get_string('mention') IS NOT NULL THEN v_excluded_mentions.append(v_candidate.get_string('mention')); END IF; END LOOP; -- Preserve the original question verbatim. The target-specific -- queryPlan below is the separate, authoritative scope contract. v_task_question := p_question; v_task_reference := JSON_OBJECT_T(); v_task_targets := JSON_ARRAY_T(); v_task_targets.append(v_target); v_task_reference.put('targetType', 'SINGLE'); v_task_reference.put('status', 'SUPPORTED'); v_task_reference.put('gameTargets', v_task_targets); v_task_plan := JSON_OBJECT_T(); v_task_plan.put('contractVersion', '2.0'); v_task_plan.put('targetType', 'SINGLE'); v_task_plan.put('status', 'SUPPORTED'); v_task_plan.put('gameTargets', v_task_targets); v_task_plan.put('selectAiReference', '[GAME QUERY REFERENCE]' || CHR(10) || CHR(10) || v_task_reference.to_clob()); v_worker_arguments := JSON_OBJECT_T(); v_worker_arguments.put('prompt', v_task_question); v_worker_arguments.put('scopeGameKey', v_target.get_string('gameKey')); v_worker_arguments.put('queryPlan', v_task_plan); v_fewshot_arguments := JSON_OBJECT_T(); v_fewshot_arguments.put('question', v_task_question); v_fewshot_arguments.put('topK', 3); v_execution_task.put('workerTool', 'oracle.select_ai.smilegate_fewshot_nl2sql'); v_execution_task.put('workerArguments', v_worker_arguments); v_execution_task.put('fewShotArguments', v_fewshot_arguments); END IF; v_execution_tasks.append(v_execution_task); END IF; END LOOP; v_result.put('executionTasks', v_execution_tasks); v_result.put('nextAction', 'CALL_FEWSHOT'); CASE v_target_type WHEN 'NONE' THEN v_result.put('executionMode', 'UNSCOPED'); WHEN 'SINGLE' THEN v_result.put('executionMode', 'SINGLE'); WHEN 'MULTI' THEN v_result.put('executionMode', 'COMBINED'); WHEN 'ALL' THEN v_result.put('executionMode', 'ALL'); END CASE; RETURN v_result.to_clob; END; /