From 987ea44f3288258a819389943ff945d392664197 Mon Sep 17 00:00:00 2001 From: devmrko Date: Tue, 28 Jul 2026 12:49:46 +0900 Subject: [PATCH] refs #739: guide no-target few-shot SQL generation --- docs/design/739-game-target-contract/README.md | 9 ++++++++- .../vpdbackoffice/service/SelectAiService.java | 7 ++++++- .../vpdbackoffice/service/SelectAiFewShotPromptTest.java | 5 +++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/design/739-game-target-contract/README.md b/docs/design/739-game-target-contract/README.md index 3013c3b..c99136a 100644 --- a/docs/design/739-game-target-contract/README.md +++ b/docs/design/739-game-target-contract/README.md @@ -136,7 +136,10 @@ Few-shot 원문 SQL은 실행 이력 그대로 검색하지 않는다. 특히 물리 테이블명은 Few-shot의 의미 규칙으로 저장하지 않는다. `SQL_TEMPLATE`은 `` 같은 논리 placeholder를 사용하며, 실제 객체는 오직 현재 `queryPlan.targets[*].userMasterObjectName`에서만 해석한다. `NO_TARGET`과 -`OBJECT_UNAVAILABLE` 예제는 SQL 패턴이 아니라 범위 경계 안내로 프롬프트에 포함한다. +`OBJECT_UNAVAILABLE` 예제는 범위 경계 안내와 검증된 빈 결과 SQL 템플릿을 함께 +프롬프트에 포함한다. 이 템플릿은 애플리케이션에서 직접 실행하거나 답변으로 +반환하지 않으며, Select AI가 현재 질문의 범위를 해석해 SQL을 생성할 때만 Few-shot +근거로 사용한다. 기존 예제는 삭제하지 않는다. 실행 가능성, 게임 범위 일치, 물리 객체 의존성을 전수 검사해 `RETIRED`로 분리하고, 검증된 정규화 예제만 `APPROVED`로 전환한다. @@ -165,3 +168,7 @@ Few-shot 원문 SQL은 실행 이력 그대로 검색하지 않는다. 특히 - 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`로 기록됐다. 직접 정답 실행 분기는 두지 않는다. diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java index d6d5fff..00e5bbf 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/SelectAiService.java @@ -447,7 +447,12 @@ public class SelectAiService { if ("NO_TARGET".equals(example.referenceKind()) || "OBJECT_UNAVAILABLE".equals(example.referenceKind())) { String boundary = truncate(example.answer(), MAX_FEW_SHOT_SQL_CHARS); - return boundary.isBlank() ? "" : prefix + "Boundary outcome:\n" + boundary + "\n\n"; + String answerSql = truncate(example.answerSql(), MAX_FEW_SHOT_SQL_CHARS); + if (boundary.isBlank() || answerSql.isBlank()) { + return ""; + } + return prefix + "Boundary rule:\n" + boundary + + "\nVerified boundary SQL template:\n" + answerSql + "\n\n"; } String answerSql = truncate(example.answerSql(), MAX_FEW_SHOT_SQL_CHARS); return answerSql.isBlank() ? "" : prefix + "Verified SQL template:\n" + answerSql + "\n\n"; diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiFewShotPromptTest.java b/src/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiFewShotPromptTest.java index a6593f8..985c32e 100644 --- a/src/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiFewShotPromptTest.java +++ b/src/test/java/com/cloudhandson/vpdbackoffice/service/SelectAiFewShotPromptTest.java @@ -55,8 +55,9 @@ class SelectAiFewShotPromptTest { ); assertThat(prompt) - .contains("Boundary outcome") + .contains("Boundary rule") .contains("Do not select a game-scoped object") - .doesNotContain("SELECT CAST(NULL AS NUMBER)"); + .contains("SELECT CAST(NULL AS NUMBER)"); } + }