refs #735: add few-shot NL2SQL MCP tool

This commit is contained in:
devmrko
2026-07-24 16:21:01 +09:00
parent b738708528
commit caa7d55085
6 changed files with 142 additions and 11 deletions

View File

@@ -17,7 +17,10 @@ public record McpProperties(
String qaVectorSearchToolDescription,
String qaVectorStoreToolName,
String qaVectorStoreToolLabel,
String qaVectorStoreToolDescription
String qaVectorStoreToolDescription,
String fewShotNl2SqlToolName,
String fewShotNl2SqlToolLabel,
String fewShotNl2SqlToolDescription
) {
private static final String DEFAULT_TOOL_NAME = "oracle.select_ai.data_text2sql";
@@ -46,6 +49,13 @@ public record McpProperties(
private static final String DEFAULT_QA_VECTOR_STORE_TOOL_DESCRIPTION =
"검토된 Select AI 결과를 후속 Text2SQL 품질 향상용 예제 SQL로 저장합니다. "
+ "질문과 읽기 전용 답 SQL이 필요합니다.";
private static final String DEFAULT_FEW_SHOT_NL2SQL_TOOL_NAME =
"oracle.select_ai.smilegate_fewshot_nl2sql";
private static final String DEFAULT_FEW_SHOT_NL2SQL_TOOL_LABEL =
"Few-shot NL2SQL 실행";
private static final String DEFAULT_FEW_SHOT_NL2SQL_TOOL_DESCRIPTION =
"벡터 Few-shot 예제를 찾아 prompt에 반영하고, SHOWSQL로 생성한 읽기 전용 SQL을 실행합니다. "
+ "Few-shot 근거, 생성 SQL, 실행 결과를 함께 반환합니다.";
public String resolvedToolName() {
return requiredOrDefault(toolName, DEFAULT_TOOL_NAME);
@@ -99,6 +109,18 @@ public record McpProperties(
return requiredOrDefault(qaVectorStoreToolDescription, DEFAULT_QA_VECTOR_STORE_TOOL_DESCRIPTION);
}
public String resolvedFewShotNl2SqlToolName() {
return requiredOrDefault(fewShotNl2SqlToolName, DEFAULT_FEW_SHOT_NL2SQL_TOOL_NAME);
}
public String resolvedFewShotNl2SqlToolLabel() {
return requiredOrDefault(fewShotNl2SqlToolLabel, DEFAULT_FEW_SHOT_NL2SQL_TOOL_LABEL);
}
public String resolvedFewShotNl2SqlToolDescription() {
return requiredOrDefault(fewShotNl2SqlToolDescription, DEFAULT_FEW_SHOT_NL2SQL_TOOL_DESCRIPTION);
}
private String requiredOrDefault(String value, String fallback) {
return value == null || value.isBlank() ? fallback : value.trim();
}

View File

@@ -73,7 +73,8 @@ public class McpSseService {
/** Tools registered by this MCP server. */
public List<McpToolView> registeredTools() {
return List.of(
selectAiQueryView(), selectAiShowpromptView(), qaVectorSearchView(), qaVectorStoreView());
selectAiQueryView(), selectAiShowpromptView(), qaVectorSearchView(),
qaVectorStoreView(), fewShotNl2SqlView());
}
private ObjectNode initializeResult(String contextPath) {
@@ -96,6 +97,7 @@ public class McpSseService {
tools.add(toolDefinition(selectAiShowpromptView()));
tools.add(toolDefinition(qaVectorSearchView()));
tools.add(toolDefinition(qaVectorStoreView()));
tools.add(toolDefinition(fewShotNl2SqlView()));
result.set("tools", tools);
return result;
}
@@ -149,7 +151,8 @@ public class McpSseService {
boolean showpromptTool = showpromptToolName().equals(calledToolName);
boolean qaVectorSearchTool = qaVectorSearchToolName().equals(calledToolName);
boolean qaVectorStoreTool = qaVectorStoreToolName().equals(calledToolName);
if (!queryTool && !showpromptTool && !qaVectorSearchTool && !qaVectorStoreTool) {
boolean fewShotNl2SqlTool = fewShotNl2SqlToolName().equals(calledToolName);
if (!queryTool && !showpromptTool && !qaVectorSearchTool && !qaVectorStoreTool && !fewShotNl2SqlTool) {
throw new AppException("등록되지 않은 MCP tool입니다: " + calledToolName);
}
@@ -172,7 +175,7 @@ public class McpSseService {
));
} else {
String prompt = arguments.path("prompt").asText("");
response = queryTool
response = queryTool || fewShotNl2SqlTool
? selectAiService.generateAndExecute(token, prompt)
: selectAiService.generatePrompt(token, prompt);
}
@@ -278,6 +281,12 @@ public class McpSseService {
qaVectorStoreToolLabel(), SELECT_AI_TOOL_PATH);
}
private McpToolView fewShotNl2SqlView() {
return new McpToolView(
fewShotNl2SqlToolName(), fewShotNl2SqlToolDescription(), -1L,
fewShotNl2SqlToolLabel(), SELECT_AI_TOOL_PATH);
}
private String selectAiProfile() {
BackofficeProperties.SelectAi selectAi = properties == null ? null : properties.selectAi();
if (selectAi == null || selectAi.profile() == null || selectAi.profile().isBlank()) {
@@ -354,6 +363,22 @@ public class McpSseService {
: mcpProperties.resolvedQaVectorStoreToolDescription();
}
private String fewShotNl2SqlToolName() {
return mcpProperties == null ? "oracle.select_ai.smilegate_fewshot_nl2sql"
: mcpProperties.resolvedFewShotNl2SqlToolName();
}
private String fewShotNl2SqlToolLabel() {
return mcpProperties == null ? "Few-shot NL2SQL 실행"
: mcpProperties.resolvedFewShotNl2SqlToolLabel();
}
private String fewShotNl2SqlToolDescription() {
return mcpProperties == null
? "벡터 Few-shot 예제를 찾아 prompt에 반영하고 SHOWSQL로 생성한 읽기 전용 SQL을 실행합니다. Few-shot 근거, 생성 SQL, 실행 결과를 함께 반환합니다."
: mcpProperties.resolvedFewShotNl2SqlToolDescription();
}
private String pretty(Object value) {
try {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);

View File

@@ -78,6 +78,7 @@ public class SelectAiService {
response.put("originalPrompt", normalizedPrompt);
response.put("fewShotStatus", enrichedPrompt.status());
response.put("fewShotExampleCount", enrichedPrompt.exampleCount());
addFewShotExamples(response, enrichedPrompt.examples());
response.put("generatedSql", normalizedSql);
response.put("execution", "READ_ONLY_EXECUTED");
response.put("rowCount", execution.items().size());
@@ -89,6 +90,21 @@ public class SelectAiService {
return response;
}
private void addFewShotExamples(ObjectNode response, List<QaVectorService.VectorExample> examples) {
ArrayNode items = response.putArray("fewShotExamples");
for (QaVectorService.VectorExample example : examples) {
ObjectNode item = items.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());
}
}
/** Returns the prompt Select AI assembled for SQL generation without executing generated SQL. */
public JsonNode generatePrompt(String bearerToken, String prompt) {
requireActiveToken(bearerToken);
@@ -171,20 +187,21 @@ public class SelectAiService {
String prompt
) {
if (!fewShotEnabled(selectAi) || qaVectorService == null) {
return new EnrichedPrompt(prompt, "DISABLED", 0);
return new EnrichedPrompt(prompt, "DISABLED", 0, List.of());
}
try {
List<QaVectorService.VectorExample> examples = qaVectorService
.search(bearerToken, prompt, fewShotTopK(selectAi))
.examples();
if (examples.isEmpty()) {
return new EnrichedPrompt(composePolicyPrompt(prompt), "NO_MATCH", 0);
return new EnrichedPrompt(composePolicyPrompt(prompt), "NO_MATCH", 0, List.of());
}
return new EnrichedPrompt(
composeFewShotPrompt(prompt, examples), "APPLIED", Math.min(examples.size(), MAX_FEW_SHOT_EXAMPLES));
composeFewShotPrompt(prompt, examples), "APPLIED", Math.min(examples.size(), MAX_FEW_SHOT_EXAMPLES),
examples.subList(0, Math.min(examples.size(), MAX_FEW_SHOT_EXAMPLES)));
} catch (Exception ignored) {
// Vector retrieval is an optional prompt aid; preserve the normal Text2SQL path on failure.
return new EnrichedPrompt(composePolicyPrompt(prompt), "UNAVAILABLE", 0);
return new EnrichedPrompt(composePolicyPrompt(prompt), "UNAVAILABLE", 0, List.of());
}
}
@@ -325,5 +342,6 @@ public class SelectAiService {
private record QueryExecution(ArrayNode items, boolean truncated) {}
private record EnrichedPrompt(String prompt, String status, int exampleCount) {}
private record EnrichedPrompt(
String prompt, String status, int exampleCount, List<QaVectorService.VectorExample> examples) {}
}

View File

@@ -98,6 +98,9 @@ backoffice:
qa-vector-store-tool-name: ${BACKOFFICE_MCP_QA_VECTOR_STORE_TOOL_NAME:oracle.select_ai.qa_vector_store}
qa-vector-store-tool-label: ${BACKOFFICE_MCP_QA_VECTOR_STORE_TOOL_LABEL:Select AI 예제 SQL 저장}
qa-vector-store-tool-description: ${BACKOFFICE_MCP_QA_VECTOR_STORE_TOOL_DESCRIPTION:검토된 Select AI 결과를 후속 Text2SQL 품질 향상용 예제 SQL로 저장합니다. 질문과 읽기 전용 답 SQL이 필요합니다.}
few-shot-nl2sql-tool-name: ${BACKOFFICE_MCP_FEW_SHOT_NL2SQL_TOOL_NAME:oracle.select_ai.smilegate_fewshot_nl2sql}
few-shot-nl2sql-tool-label: ${BACKOFFICE_MCP_FEW_SHOT_NL2SQL_TOOL_LABEL:Few-shot NL2SQL 실행}
few-shot-nl2sql-tool-description: ${BACKOFFICE_MCP_FEW_SHOT_NL2SQL_TOOL_DESCRIPTION:벡터 Few-shot 예제를 찾아 prompt에 반영하고 SHOWSQL로 생성한 읽기 전용 SQL을 실행합니다. Few-shot 근거, 생성 SQL, 실행 결과를 함께 반환합니다.}
masking:
policies: ${BACKOFFICE_MASKING_POLICIES:}
security-sql-scripts: