refs #739: normalize MCP query plan envelopes
This commit is contained in:
@@ -2,6 +2,7 @@ package com.cloudhandson.vpdbackoffice.service;
|
|||||||
|
|
||||||
import com.cloudhandson.vpdbackoffice.config.BackofficeProperties;
|
import com.cloudhandson.vpdbackoffice.config.BackofficeProperties;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.token.BearerTokenRecord;
|
import com.cloudhandson.vpdbackoffice.domain.token.BearerTokenRecord;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
@@ -185,14 +186,16 @@ public class SelectAiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private QueryPlanContext requiredQueryPlan(JsonNode plan) {
|
private QueryPlanContext requiredQueryPlan(JsonNode plan) {
|
||||||
String targetType = plan.path("targetType").asText("").trim().toUpperCase(Locale.ROOT);
|
JsonNode normalizedPlan = unwrapMcpToolResult(plan);
|
||||||
|
String targetType = normalizedPlan.path("targetType")
|
||||||
|
.asText("").trim().toUpperCase(Locale.ROOT);
|
||||||
if (!Set.of("NONE", "SINGLE", "MULTI", "ALL").contains(targetType)
|
if (!Set.of("NONE", "SINGLE", "MULTI", "ALL").contains(targetType)
|
||||||
|| !plan.path("targets").isArray()) {
|
|| !normalizedPlan.path("targets").isArray()) {
|
||||||
throw new AppException(
|
throw new AppException(
|
||||||
"queryPlan은 targetType(NONE/SINGLE/MULTI/ALL)과 targets 배열이 필요합니다.");
|
"queryPlan은 targetType(NONE/SINGLE/MULTI/ALL)과 targets 배열이 필요합니다.");
|
||||||
}
|
}
|
||||||
Set<String> allowedPrefixes = new LinkedHashSet<>();
|
Set<String> allowedPrefixes = new LinkedHashSet<>();
|
||||||
for (JsonNode target : plan.path("targets")) {
|
for (JsonNode target : normalizedPlan.path("targets")) {
|
||||||
String prefix = target.path("gamePrefix").asText("").trim();
|
String prefix = target.path("gamePrefix").asText("").trim();
|
||||||
if (!prefix.isEmpty()) {
|
if (!prefix.isEmpty()) {
|
||||||
allowedPrefixes.add(prefix.toUpperCase(Locale.ROOT));
|
allowedPrefixes.add(prefix.toUpperCase(Locale.ROOT));
|
||||||
@@ -200,13 +203,40 @@ public class SelectAiService {
|
|||||||
}
|
}
|
||||||
return new QueryPlanContext(
|
return new QueryPlanContext(
|
||||||
targetType,
|
targetType,
|
||||||
plan.path("status").asText(""),
|
normalizedPlan.path("status").asText(""),
|
||||||
plan.path("targets").size(),
|
normalizedPlan.path("targets").size(),
|
||||||
Set.copyOf(allowedPrefixes),
|
Set.copyOf(allowedPrefixes),
|
||||||
plan.deepCopy()
|
normalizedPlan.deepCopy()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept the direct game-query-plan contract as well as the standard MCP
|
||||||
|
* tools/call envelope the ReAct model can copy from a preceding observation.
|
||||||
|
* This is transport normalization only; the target contract itself remains
|
||||||
|
* the authoritative data-driven policy.
|
||||||
|
*/
|
||||||
|
private JsonNode unwrapMcpToolResult(JsonNode candidate) {
|
||||||
|
JsonNode current = candidate;
|
||||||
|
for (int depth = 0; depth < 3 && current != null; depth++) {
|
||||||
|
if (current.isTextual()) {
|
||||||
|
try {
|
||||||
|
current = objectMapper.readTree(current.asText());
|
||||||
|
continue;
|
||||||
|
} catch (JsonProcessingException ignored) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonNode response = current.path("response");
|
||||||
|
if (response.isObject() || response.isTextual()) {
|
||||||
|
current = response;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return current == null ? objectMapper.createObjectNode() : current;
|
||||||
|
}
|
||||||
|
|
||||||
private GameContext resolveGameContext(String bearerToken, String question) {
|
private GameContext resolveGameContext(String bearerToken, String question) {
|
||||||
try {
|
try {
|
||||||
List<GameCatalogVectorService.GameCandidate> candidates =
|
List<GameCatalogVectorService.GameCandidate> candidates =
|
||||||
|
|||||||
Reference in New Issue
Block a user