From beaef2d17108005a43ca2ab9b81ad229ecc0d050 Mon Sep 17 00:00:00 2001 From: devmrko Date: Mon, 27 Jul 2026 14:14:20 +0900 Subject: [PATCH] chain ADB chat game extraction before vector resolution --- .../service/GameCatalogVectorService.java | 31 ++++++++++++++++++- .../vpdbackoffice/service/McpSseService.java | 9 +++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/GameCatalogVectorService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/GameCatalogVectorService.java index 59b8122..6aed340 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/GameCatalogVectorService.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/GameCatalogVectorService.java @@ -4,16 +4,44 @@ import com.cloudhandson.vpdbackoffice.config.BackofficeProperties; import java.sql.*; import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.stereotype.Service; @Service public class GameCatalogVectorService { private final BackofficeProperties properties; private final BearerTokenService bearerTokenService; + private final ObjectMapper objectMapper; - public GameCatalogVectorService(BackofficeProperties properties, BearerTokenService bearerTokenService) { + public GameCatalogVectorService(BackofficeProperties properties, BearerTokenService bearerTokenService, ObjectMapper objectMapper) { this.properties = properties; this.bearerTokenService = bearerTokenService; + this.objectMapper = objectMapper; + } + + public Resolution resolve(String token, String question, int topK) { + String raw = extractMentions(token, question); + try { + JsonNode json = objectMapper.readTree(raw); + String hint = json.path("scope_hint").asText("UNKNOWN"); + List all = new ArrayList<>(); + for (JsonNode mention : json.path("game_mentions")) { + all.addAll(search(token, mention.asText(""), topK)); + } + return new Resolution(hint, all); + } catch (Exception e) { throw new AppException("게임명 추출 결과 JSON 파싱 실패: " + e.getMessage()); } + } + + private String extractMentions(String token, String question) { + BackofficeProperties.SelectAi db = properties.selectAi(); + try (Connection c = DriverManager.getConnection(db.dbUrl(), db.dbUsername(), db.dbPassword()); + CallableStatement s = c.prepareCall("{ ? = call sg_game_extract_mentions(?) }")) { + s.registerOutParameter(1, Types.CLOB); s.setString(2, question); s.execute(); + Clob value = (Clob) s.getObject(1); + return value == null ? "{\"game_mentions\":[],\"scope_hint\":\"UNKNOWN\"}" + : value.getSubString(1, (int) value.length()); + } catch (SQLException e) { throw new AppException("ADB 게임명 추출 실패: " + e.getMessage()); } } public List search(String token, String question, int topK) { @@ -34,4 +62,5 @@ public class GameCatalogVectorService { } public record GameCandidate(String gameKey, String gameId, String gamePrefix, String gameName, String aliases, double similarity) {} + public record Resolution(String scopeType, List candidates) {} } diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java index 3c93652..9d2f6ee 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java @@ -194,7 +194,7 @@ public class McpSseService { response = gameScopeResponse(gameScopeService.resolve( token, arguments.path("question").asText(""))); } else if (gameCatalogTool) { - response = gameCatalogVectorResponse(gameCatalogVectorService.search( + response = gameCatalogVectorResponse(gameCatalogVectorService.resolve( token, arguments.path("question").asText(""), arguments.path("topK").asInt(5))); } else { String prompt = arguments.path("prompt").asText(""); @@ -247,11 +247,12 @@ public class McpSseService { return response; } - private ObjectNode gameCatalogVectorResponse(List candidates) { + private ObjectNode gameCatalogVectorResponse(GameCatalogVectorService.Resolution resolution) { ObjectNode response = objectMapper.createObjectNode(); - response.put("status", candidates.isEmpty() ? "NO_MATCH" : "CANDIDATES"); + response.put("scopeType", resolution.scopeType()); + response.put("status", resolution.candidates().isEmpty() ? "NO_MATCH" : "CANDIDATES"); ArrayNode items = response.putArray("matchedGames"); - for (GameCatalogVectorService.GameCandidate c : candidates) { + for (GameCatalogVectorService.GameCandidate c : resolution.candidates()) { ObjectNode item = items.addObject(); item.put("gameKey", c.gameKey()); item.put("gameId", c.gameId()); item.put("gamePrefix", c.gamePrefix()); item.put("gameName", c.gameName());