14 KiB
Smilegate 게임 대상 계약 통합 설계
1. 배경
현재 game_query_plan은 게임을 찾지 못하면 STOP_NO_MATCH, 여러 게임이면
FAN_OUT을 반환한다. Portal의 ReAct 모델이 이 지시를 종료 조건으로 해석하거나
일부 게임만 다음 도구로 전달하면서 다음 문제가 발생했다.
- 게임명이 없는 전체 매출 질의가 SQL 생성 전에 중단된다.
- 여러 게임 중 일부만 매칭되면 매칭된 게임의 조회도 생략될 수 있다.
- 계획 결과를 전달해도 NL2SQL worker가 질문 전체를 다시 단일 게임 벡터검색하여 범위를 축소할 수 있다.
- 게임별 사용자 마스터 물리 객체를 모델이 추측해야 한다.
2. 목표
질문의 게임 대상을 NONE, SINGLE, MULTI, ALL 네 가지로 통일하고,
모든 유형에서 원 질문과 전체 계획 결과를 smilegate_fewshot_nl2sql에 한 번
전달한다. 계획 단계는 조회 중단이나 SQL 조립을 결정하지 않는다.
3. 계약
game_query_plan은 ReAct가 판단에 사용할 고정 JSON 계약을 반환한다. 자연어
설명, 후보 검색 원문, 기존 호환 필드는 진단 영역으로 분리하며 실행 판단은 아래
권위 필드만 사용한다.
{
"contractVersion": "1.0",
"scopeType": "MULTI",
"targets": [
{
"mention": "사용자가 언급한 게임명",
"matchStatus": "MATCHED",
"dataStatus": "AVAILABLE",
"gameKey": "카탈로그 키 또는 null",
"gameId": "카탈로그 식별자 또는 null",
"userMasterObjectName": "승인 객체 또는 null"
}
],
"dataEligibleTargets": [],
"unresolvedTargets": []
}
고정 enum은 다음과 같다.
scopeType:NONE,SINGLE,MULTI,ALLmatchStatus:NOT_APPLICABLE,MATCHED,UNMATCHEDdataStatus:NOT_APPLICABLE,AVAILABLE,UNAVAILABLE
scopeType은 질문에 언급된 범위만 나타낸다. 실행 가능성이나 SQL 조립 방식을
암시하지 않는다. targets는 언급 순서를 유지하고, dataEligibleTargets는 현재
질의에 사용할 수 있는 카탈로그 대상만, unresolvedTargets는 미매칭 또는 데이터
객체가 없는 대상을 보존한다. ReAct는 이 계약 전체를 관찰한 뒤 공통 팩트 단일
호출, 대상별 호출, 또는 빈 결과 응답을 선택한다.
기존 API 호환 기간에는 아래 필드도 반환할 수 있지만 ReAct의 실행 판단에 사용하지
않는다: targetType, status, matchedGames, supportedGames,
mentionResults, dataIneligibleTargets, unmatchedGames, nextAction,
executionMode.
기존 필드의 의미는 다음과 같다.
| 필드 | 내용 |
|---|---|
targetType |
호환용 scopeType 별칭 |
status |
호환용 요약 상태. ReAct 실행 분기에 사용하지 않음 |
targets |
언급 순서를 보존한 게임 대상 배열 |
nextAction |
호환용 안내. 실행을 강제하지 않음 |
executionMode |
호환용 안내. 실행을 강제하지 않음 |
각 targets 항목은 다음 값을 가진다.
| 필드 | 내용 |
|---|---|
mention |
질문에서 추출한 게임명. NONE은 null |
status |
호환용 대상 상태 |
matchStatus |
NOT_APPLICABLE, MATCHED, UNMATCHED |
dataStatus |
NOT_APPLICABLE, AVAILABLE, UNAVAILABLE |
gameKey, gameId |
DB 카탈로그가 반환한 게임 식별자 |
gamePrefix |
DB 카탈로그가 반환한 prefix |
gameName, aliases |
표시명과 별칭 |
userMasterObjectName |
현재 Select AI 승인 객체 중 해당 게임의 사용자 마스터 물리 객체. 없으면 null |
cosineDistance |
후보 검색 거리. 적용되지 않으면 null |
reasonCode, reason |
매칭 또는 미매칭 근거 |
유형별 처리 방식은 다음과 같다.
NONE: 게임 대상 항목 하나를 모든 게임 필드가null인 상태로 전달한다. 공통 거래·기준 객체 질의는 계속하되 prefix 전용 객체를 임의 선택하지 않는다.SINGLE: 질문에서 언급된 대상 한 건을 전달한다. 미매칭이면 게임 필드가null인 대상도 그대로 다음 도구에 전달한다.MULTI: 언급된 대상 모두를 전달한다. 매칭·미매칭을 함께 보존한다.ALL: 게임 카탈로그의 활성 게임을 모두 반환한다. 사용자 마스터 물리 객체가 없는 게임도null로 보존한다.
4. DB 설계
SG_GAME_CATALOG에 USER_MASTER_OBJECT_NAME을 추가한다.
- 값은
COMN_GAME_ALIAS_BAS/운영 게임 registry의GAME_PREFIX와 현재USER_CLOUD_AI_PROFILE_ATTRIBUTES.object_list,USER_OBJECTS를 조합해 계산한다. - 공통 정책이나 애플리케이션 코드에는 특정 게임명, prefix, 테이블명을 넣지 않는다.
- 운영 registry에만 존재하는 게임도 카탈로그에 동기화하고 임베딩을 생성한다.
- 게임별 물리 객체가 없으면 컬럼은
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_QUERY_PLAN은 추출된 mention마다 벡터 점수 기반 후보 결과를 targets와
mentionResults.candidateGames로 만들며, ALL은 벡터 선택 없이 전체 카탈로그를
사용한다.
5. MCP와 NL2SQL 설계
game_query_plan 도구 설명은 “항상 먼저 호출하고 결과를 원 질문과 함께 다음
도구에 전달”하도록 바꾼다. smilegate_fewshot_nl2sql의 입력은 다음과 같다.
prompt: 사용자가 입력한 원 질문queryPlan: 바로 앞game_query_plan의 전체 JSONscopeGameKey: 하위 호환용 선택 값. 새 흐름에서는 사용하지 않는다.
NL2SQL worker는 전달된 queryPlan을 권위 있는 게임 범위로 사용한다.
계획이 있으면 질문 전체를 다시 게임 카탈로그 벡터검색하지 않는다.
- 공통 객체에 게임 식별 컬럼이 있으면 질문 의미에 따라
IN과GROUP BY를 사용할 수 있다. - 대상별 물리 객체가 다르면 제공된 객체만 사용해
UNION ALL할 수 있다. - 물리 객체가
null인 대상을 임의 객체로 대체하지 않는다. NONE에서 생성 SQL이 prefix 전용 객체를 참조하면 실행 전에 차단한다.- 최종 SQL은 기존과 같이 단일
SELECT/WITH, read-only transaction, 최대 행수와 timeout 제한을 적용한다.
6. Portal ReAct 설계
Portal은 도구 호출 순서를 Java/Python 조건문으로 강제하지 않는다. 외부 환경변수의 system prompt와 MCP 도구 설명으로 다음 절차를 안내한다.
game_query_plan을 호출한다.targetType과 상관없이 원 질문, 전체queryPlan으로smilegate_fewshot_nl2sql을 한 번 호출한다.- SQL, 실행 결과, 매칭·미매칭 대상을 함께 설명한다.
7. 검증
운영 MCP에서 다음 네 질문군을 실제 호출한다.
| 유형 | 검증 내용 |
|---|---|
NONE |
게임명이 없는 전체 매출 질의가 공통 테이블로 실행되고 게임 필터가 임의 추가되지 않는다. |
SINGLE |
한 게임의 AU 질의가 해당 게임 식별자/물리 객체로 실행된다. |
MULTI |
지원·미지원 게임을 함께 질문해 지원 결과와 미지원 상태가 한 답변에 보존된다. |
ALL |
전체 게임 질의가 전체 카탈로그 대상을 받아 공통 객체 그룹 또는 게임별 객체 결합 SQL로 실행된다. |
추가 검증:
- Maven 테스트
- Portal Python 테스트
- MCP
tools/listschema/description 확인 - 생성 SQL에서
NONE의 prefix 전용 객체 차단 확인 - 운영 서비스 health와 Portal ReAct 실제 응답 확인
8. Few-shot 참조 거버넌스
Few-shot 원문 SQL은 실행 이력 그대로 검색하지 않는다. 특히 특정 게임의 물리 객체명이 포함된 예제는 다른 게임 또는 게임 미지정 질의의 벡터 후보가 될 수 있으므로, 예제는 검증·정규화·승인 단계를 거쳐야 한다.
SG_QA_VECTOR_EXAMPLE에는 다음 참조 메타데이터를 둔다.
| 필드 | 내용 |
|---|---|
REFERENCE_STATUS |
DRAFT, APPROVED, RETIRED. 검색은 APPROVED만 사용한다. |
REFERENCE_KIND |
SQL_TEMPLATE, NO_TARGET, OBJECT_UNAVAILABLE, METADATA_POLICY |
TARGET_TYPE |
NONE, SINGLE, MULTI, ALL, ANY. 현재 game query plan과 일치하는 예제만 벡터 순위에 포함한다. |
OBJECT_ROLE |
물리 테이블명이 아닌 GAME_USER_MASTER 등 논리 객체 역할 |
INSPECTION_STATUS, INSPECTION_NOTE |
전수 실행·정책 검토 결과와 사유 |
VERIFIED_AT, VERIFIED_BY |
승인 감사 이력 |
물리 테이블명은 Few-shot의 의미 규칙으로 저장하지 않는다. SQL_TEMPLATE은
<RESOLVED_GAME_USER_MASTER> 같은 논리 placeholder를 사용하며, 실제 객체는 오직
현재 queryPlan.targets[*].userMasterObjectName에서만 해석한다. NO_TARGET과
OBJECT_UNAVAILABLE 예제는 범위 경계 안내와 검증된 빈 결과 SQL 템플릿을 함께
프롬프트에 포함한다. 이 템플릿은 애플리케이션에서 직접 실행하거나 답변으로
반환하지 않으며, Select AI가 현재 질문의 범위를 해석해 SQL을 생성할 때만 Few-shot
근거로 사용한다.
NONE은 게임이 선택되지 않았다는 범위 정보이며 질의 불가 상태가 아니다. 각 경계
예제는 OBJECT_ROLE로 적용 대상을 밝힌다. 예를 들어 게임별 사용자 마스터 경계는
공통 판매·환불 같은 공통 객체 질의에 적용하지 않는다. 공통 객체로 답할 수 있는
질문은 게임 중립 SQL로 계속 실행하고, 게임별 객체가 본질적으로 필요한 질문만 빈
결과 또는 게임 식별 요청으로 처리한다.
UNMATCHED와 OBJECT_STATUS=UNAVAILABLE는 NONE과 별도 계약이다. 요청한
게임 전용 논리 객체를 현재 카탈로그에서 확인할 수 없다는 뜻이므로 다른 게임의
객체로 대체하지 않는다. 해당 객체가 반드시 필요한 연산이면 실행 가능한 읽기 전용
0행 결과로 종료한다. 이때 NULL을 담은 합성 1행은 빈 결과로 취급하지 않는다.
이 규칙은 특정 게임·prefix·물리 객체에 의존하지 않으며, Java 분기가 아니라 Select
AI 프로파일의 additional_instructions로 관리한다. 애플리케이션은 계획 전달과
read-only·객체 범위 검증만 담당한다.
공통 거래처럼 게임 식별 컬럼이 있는 객체는 게임 식별자를 SQL 리터럴로 직접 넣지
않는다. 활성 게임 별칭 카탈로그를 통해 허용 식별자를 구하는 서브쿼리 또는 EXISTS
조건을 사용하며, 카탈로그에는 있으나 해당 공통 객체의 데이터가 없는 경우는 다른
게임으로 대체하지 않고 빈 결과 또는 집계 NULL을 그대로 보존한다.
기존 예제는 삭제하지 않는다. 실행 가능성, 게임 범위 일치, 물리 객체 의존성을 전수
검사해 RETIRED로 분리하고, 검증된 정규화 예제만 APPROVED로 전환한다.
고객 질답 47건 후보화·승인 규칙
SG_AI_QA_QUESTION의 고객 Excel 47건은 SOURCE_TYPE=CUSTOMER_QA_BENCHMARK,
SOURCE_CASE_ID로 벡터 예제에 한 번씩 적재한다. 초기 상태는 모두 DRAFT이고,
질문·기대 포인트·기준 SQL을 embedding 문서로 사용한다. 이 상태의 원문은 운영 검색
결과에 절대 포함되지 않는다.
SG_QA_VECTOR_APPROVE_VERIFIED_BENCHMARK는 최신 Portal ReAct 실행이 다음을 모두
만족한 후보만 자동으로 APPROVED로 전환한다.
- 고객 지원 범위가
SUPPORTED일 것 - 최신 실행이
COMPLETED이며 고객 기준 판정이PASS일 것 - 실행 가능한 읽기 전용 SQL이 있을 것
승인 시 최신 실행 SQL과 응답으로 embedding을 재생성하고, 실행 이력의 query plan에서
확인된 TARGET_TYPE을 함께 보관한다. WARN·FAIL·미지원·SQL 미생성 후보는
DRAFT / REVIEW로 남긴다. 따라서 개선 전 SQL이 Few-shot 근거로 노출되지 않으며,
각 검색 결과는 SOURCE_CASE_ID로 고객 기준 항목까지 추적할 수 있다.
9. 변경 이력
- Redmine:
#739 - Backoffice branch:
smilegate - Portal branch:
main - 2026-07-28:
STD-03의NO_TARGET예제에 검증된 빈 결과 SQL 템플릿을 Few-shot 근거로 노출했다. 운영 Portal ReAct에서game_query_plan → fewshot_nl2sql경로로 생성·실행한 SQL은SELECT CAST(NULL AS NUMBER) ... FROM DUAL WHERE 1 = 0이었고, 답변 이력422는PASS로 기록됐다. 직접 정답 실행 분기는 두지 않는다. - 2026-07-28:
NONE을 질의 차단으로 해석하지 않도록 경계 예제에 논리 객체 역할을 부여하고, 공통 객체 질의에는 경계 SQL을 일반화하지 않는 프롬프트 규칙을 추가했다. - 2026-07-29: ReAct가 자유 설명문이나 중복 요약 필드를 해석하지 않도록, 게임 범위
계획의 권위 JSON 계약을
contractVersion,scopeType,targets[*].matchStatus,targets[*].dataStatus,dataEligibleTargets,unresolvedTargets로 정리했다. 이 계약은 게임명·prefix·물리 객체를 애플리케이션에 고정하지 않으며, 다음 도구 호출 횟수와 방식은 ReAct가 현재 질문과 계획 결과로 판단한다.