refs #731: add QA vector MCP tools
This commit is contained in:
@@ -20,17 +20,20 @@ public class McpSseService {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final BackofficeProperties properties;
|
||||
private final McpProperties mcpProperties;
|
||||
private final QaVectorService qaVectorService;
|
||||
|
||||
public McpSseService(
|
||||
SelectAiService selectAiService,
|
||||
ObjectMapper objectMapper,
|
||||
BackofficeProperties properties,
|
||||
McpProperties mcpProperties
|
||||
McpProperties mcpProperties,
|
||||
QaVectorService qaVectorService
|
||||
) {
|
||||
this.selectAiService = selectAiService;
|
||||
this.objectMapper = objectMapper;
|
||||
this.properties = properties;
|
||||
this.mcpProperties = mcpProperties;
|
||||
this.qaVectorService = qaVectorService;
|
||||
}
|
||||
|
||||
public ObjectNode handle(String contextPath, JsonNode request) {
|
||||
@@ -69,7 +72,8 @@ public class McpSseService {
|
||||
|
||||
/** Tools registered by this MCP server. */
|
||||
public List<McpToolView> registeredTools() {
|
||||
return List.of(selectAiQueryView(), selectAiShowpromptView());
|
||||
return List.of(
|
||||
selectAiQueryView(), selectAiShowpromptView(), qaVectorSearchView(), qaVectorStoreView());
|
||||
}
|
||||
|
||||
private ObjectNode initializeResult(String contextPath) {
|
||||
@@ -90,6 +94,8 @@ public class McpSseService {
|
||||
ArrayNode tools = objectMapper.createArrayNode();
|
||||
tools.add(toolDefinition(selectAiQueryView()));
|
||||
tools.add(toolDefinition(selectAiShowpromptView()));
|
||||
tools.add(toolDefinition(qaVectorSearchView()));
|
||||
tools.add(toolDefinition(qaVectorStoreView()));
|
||||
result.set("tools", tools);
|
||||
return result;
|
||||
}
|
||||
@@ -103,26 +109,47 @@ public class McpSseService {
|
||||
schema.put("type", "object");
|
||||
ObjectNode properties = objectMapper.createObjectNode();
|
||||
|
||||
ObjectNode prompt = objectMapper.createObjectNode();
|
||||
prompt.put("type", "string");
|
||||
prompt.put("description", promptDescription());
|
||||
prompt.put("maxLength", 4000);
|
||||
properties.set("prompt", prompt);
|
||||
|
||||
schema.set("properties", properties);
|
||||
ArrayNode required = objectMapper.createArrayNode();
|
||||
required.add("prompt");
|
||||
if (qaVectorSearchToolName().equals(toolView.name())) {
|
||||
addStringProperty(properties, "question", "few-shot 예제 SQL을 찾을 현재 질문입니다.", 4000);
|
||||
ObjectNode topK = properties.putObject("topK");
|
||||
topK.put("type", "integer");
|
||||
topK.put("description", "반환할 유사 예제 수입니다. 기본값은 3입니다.");
|
||||
topK.put("minimum", 1);
|
||||
topK.put("maximum", 20);
|
||||
topK.put("default", 3);
|
||||
required.add("question");
|
||||
} else if (qaVectorStoreToolName().equals(toolView.name())) {
|
||||
addStringProperty(properties, "question", "검토된 Select AI 예제가 답한 업무 질문입니다.", 4000);
|
||||
addStringProperty(properties, "answerSql", "검토된 단일 읽기 전용 SELECT/WITH SQL입니다.", 20000);
|
||||
addStringProperty(properties, "answer", "선택 사항인 답변 또는 검토 메모입니다.", 20000);
|
||||
required.add("question");
|
||||
required.add("answerSql");
|
||||
} else {
|
||||
addStringProperty(properties, "prompt", promptDescription(), 4000);
|
||||
required.add("prompt");
|
||||
}
|
||||
schema.set("properties", properties);
|
||||
schema.set("required", required);
|
||||
schema.put("additionalProperties", false);
|
||||
item.set("inputSchema", schema);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void addStringProperty(ObjectNode properties, String name, String description, int maxLength) {
|
||||
ObjectNode property = properties.putObject(name);
|
||||
property.put("type", "string");
|
||||
property.put("description", description);
|
||||
property.put("maxLength", maxLength);
|
||||
}
|
||||
|
||||
private ObjectNode toolsCallResult(JsonNode params, String vpdBearerToken) {
|
||||
String calledToolName = params.path("name").asText("");
|
||||
boolean queryTool = toolName().equals(calledToolName);
|
||||
boolean showpromptTool = showpromptToolName().equals(calledToolName);
|
||||
if (!queryTool && !showpromptTool) {
|
||||
boolean qaVectorSearchTool = qaVectorSearchToolName().equals(calledToolName);
|
||||
boolean qaVectorStoreTool = qaVectorStoreToolName().equals(calledToolName);
|
||||
if (!queryTool && !showpromptTool && !qaVectorSearchTool && !qaVectorStoreTool) {
|
||||
throw new AppException("등록되지 않은 MCP tool입니다: " + calledToolName);
|
||||
}
|
||||
|
||||
@@ -133,10 +160,22 @@ public class McpSseService {
|
||||
}
|
||||
JsonNode response;
|
||||
try {
|
||||
String prompt = arguments.path("prompt").asText("");
|
||||
response = queryTool
|
||||
? selectAiService.generateAndExecute(token, prompt)
|
||||
: selectAiService.generatePrompt(token, prompt);
|
||||
if (qaVectorSearchTool) {
|
||||
response = qaVectorSearchResponse(
|
||||
qaVectorService.search(token, arguments.path("question").asText(""), arguments.path("topK").asInt(3)));
|
||||
} else if (qaVectorStoreTool) {
|
||||
response = qaVectorStoreResponse(qaVectorService.store(
|
||||
token,
|
||||
arguments.path("question").asText(""),
|
||||
arguments.path("answerSql").asText(""),
|
||||
arguments.path("answer").isMissingNode() ? null : arguments.path("answer").asText(null)
|
||||
));
|
||||
} else {
|
||||
String prompt = arguments.path("prompt").asText("");
|
||||
response = queryTool
|
||||
? selectAiService.generateAndExecute(token, prompt)
|
||||
: selectAiService.generatePrompt(token, prompt);
|
||||
}
|
||||
} catch (VpdTokenAccessDeniedException ignored) {
|
||||
return tokenAccessDeniedResult();
|
||||
}
|
||||
@@ -158,6 +197,37 @@ public class McpSseService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private ObjectNode qaVectorSearchResponse(QaVectorService.VectorSearchResult result) {
|
||||
ObjectNode response = objectMapper.createObjectNode();
|
||||
response.put("status", "QA_VECTOR_SEARCH");
|
||||
response.put("instruction", "Select AI SQL 생성 전에 few-shot 예제 SQL 후보를 확인합니다.");
|
||||
response.put("question", result.question());
|
||||
response.put("topK", result.topK());
|
||||
ArrayNode examples = response.putArray("examples");
|
||||
for (QaVectorService.VectorExample example : result.examples()) {
|
||||
ObjectNode item = examples.addObject();
|
||||
item.put("exampleId", example.exampleId());
|
||||
item.put("question", example.question());
|
||||
item.put("answerSql", example.answerSql());
|
||||
if (example.answer() != null) {
|
||||
item.put("answer", example.answer());
|
||||
}
|
||||
item.put("embeddingModel", example.embeddingModel());
|
||||
item.put("cosineDistance", example.cosineDistance());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private ObjectNode qaVectorStoreResponse(QaVectorService.VectorStoreResult stored) {
|
||||
ObjectNode response = objectMapper.createObjectNode();
|
||||
response.put("status", "QA_VECTOR_STORED");
|
||||
response.put("instruction", "검토된 Select AI 결과를 후속 Text2SQL 품질 향상용 예제 SQL로 저장했습니다.");
|
||||
response.put("exampleId", stored.exampleId());
|
||||
response.put("question", stored.question());
|
||||
response.put("embeddingModel", stored.embeddingModel());
|
||||
return response;
|
||||
}
|
||||
|
||||
private ObjectNode tokenAccessDeniedResult() {
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.put("status", "VPD_TOKEN_DENIED");
|
||||
@@ -196,6 +266,18 @@ public class McpSseService {
|
||||
);
|
||||
}
|
||||
|
||||
private McpToolView qaVectorSearchView() {
|
||||
return new McpToolView(
|
||||
qaVectorSearchToolName(), qaVectorSearchToolDescription(), -1L,
|
||||
qaVectorSearchToolLabel(), SELECT_AI_TOOL_PATH);
|
||||
}
|
||||
|
||||
private McpToolView qaVectorStoreView() {
|
||||
return new McpToolView(
|
||||
qaVectorStoreToolName(), qaVectorStoreToolDescription(), -1L,
|
||||
qaVectorStoreToolLabel(), SELECT_AI_TOOL_PATH);
|
||||
}
|
||||
|
||||
private String selectAiProfile() {
|
||||
BackofficeProperties.SelectAi selectAi = properties == null ? null : properties.selectAi();
|
||||
if (selectAi == null || selectAi.profile() == null || selectAi.profile().isBlank()) {
|
||||
@@ -240,6 +322,38 @@ public class McpSseService {
|
||||
: mcpProperties.resolvedShowpromptToolDescription();
|
||||
}
|
||||
|
||||
private String qaVectorSearchToolName() {
|
||||
return mcpProperties == null ? "oracle.select_ai.qa_vector_search"
|
||||
: mcpProperties.resolvedQaVectorSearchToolName();
|
||||
}
|
||||
|
||||
private String qaVectorSearchToolLabel() {
|
||||
return mcpProperties == null ? "Select AI 예제 SQL 조회"
|
||||
: mcpProperties.resolvedQaVectorSearchToolLabel();
|
||||
}
|
||||
|
||||
private String qaVectorSearchToolDescription() {
|
||||
return mcpProperties == null
|
||||
? "현재 질문에 사용할 유사 예제 SQL을 Select AI 실행 전에 조회합니다."
|
||||
: mcpProperties.resolvedQaVectorSearchToolDescription();
|
||||
}
|
||||
|
||||
private String qaVectorStoreToolName() {
|
||||
return mcpProperties == null ? "oracle.select_ai.qa_vector_store"
|
||||
: mcpProperties.resolvedQaVectorStoreToolName();
|
||||
}
|
||||
|
||||
private String qaVectorStoreToolLabel() {
|
||||
return mcpProperties == null ? "Select AI 예제 SQL 저장"
|
||||
: mcpProperties.resolvedQaVectorStoreToolLabel();
|
||||
}
|
||||
|
||||
private String qaVectorStoreToolDescription() {
|
||||
return mcpProperties == null
|
||||
? "검토된 Select AI 결과를 후속 Text2SQL 품질 향상용 예제 SQL로 저장합니다."
|
||||
: mcpProperties.resolvedQaVectorStoreToolDescription();
|
||||
}
|
||||
|
||||
private String pretty(Object value) {
|
||||
try {
|
||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
|
||||
|
||||
Reference in New Issue
Block a user