inject game catalog context into few-shot prompt

This commit is contained in:
devmrko
2026-07-27 13:58:37 +09:00
parent 5d4af888f3
commit caa2df3e5d

View File

@@ -49,6 +49,7 @@ public class SelectAiService {
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final QaVectorService qaVectorService; private final QaVectorService qaVectorService;
private final GameScopeService gameScopeService; private final GameScopeService gameScopeService;
private final GameCatalogVectorService gameCatalogVectorService;
public SelectAiService( public SelectAiService(
BackofficeProperties properties, BackofficeProperties properties,
@@ -56,7 +57,8 @@ public class SelectAiService {
Clock clock, Clock clock,
ObjectMapper objectMapper, ObjectMapper objectMapper,
QaVectorService qaVectorService, QaVectorService qaVectorService,
GameScopeService gameScopeService GameScopeService gameScopeService,
GameCatalogVectorService gameCatalogVectorService
) { ) {
this.properties = properties; this.properties = properties;
this.bearerTokenService = bearerTokenService; this.bearerTokenService = bearerTokenService;
@@ -64,6 +66,7 @@ public class SelectAiService {
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.qaVectorService = qaVectorService; this.qaVectorService = qaVectorService;
this.gameScopeService = gameScopeService; this.gameScopeService = gameScopeService;
this.gameCatalogVectorService = gameCatalogVectorService;
} }
public JsonNode generateAndExecute(String bearerToken, String prompt) { public JsonNode generateAndExecute(String bearerToken, String prompt) {
@@ -80,7 +83,9 @@ public class SelectAiService {
BackofficeProperties.SelectAi selectAi = requiredSelectAi(); BackofficeProperties.SelectAi selectAi = requiredSelectAi();
ResolvedExecutionScope scope = resolveExecutionScope(bearerToken, normalizedPrompt, scopeGameKey); ResolvedExecutionScope scope = resolveExecutionScope(bearerToken, normalizedPrompt, scopeGameKey);
EnrichedPrompt enrichedPrompt = enrichWithFewShot(bearerToken, selectAi, scope.prompt()); GameContext gameContext = resolveGameContext(bearerToken, normalizedPrompt);
EnrichedPrompt enrichedPrompt = enrichWithFewShot(
bearerToken, selectAi, gameContext.prompt(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);
@@ -88,6 +93,9 @@ 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);
response.put("gameScopeType", gameContext.scopeType());
response.put("gameScopeStatus", gameContext.status());
response.put("gameCandidateCount", gameContext.candidates().size());
if (scope.gameKey() != null) { if (scope.gameKey() != null) {
response.put("scopeGameKey", scope.gameKey()); response.put("scopeGameKey", scope.gameKey());
response.put("scopeDisplayName", scope.displayName()); response.put("scopeDisplayName", scope.displayName());
@@ -107,6 +115,18 @@ public class SelectAiService {
return response; return response;
} }
private GameContext resolveGameContext(String bearerToken, String question) {
try {
List<GameCatalogVectorService.GameCandidate> candidates =
gameCatalogVectorService.search(bearerToken, question, 5);
String status = candidates.isEmpty() ? "NO_MATCH" : "RESOLVED";
String scopeType = candidates.isEmpty() ? "UNKNOWN" : "SINGLE_GAME";
return new GameContext(scopeType, status, candidates);
} catch (Exception ignored) {
return new GameContext("UNKNOWN", "UNAVAILABLE", List.of());
}
}
private ResolvedExecutionScope resolveExecutionScope( private ResolvedExecutionScope resolveExecutionScope(
String bearerToken, String originalPrompt, String scopeGameKey String bearerToken, String originalPrompt, String scopeGameKey
) { ) {
@@ -397,4 +417,27 @@ public class SelectAiService {
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) {} private record ResolvedExecutionScope(String prompt, String gameKey, String displayName) {}
private record GameContext(
String scopeType, String status, List<GameCatalogVectorService.GameCandidate> candidates) {
String prompt(String original) {
StringBuilder context = new StringBuilder();
context.append("[GAME CATALOG MATCH]\n")
.append("scope_type: ").append(scopeType).append('\n')
.append("status: ").append(status).append('\n');
for (GameCatalogVectorService.GameCandidate candidate : candidates) {
context.append("game_key: ").append(candidate.gameKey()).append('\n')
.append("game_id: ").append(candidate.gameId()).append('\n')
.append("game_prefix: ").append(candidate.gamePrefix()).append('\n')
.append("game_name: ").append(candidate.gameName()).append('\n')
.append("matched_aliases: ").append(candidate.aliases()).append('\n')
.append("cosine_distance: ").append(candidate.similarity()).append('\n');
}
context.append("[GAME SCOPE POLICY]\n")
.append("Use only DB-approved game scope when status is RESOLVED. ")
.append("When status is NO_MATCH or UNKNOWN, do not infer a game-specific object.\n\n")
.append(original);
return context.toString();
}
}
} }