generalize game scope guidance through metadata few-shot
This commit is contained in:
@@ -48,27 +48,39 @@ public class SelectAiService {
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final QaVectorService qaVectorService;
|
private final QaVectorService qaVectorService;
|
||||||
|
private final GameScopeService gameScopeService;
|
||||||
|
|
||||||
public SelectAiService(
|
public SelectAiService(
|
||||||
BackofficeProperties properties,
|
BackofficeProperties properties,
|
||||||
BearerTokenService bearerTokenService,
|
BearerTokenService bearerTokenService,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
QaVectorService qaVectorService
|
QaVectorService qaVectorService,
|
||||||
|
GameScopeService gameScopeService
|
||||||
) {
|
) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.bearerTokenService = bearerTokenService;
|
this.bearerTokenService = bearerTokenService;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.qaVectorService = qaVectorService;
|
this.qaVectorService = qaVectorService;
|
||||||
|
this.gameScopeService = gameScopeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonNode generateAndExecute(String bearerToken, String prompt) {
|
public JsonNode generateAndExecute(String bearerToken, String prompt) {
|
||||||
|
return generateAndExecute(bearerToken, prompt, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes one DB-resolved game scope. The optional key is revalidated for
|
||||||
|
* the original question; it is never a caller-provided table or prefix.
|
||||||
|
*/
|
||||||
|
public JsonNode generateAndExecute(String bearerToken, String prompt, String scopeGameKey) {
|
||||||
requireActiveToken(bearerToken);
|
requireActiveToken(bearerToken);
|
||||||
String normalizedPrompt = requiredPrompt(prompt);
|
String normalizedPrompt = requiredPrompt(prompt);
|
||||||
BackofficeProperties.SelectAi selectAi = requiredSelectAi();
|
BackofficeProperties.SelectAi selectAi = requiredSelectAi();
|
||||||
|
ResolvedExecutionScope scope = resolveExecutionScope(bearerToken, normalizedPrompt, scopeGameKey);
|
||||||
|
|
||||||
EnrichedPrompt enrichedPrompt = enrichWithFewShot(bearerToken, selectAi, normalizedPrompt);
|
EnrichedPrompt enrichedPrompt = enrichWithFewShot(bearerToken, selectAi, scope.prompt());
|
||||||
String generatedSql = generate(selectAi, enrichedPrompt.prompt(), "showsql");
|
String generatedSql = generate(selectAi, enrichedPrompt.prompt(), "showsql");
|
||||||
String normalizedSql = validateReadOnlySql(generatedSql);
|
String normalizedSql = validateReadOnlySql(generatedSql);
|
||||||
QueryExecution execution = executeReadOnly(selectAi, normalizedSql);
|
QueryExecution execution = executeReadOnly(selectAi, normalizedSql);
|
||||||
@@ -76,6 +88,11 @@ public class SelectAiService {
|
|||||||
response.put("status", "SHOWSQL_AND_EXECUTED");
|
response.put("status", "SHOWSQL_AND_EXECUTED");
|
||||||
response.put("profile", selectAi.profile());
|
response.put("profile", selectAi.profile());
|
||||||
response.put("originalPrompt", normalizedPrompt);
|
response.put("originalPrompt", normalizedPrompt);
|
||||||
|
if (scope.gameKey() != null) {
|
||||||
|
response.put("scopeGameKey", scope.gameKey());
|
||||||
|
response.put("scopeDisplayName", scope.displayName());
|
||||||
|
response.put("scopeStatus", "DB_REVALIDATED");
|
||||||
|
}
|
||||||
response.put("fewShotStatus", enrichedPrompt.status());
|
response.put("fewShotStatus", enrichedPrompt.status());
|
||||||
response.put("fewShotExampleCount", enrichedPrompt.exampleCount());
|
response.put("fewShotExampleCount", enrichedPrompt.exampleCount());
|
||||||
addFewShotExamples(response, enrichedPrompt.examples());
|
addFewShotExamples(response, enrichedPrompt.examples());
|
||||||
@@ -90,6 +107,33 @@ public class SelectAiService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ResolvedExecutionScope resolveExecutionScope(
|
||||||
|
String bearerToken, String originalPrompt, String scopeGameKey
|
||||||
|
) {
|
||||||
|
String requestedKey = scopeGameKey == null ? "" : scopeGameKey.trim();
|
||||||
|
if (requestedKey.isEmpty()) {
|
||||||
|
return new ResolvedExecutionScope(originalPrompt, null, null);
|
||||||
|
}
|
||||||
|
if (gameScopeService == null) {
|
||||||
|
throw new AppException("게임 범위 검증 서비스를 사용할 수 없습니다.");
|
||||||
|
}
|
||||||
|
GameScopeService.GameScope scope = gameScopeService.resolve(bearerToken, originalPrompt).scopes().stream()
|
||||||
|
.filter(item -> requestedKey.equals(item.gameKey()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new AppException("요청한 게임 범위가 DB 조회 결과에 없습니다."));
|
||||||
|
if (!"SUPPORTED".equals(scope.status())) {
|
||||||
|
throw new AppException("DB 게임 범위가 조회 실행을 허용하지 않습니다: " + scope.reasonCode());
|
||||||
|
}
|
||||||
|
String scopedPrompt = "Use only the DB-resolved game scope below. Do not generate SQL for any other "
|
||||||
|
+ "game mentioned in the original question. The scope was validated through current game alias "
|
||||||
|
+ "metadata and approved object availability.\n"
|
||||||
|
+ "Resolved game key: " + scope.gameKey() + "\n"
|
||||||
|
+ "Resolved display name: " + scope.displayName() + "\n"
|
||||||
|
+ "Matched alias: " + scope.matchedAlias() + "\n\n"
|
||||||
|
+ "Original user question:\n" + originalPrompt;
|
||||||
|
return new ResolvedExecutionScope(scopedPrompt, scope.gameKey(), scope.displayName());
|
||||||
|
}
|
||||||
|
|
||||||
private void addFewShotExamples(ObjectNode response, List<QaVectorService.VectorExample> examples) {
|
private void addFewShotExamples(ObjectNode response, List<QaVectorService.VectorExample> examples) {
|
||||||
ArrayNode items = response.putArray("fewShotExamples");
|
ArrayNode items = response.putArray("fewShotExamples");
|
||||||
for (QaVectorService.VectorExample example : examples) {
|
for (QaVectorService.VectorExample example : examples) {
|
||||||
@@ -206,13 +250,19 @@ public class SelectAiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String composePolicyPrompt(String prompt) {
|
static String composePolicyPrompt(String prompt) {
|
||||||
return POLICY_PREFIX + "Original user question:\n" + prompt;
|
return POLICY_PREFIX
|
||||||
|
+ "Resolve business terms and game names from the approved game-alias metadata before selecting a "
|
||||||
|
+ "game-scoped object. A generic term such as common user means no particular game. If no game alias "
|
||||||
|
+ "is resolved, do not substitute an arbitrary game-scoped object; ask for the game or return no data.\n"
|
||||||
|
+ "Original user question:\n" + prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String composeFewShotPrompt(String prompt, List<QaVectorService.VectorExample> examples) {
|
static String composeFewShotPrompt(String prompt, List<QaVectorService.VectorExample> examples) {
|
||||||
StringBuilder enriched = new StringBuilder(POLICY_PREFIX
|
StringBuilder enriched = new StringBuilder(POLICY_PREFIX
|
||||||
+ "The verified examples below are guidance only: use only relevant SQL patterns, do not invent "
|
+ "The verified examples below are guidance only: use only relevant SQL patterns, do not invent "
|
||||||
+ "identifiers, and do not override current metadata or game-alias resolution policy.\n\n"
|
+ "identifiers, and do not override current metadata or game-alias resolution policy. "
|
||||||
|
+ "When examples use a game-specific object, reuse that pattern only after the current question "
|
||||||
|
+ "resolves the same game alias; otherwise keep the query unscoped or request clarification.\n\n"
|
||||||
+ "Verified few-shot examples:\n");
|
+ "Verified few-shot examples:\n");
|
||||||
int included = 0;
|
int included = 0;
|
||||||
for (QaVectorService.VectorExample example : examples) {
|
for (QaVectorService.VectorExample example : examples) {
|
||||||
@@ -344,4 +394,6 @@ public class SelectAiService {
|
|||||||
|
|
||||||
private record EnrichedPrompt(
|
private record EnrichedPrompt(
|
||||||
String prompt, String status, int exampleCount, List<QaVectorService.VectorExample> examples) {}
|
String prompt, String status, int exampleCount, List<QaVectorService.VectorExample> examples) {}
|
||||||
|
|
||||||
|
private record ResolvedExecutionScope(String prompt, String gameKey, String displayName) {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user