refs #739: unify Smilegate game target planning
This commit is contained in:
@@ -59,6 +59,8 @@ public class GameCatalogVectorService {
|
||||
JsonNode parsed = objectMapper.readTree(raw);
|
||||
if (!(parsed instanceof ObjectNode plan)
|
||||
|| !plan.hasNonNull("status")
|
||||
|| !plan.hasNonNull("targetType")
|
||||
|| !plan.path("targets").isArray()
|
||||
|| !plan.path("supportedGames").isArray()
|
||||
|| !plan.path("unmatchedGames").isArray()) {
|
||||
throw new AppException("ADB 게임 질의 계획 응답 형식이 올바르지 않습니다.");
|
||||
@@ -97,6 +99,7 @@ public class GameCatalogVectorService {
|
||||
rows.getString("GAME_PREFIX"),
|
||||
rows.getString("GAME_NM"),
|
||||
rows.getString("GAME_ALIAS_NM"),
|
||||
rows.getString("USER_MASTER_OBJECT_NAME"),
|
||||
rows.getDouble("COSINE_DISTANCE")
|
||||
));
|
||||
}
|
||||
@@ -135,6 +138,7 @@ public class GameCatalogVectorService {
|
||||
String gamePrefix,
|
||||
String gameName,
|
||||
String aliases,
|
||||
String userMasterObjectName,
|
||||
double similarity
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -109,6 +110,16 @@ public class GameScopeService {
|
||||
* name is embedded in application code.
|
||||
*/
|
||||
public boolean referencesGameScopedObject(String bearerToken, String sql) {
|
||||
return referencesGameScopedObjectOutsidePrefixes(bearerToken, sql, Set.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when SQL references a DB-declared game-prefix object whose
|
||||
* prefix is not present in the authoritative query plan.
|
||||
*/
|
||||
public boolean referencesGameScopedObjectOutsidePrefixes(
|
||||
String bearerToken, String sql, Set<String> allowedPrefixes
|
||||
) {
|
||||
requireActiveToken(bearerToken);
|
||||
if (!gameScopeProperties.configured() || sql == null || sql.isBlank()) {
|
||||
return false;
|
||||
@@ -124,11 +135,17 @@ public class GameScopeService {
|
||||
statement.setString(1, selectAi.profile());
|
||||
try (ResultSet rows = statement.executeQuery()) {
|
||||
String normalizedSql = sql.toUpperCase(Locale.ROOT);
|
||||
Set<String> normalizedAllowed = allowedPrefixes == null ? Set.of()
|
||||
: allowedPrefixes.stream()
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.map(value -> value.trim().toUpperCase(Locale.ROOT))
|
||||
.collect(java.util.stream.Collectors.toUnmodifiableSet());
|
||||
while (rows.next()) {
|
||||
String prefix = rows.getString("GAME_PREFIX");
|
||||
if (prefix != null && Pattern.compile(
|
||||
"(?<![A-Z0-9_$#])" + Pattern.quote(prefix.toUpperCase(Locale.ROOT))
|
||||
+ "_[A-Z0-9_$#]+(?![A-Z0-9_$#])").matcher(normalizedSql).find()) {
|
||||
+ "_[A-Z0-9_$#]+(?![A-Z0-9_$#])").matcher(normalizedSql).find()
|
||||
&& !normalizedAllowed.contains(prefix.toUpperCase(Locale.ROOT))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,11 +143,13 @@ public class McpSseService {
|
||||
addStringProperty(properties, "prompt", promptDescription(), 4000);
|
||||
if (fewShotNl2SqlToolName().equals(toolView.name())) {
|
||||
addStringProperty(properties, "scopeGameKey",
|
||||
"선택 사항입니다. game_scope_resolve가 반환한 SUPPORTED gameKey만 전달하세요.", 128);
|
||||
"하위 호환용 선택 값입니다. 새 흐름에서는 queryPlan 전체를 전달하고 비워 둡니다.", 128);
|
||||
ObjectNode plan = properties.putObject("queryPlan");
|
||||
plan.put("type", "object");
|
||||
plan.put("description", "앞 단계 game_query_plan의 구조화된 결과입니다.");
|
||||
plan.putObject("additionalProperties").put("type", "object");
|
||||
plan.put("description",
|
||||
"바로 앞 game_query_plan의 전체 결과입니다. targetType이 NONE, SINGLE, MULTI, ALL 중 "
|
||||
+ "어느 값이어도 원 질문과 함께 그대로 전달합니다.");
|
||||
plan.put("additionalProperties", true);
|
||||
}
|
||||
required.add("prompt");
|
||||
}
|
||||
@@ -342,7 +344,12 @@ public class McpSseService {
|
||||
|
||||
private McpToolView fewShotNl2SqlView() {
|
||||
return new McpToolView(
|
||||
fewShotNl2SqlToolName(), fewShotNl2SqlToolDescription(), -1L,
|
||||
fewShotNl2SqlToolName(),
|
||||
fewShotNl2SqlToolDescription()
|
||||
+ " game_query_plan의 targetType이 NONE, SINGLE, MULTI, ALL 중 어느 값이어도 "
|
||||
+ "원 질문과 전체 queryPlan을 한 번 받아 단일 읽기 전용 SQL을 생성·실행합니다. "
|
||||
+ "queryPlan에 없는 게임, prefix, 물리 객체를 추측하지 않습니다.",
|
||||
-1L,
|
||||
fewShotNl2SqlToolLabel(), SELECT_AI_TOOL_PATH);
|
||||
}
|
||||
|
||||
@@ -361,7 +368,10 @@ public class McpSseService {
|
||||
|
||||
private McpToolView gameQueryPlanView() {
|
||||
return new McpToolView(GAME_QUERY_PLAN_TOOL,
|
||||
"질문에서 게임 범위와 실행 모드를 판정합니다. SQL은 실행하지 않습니다.",
|
||||
"항상 먼저 호출해 질문의 게임 대상을 NONE, SINGLE, MULTI, ALL로 판정합니다. "
|
||||
+ "targets에는 DB 카탈로그의 게임 식별자와 승인된 사용자 마스터 물리 객체명이 포함됩니다. "
|
||||
+ "어떤 targetType도 종료 조건이 아닙니다. 원 질문과 이 도구의 전체 결과를 "
|
||||
+ "smilegate_fewshot_nl2sql의 prompt와 queryPlan에 한 번 전달하세요. SQL은 실행하지 않습니다.",
|
||||
-1L, "게임 질의 계획", SELECT_AI_TOOL_PATH);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ import java.sql.Statement;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -36,7 +39,9 @@ public class SelectAiService {
|
||||
private static final String POLICY_PREFIX =
|
||||
"Answer the original user question using the current approved object list and policy. "
|
||||
+ "If no game identifier resolves through game-alias metadata, do not select a prefix-specific object "
|
||||
+ "and do not infer a default game. State that the required game identifier is missing instead.\n\n";
|
||||
+ "and do not infer a default game. Continue a game-neutral question with approved common objects. "
|
||||
+ "Only report a missing game identifier when the requested operation inherently requires a "
|
||||
+ "game-specific object.\n\n";
|
||||
private static final Pattern UNSAFE_SQL = Pattern.compile(
|
||||
"(?is)\\b(?:insert|update|delete|merge|alter|drop|create|truncate|grant|revoke|"
|
||||
+ "commit|rollback|savepoint|lock|call|exec(?:ute)?|begin|declare|for\\s+update|"
|
||||
@@ -84,22 +89,86 @@ public class SelectAiService {
|
||||
ResolvedExecutionScope scope = resolveExecutionScope(bearerToken, normalizedPrompt, scopeGameKey);
|
||||
|
||||
GameContext gameContext = resolveGameContext(bearerToken, normalizedPrompt);
|
||||
return generateAndExecutePrepared(
|
||||
bearerToken,
|
||||
normalizedPrompt,
|
||||
selectAi,
|
||||
gameContext.prompt(scope.prompt()),
|
||||
gameContext.scopeType(),
|
||||
gameContext.status(),
|
||||
gameContext.candidates().size(),
|
||||
scope,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
public JsonNode generateAndExecute(
|
||||
String bearerToken, String prompt, String scopeGameKey, JsonNode priorToolContext) {
|
||||
if (priorToolContext == null || priorToolContext.isMissingNode()
|
||||
|| priorToolContext.isNull()) {
|
||||
return generateAndExecute(bearerToken, prompt, scopeGameKey);
|
||||
}
|
||||
requireActiveToken(bearerToken);
|
||||
String normalizedPrompt = requiredPrompt(prompt);
|
||||
BackofficeProperties.SelectAi selectAi = requiredSelectAi();
|
||||
QueryPlanContext queryPlan = requiredQueryPlan(priorToolContext);
|
||||
ResolvedExecutionScope scope = queryPlan.scope(scopeGameKey);
|
||||
return generateAndExecutePrepared(
|
||||
bearerToken,
|
||||
normalizedPrompt,
|
||||
selectAi,
|
||||
queryPlan.prompt(normalizedPrompt),
|
||||
queryPlan.targetType(),
|
||||
queryPlan.status(),
|
||||
queryPlan.targetCount(),
|
||||
scope,
|
||||
queryPlan.allowedPrefixes(),
|
||||
queryPlan
|
||||
);
|
||||
}
|
||||
|
||||
private JsonNode generateAndExecutePrepared(
|
||||
String bearerToken,
|
||||
String originalPrompt,
|
||||
BackofficeProperties.SelectAi selectAi,
|
||||
String executionPrompt,
|
||||
String gameScopeType,
|
||||
String gameScopeStatus,
|
||||
int gameCandidateCount,
|
||||
ResolvedExecutionScope scope,
|
||||
Set<String> allowedPrefixes,
|
||||
QueryPlanContext queryPlan
|
||||
) {
|
||||
EnrichedPrompt enrichedPrompt = enrichWithFewShot(
|
||||
bearerToken, selectAi, gameContext.prompt(scope.prompt()));
|
||||
bearerToken, selectAi, executionPrompt);
|
||||
String generatedSql = generate(selectAi, enrichedPrompt.prompt(), "showsql");
|
||||
String normalizedSql = validateReadOnlySql(generatedSql);
|
||||
if (allowedPrefixes != null && gameScopeService != null
|
||||
&& gameScopeService.configured()
|
||||
&& gameScopeService.referencesGameScopedObjectOutsidePrefixes(
|
||||
bearerToken, normalizedSql, allowedPrefixes)) {
|
||||
throw new AppException(
|
||||
"Select AI 생성 SQL이 게임 질의 계획에 없는 prefix 전용 객체를 참조했습니다.");
|
||||
}
|
||||
QueryExecution execution = executeReadOnly(selectAi, normalizedSql);
|
||||
ObjectNode response = objectMapper.createObjectNode();
|
||||
response.put("status", "SHOWSQL_AND_EXECUTED");
|
||||
response.put("profile", selectAi.profile());
|
||||
response.put("originalPrompt", normalizedPrompt);
|
||||
response.put("gameScopeType", gameContext.scopeType());
|
||||
response.put("gameScopeStatus", gameContext.status());
|
||||
response.put("gameCandidateCount", gameContext.candidates().size());
|
||||
response.put("originalPrompt", originalPrompt);
|
||||
response.put("gameScopeType", gameScopeType);
|
||||
response.put("gameScopeStatus", gameScopeStatus);
|
||||
response.put("gameCandidateCount", gameCandidateCount);
|
||||
if (queryPlan != null) {
|
||||
response.put("queryPlanTargetType", queryPlan.targetType());
|
||||
response.put("queryPlanStatus", queryPlan.status());
|
||||
response.put("queryPlanTargetCount", queryPlan.targetCount());
|
||||
}
|
||||
if (scope.gameKey() != null) {
|
||||
response.put("scopeGameKey", scope.gameKey());
|
||||
response.put("scopeDisplayName", scope.displayName());
|
||||
response.put("scopeStatus", "DB_REVALIDATED");
|
||||
response.put("scopeStatus", queryPlan == null
|
||||
? "DB_REVALIDATED" : "QUERY_PLAN_VALIDATED");
|
||||
}
|
||||
response.put("fewShotStatus", enrichedPrompt.status());
|
||||
response.put("fewShotExampleCount", enrichedPrompt.exampleCount());
|
||||
@@ -115,11 +184,27 @@ public class SelectAiService {
|
||||
return response;
|
||||
}
|
||||
|
||||
public JsonNode generateAndExecute(
|
||||
String bearerToken, String prompt, String scopeGameKey, JsonNode priorToolContext) {
|
||||
String context = priorToolContext == null || priorToolContext.isMissingNode()
|
||||
|| priorToolContext.isNull() ? "" : "\n[PREVIOUS TOOL RESULT]\n" + priorToolContext;
|
||||
return generateAndExecute(bearerToken, prompt + context, scopeGameKey);
|
||||
private QueryPlanContext requiredQueryPlan(JsonNode plan) {
|
||||
String targetType = plan.path("targetType").asText("").trim().toUpperCase(Locale.ROOT);
|
||||
if (!Set.of("NONE", "SINGLE", "MULTI", "ALL").contains(targetType)
|
||||
|| !plan.path("targets").isArray()) {
|
||||
throw new AppException(
|
||||
"queryPlan은 targetType(NONE/SINGLE/MULTI/ALL)과 targets 배열이 필요합니다.");
|
||||
}
|
||||
Set<String> allowedPrefixes = new LinkedHashSet<>();
|
||||
for (JsonNode target : plan.path("targets")) {
|
||||
String prefix = target.path("gamePrefix").asText("").trim();
|
||||
if (!prefix.isEmpty()) {
|
||||
allowedPrefixes.add(prefix.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
return new QueryPlanContext(
|
||||
targetType,
|
||||
plan.path("status").asText(""),
|
||||
plan.path("targets").size(),
|
||||
Set.copyOf(allowedPrefixes),
|
||||
plan.deepCopy()
|
||||
);
|
||||
}
|
||||
|
||||
private GameContext resolveGameContext(String bearerToken, String question) {
|
||||
@@ -280,8 +365,8 @@ public class SelectAiService {
|
||||
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. Return a read-only no-match result "
|
||||
+ "and preserve the resolver status for the answer layer; do not invent a user-facing explanation.\n"
|
||||
+ "is resolved, do not substitute an arbitrary game-scoped object. Keep common-object questions "
|
||||
+ "game-neutral and preserve the resolver status for the answer layer.\n"
|
||||
+ "Original user question:\n" + prompt;
|
||||
}
|
||||
|
||||
@@ -425,6 +510,42 @@ public class SelectAiService {
|
||||
|
||||
private record ResolvedExecutionScope(String prompt, String gameKey, String displayName) {}
|
||||
|
||||
private record QueryPlanContext(
|
||||
String targetType,
|
||||
String status,
|
||||
int targetCount,
|
||||
Set<String> allowedPrefixes,
|
||||
JsonNode plan
|
||||
) {
|
||||
String prompt(String originalQuestion) {
|
||||
return "Use the authoritative game query plan below without independently rematching "
|
||||
+ "the game scope. Always answer the original question with one read-only SQL statement. "
|
||||
+ "For NONE, keep the query game-neutral and never select a prefix-specific object. "
|
||||
+ "For SINGLE, use only the matched target metadata. For MULTI or ALL, use the supplied "
|
||||
+ "target identifiers with IN and GROUP BY when a common object fits the question, or "
|
||||
+ "combine only the supplied non-null physical objects with UNION ALL when separate "
|
||||
+ "objects are required. A target with a null physical object must remain unresolved and "
|
||||
+ "must never be substituted with another target's object. Do not invent identifiers, "
|
||||
+ "prefixes, or physical object names.\n"
|
||||
+ "[AUTHORITATIVE GAME QUERY PLAN]\n" + plan
|
||||
+ "\n[ORIGINAL USER QUESTION]\n" + originalQuestion;
|
||||
}
|
||||
|
||||
ResolvedExecutionScope scope(String requestedGameKey) {
|
||||
String requested = requestedGameKey == null ? "" : requestedGameKey.trim();
|
||||
if (requested.isEmpty()) {
|
||||
return new ResolvedExecutionScope("", null, null);
|
||||
}
|
||||
for (JsonNode target : plan.path("targets")) {
|
||||
if (requested.equals(target.path("gameKey").asText(""))) {
|
||||
return new ResolvedExecutionScope(
|
||||
"", requested, target.path("gameName").asText(requested));
|
||||
}
|
||||
}
|
||||
throw new AppException("scopeGameKey가 queryPlan targets에 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
private record GameContext(
|
||||
String scopeType, String status, List<GameCatalogVectorService.GameCandidate> candidates) {
|
||||
String prompt(String original) {
|
||||
@@ -438,6 +559,8 @@ public class SelectAiService {
|
||||
.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("user_master_object_name: ")
|
||||
.append(candidate.userMasterObjectName()).append('\n')
|
||||
.append("cosine_distance: ").append(candidate.similarity()).append('\n');
|
||||
}
|
||||
context.append("[GAME SCOPE METADATA]\n")
|
||||
|
||||
@@ -98,7 +98,12 @@ class McpSseServiceTest {
|
||||
var fewShot = tools.get(4);
|
||||
assertThat(fewShot.path("name").asText())
|
||||
.isEqualTo("oracle.select_ai.test_fewshot_nl2sql");
|
||||
assertThat(fewShot.path("description").asText()).contains("Few-shot");
|
||||
assertThat(fewShot.path("description").asText())
|
||||
.contains("Few-shot")
|
||||
.contains("NONE, SINGLE, MULTI, ALL");
|
||||
assertThat(fewShot.path("inputSchema").path("properties")
|
||||
.path("queryPlan").path("description").asText())
|
||||
.contains("NONE, SINGLE, MULTI, ALL");
|
||||
|
||||
var gameScope = tools.get(5);
|
||||
assertThat(gameScope.path("name").asText())
|
||||
@@ -110,6 +115,10 @@ class McpSseServiceTest {
|
||||
.isEqualTo("oracle.select_ai.game_catalog_resolve");
|
||||
assertThat(tools.get(7).path("name").asText())
|
||||
.isEqualTo("oracle.select_ai.game_query_plan");
|
||||
assertThat(tools.get(7).path("description").asText())
|
||||
.contains("항상 먼저 호출")
|
||||
.contains("NONE, SINGLE, MULTI, ALL")
|
||||
.contains("smilegate_fewshot_nl2sql");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -161,10 +170,22 @@ class McpSseServiceTest {
|
||||
ObjectNode request = request(7, "tools/call");
|
||||
ObjectNode params = (ObjectNode) request.putObject("params");
|
||||
params.put("name", "oracle.select_ai.test_fewshot_nl2sql");
|
||||
params.putObject("arguments").put("prompt", "카제나 AU를 조회해 줘");
|
||||
ObjectNode arguments = params.putObject("arguments");
|
||||
arguments.put("prompt", "전체 매출을 조회해 줘");
|
||||
ObjectNode plan = arguments.putObject("queryPlan");
|
||||
plan.put("targetType", "NONE");
|
||||
plan.put("status", "NO_TARGET");
|
||||
plan.putArray("targets").addObject()
|
||||
.put("status", "NONE")
|
||||
.putNull("gameKey")
|
||||
.putNull("gamePrefix")
|
||||
.putNull("userMasterObjectName");
|
||||
|
||||
ObjectNode response = service.handle("default", request, "user-bearer");
|
||||
|
||||
CapturingSelectAiService agentService = (CapturingSelectAiService) selectAiService;
|
||||
assertThat(agentService.prompt).isEqualTo("전체 매출을 조회해 줘");
|
||||
assertThat(agentService.queryPlan.path("targetType").asText()).isEqualTo("NONE");
|
||||
assertThat(response.path("result").path("isError").asBoolean()).isFalse();
|
||||
String payload = response.path("result").path("content").get(0).path("text").asText();
|
||||
assertThat(payload)
|
||||
@@ -262,7 +283,9 @@ class McpSseServiceTest {
|
||||
.contains("\"status\" : \"PARTIAL\"")
|
||||
.contains("\"gameKey\" : \"SUPPORTED_GAME\"")
|
||||
.contains("\"mention\" : \"Unknown game\"")
|
||||
.contains("\"reasonCode\" : \"LLM_NO_CANDIDATE_MATCH\"");
|
||||
.contains("\"reasonCode\" : \"LLM_NO_CANDIDATE_MATCH\"")
|
||||
.contains("\"targetType\" : \"MULTI\"")
|
||||
.contains("\"nextAction\" : \"CALL_FEWSHOT\"");
|
||||
}
|
||||
|
||||
private ObjectNode request(int id, String method) {
|
||||
@@ -277,6 +300,7 @@ class McpSseServiceTest {
|
||||
|
||||
private String bearerToken;
|
||||
private String prompt;
|
||||
private JsonNode queryPlan;
|
||||
private boolean showpromptCalled;
|
||||
|
||||
private CapturingSelectAiService() {
|
||||
@@ -302,6 +326,13 @@ class McpSseServiceTest {
|
||||
return generateAndExecute(bearerToken, prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode generateAndExecute(
|
||||
String bearerToken, String prompt, String scopeGameKey, JsonNode priorToolContext) {
|
||||
this.queryPlan = priorToolContext;
|
||||
return generateAndExecute(bearerToken, prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode generatePrompt(String bearerToken, String prompt) {
|
||||
this.bearerToken = bearerToken;
|
||||
@@ -372,8 +403,23 @@ class McpSseServiceTest {
|
||||
@Override
|
||||
public ObjectNode queryPlan(String token, String question, int topK) {
|
||||
ObjectNode plan = new ObjectMapper().createObjectNode();
|
||||
plan.put("scopeType", "MULTI_GAME");
|
||||
plan.put("targetType", "MULTI");
|
||||
plan.put("scopeType", "MULTI");
|
||||
plan.put("status", "PARTIAL");
|
||||
plan.putArray("targets")
|
||||
.addObject()
|
||||
.put("mention", "Supported game")
|
||||
.put("status", "MATCHED")
|
||||
.put("gameKey", "SUPPORTED_GAME")
|
||||
.put("gamePrefix", "SUPPORTED")
|
||||
.put("userMasterObjectName", "SUPPORTED_COMN_USER_MST");
|
||||
plan.withArray("targets")
|
||||
.addObject()
|
||||
.put("mention", "Unknown game")
|
||||
.put("status", "UNMATCHED")
|
||||
.putNull("gameKey")
|
||||
.putNull("gamePrefix")
|
||||
.putNull("userMasterObjectName");
|
||||
plan.putArray("matchedGames")
|
||||
.addObject()
|
||||
.put("gameKey", "SUPPORTED_GAME");
|
||||
@@ -385,8 +431,8 @@ class McpSseServiceTest {
|
||||
.addObject()
|
||||
.put("mention", "Unknown game")
|
||||
.put("reasonCode", "LLM_NO_CANDIDATE_MATCH");
|
||||
plan.put("nextAction", "CALL_FEWSHOT_PER_GAME");
|
||||
plan.put("executionMode", "FAN_OUT");
|
||||
plan.put("nextAction", "CALL_FEWSHOT");
|
||||
plan.put("executionMode", "COMBINED");
|
||||
return plan;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ class SelectAiFewShotPromptTest {
|
||||
.contains("SELECT COUNT(*) AS AU_COUNT FROM APP_USER")
|
||||
.contains("Original user question:\ncurrent active users")
|
||||
.contains("current approved object list and policy")
|
||||
.contains("do not infer a default game");
|
||||
.contains("do not infer a default game")
|
||||
.contains("Continue a game-neutral question with approved common objects");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user