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 ObjectMapper objectMapper;
|
||||
private final QaVectorService qaVectorService;
|
||||
private final GameScopeService gameScopeService;
|
||||
|
||||
public SelectAiService(
|
||||
BackofficeProperties properties,
|
||||
BearerTokenService bearerTokenService,
|
||||
Clock clock,
|
||||
ObjectMapper objectMapper,
|
||||
QaVectorService qaVectorService
|
||||
QaVectorService qaVectorService,
|
||||
GameScopeService gameScopeService
|
||||
) {
|
||||
this.properties = properties;
|
||||
this.bearerTokenService = bearerTokenService;
|
||||
this.clock = clock;
|
||||
this.objectMapper = objectMapper;
|
||||
this.qaVectorService = qaVectorService;
|
||||
this.gameScopeService = gameScopeService;
|
||||
}
|
||||
|
||||
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);
|
||||
String normalizedPrompt = requiredPrompt(prompt);
|
||||
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 normalizedSql = validateReadOnlySql(generatedSql);
|
||||
QueryExecution execution = executeReadOnly(selectAi, normalizedSql);
|
||||
@@ -76,6 +88,11 @@ public class SelectAiService {
|
||||
response.put("status", "SHOWSQL_AND_EXECUTED");
|
||||
response.put("profile", selectAi.profile());
|
||||
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("fewShotExampleCount", enrichedPrompt.exampleCount());
|
||||
addFewShotExamples(response, enrichedPrompt.examples());
|
||||
@@ -90,6 +107,33 @@ public class SelectAiService {
|
||||
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) {
|
||||
ArrayNode items = response.putArray("fewShotExamples");
|
||||
for (QaVectorService.VectorExample example : examples) {
|
||||
@@ -206,13 +250,19 @@ public class SelectAiService {
|
||||
}
|
||||
|
||||
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) {
|
||||
StringBuilder enriched = new StringBuilder(POLICY_PREFIX
|
||||
+ "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");
|
||||
int included = 0;
|
||||
for (QaVectorService.VectorExample example : examples) {
|
||||
@@ -344,4 +394,6 @@ public class SelectAiService {
|
||||
|
||||
private record EnrichedPrompt(
|
||||
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