refs #726: add Select AI SHOWPROMPT diagnostic tool
This commit is contained in:
52
docs/design/726-showprompt-diagnostic-mcp/README.md
Normal file
52
docs/design/726-showprompt-diagnostic-mcp/README.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Redmine #726 · Select AI SHOWPROMPT 진단 MCP
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
|
||||||
|
기존 Text2SQL 생성·실행 tool과 분리된 읽기 전용 SHOWPROMPT 진단 tool을
|
||||||
|
MCP `tools/list`에 추가한다. 일반 질문 처리 Agent는 포털 allowlist를 통해
|
||||||
|
기존 tool만 사용하고, FAIL 개선 제안 버튼만 진단 tool을 직접 호출한다.
|
||||||
|
|
||||||
|
## 외부 설정
|
||||||
|
|
||||||
|
- `BACKOFFICE_MCP_SHOWPROMPT_TOOL_NAME`
|
||||||
|
- `BACKOFFICE_MCP_SHOWPROMPT_TOOL_LABEL`
|
||||||
|
- `BACKOFFICE_MCP_SHOWPROMPT_TOOL_DESCRIPTION`
|
||||||
|
|
||||||
|
기본값은 제품 중립적인 `oracle.select_ai.data_showprompt`와 업무 데이터
|
||||||
|
표현을 사용한다. 스마일게이트 운영값은 외부 env에서 고객 전용 이름으로
|
||||||
|
설정한다.
|
||||||
|
|
||||||
|
## 동작
|
||||||
|
|
||||||
|
1. HTTP Bearer Token을 기존 업무 사용자 토큰으로 검증한다.
|
||||||
|
2. prompt를 기존 4,000자 제한으로 검증한다.
|
||||||
|
3. schema-owned Select AI 연결에서
|
||||||
|
`DBMS_CLOUD_AI.GENERATE(prompt, profile, 'showprompt')`를 호출한다.
|
||||||
|
4. SQL을 실행하지 않고 다음 JSON을 반환한다.
|
||||||
|
- `status=SHOWPROMPT`
|
||||||
|
- `profile`
|
||||||
|
- `selectAiPrompt`
|
||||||
|
|
||||||
|
## 변경 함수
|
||||||
|
|
||||||
|
| 파일/함수 | 변경 |
|
||||||
|
|---|---|
|
||||||
|
| `McpProperties` | SHOWPROMPT tool 이름·label·description 외부 설정 |
|
||||||
|
| `SelectAiService.generatePrompt` | 인증·prompt 검증 후 SHOWPROMPT 반환 |
|
||||||
|
| `SelectAiService.generate` | `showsql/showprompt` action을 bind하는 공통 생성 함수 |
|
||||||
|
| `McpSseService.toolsListResult` | Text2SQL과 SHOWPROMPT 두 tool 등록 |
|
||||||
|
| `McpSseService.toolsCallResult` | exact tool name에 따라 query/diagnostic 분기 |
|
||||||
|
|
||||||
|
## 안전 조건
|
||||||
|
|
||||||
|
- SHOWPROMPT tool은 생성 SQL을 실행하지 않는다.
|
||||||
|
- 임의 action 인자를 사용자에게 받지 않는다.
|
||||||
|
- 응답에 Bearer Token이나 DB 연결 정보를 포함하지 않는다.
|
||||||
|
- 기존 Text2SQL tool 이름과 계약은 유지한다.
|
||||||
|
|
||||||
|
## 테스트
|
||||||
|
|
||||||
|
- tools/list에 두 tool과 각 prompt schema가 존재한다.
|
||||||
|
- Text2SQL 호출은 기존 generate-and-execute 경로를 유지한다.
|
||||||
|
- SHOWPROMPT 호출은 generatePrompt만 실행한다.
|
||||||
|
- 누락/비활성 Bearer Token은 기존과 동일하게 거절한다.
|
||||||
@@ -8,7 +8,10 @@ public record McpProperties(
|
|||||||
String toolName,
|
String toolName,
|
||||||
String toolLabel,
|
String toolLabel,
|
||||||
String toolDescription,
|
String toolDescription,
|
||||||
String promptDescription
|
String promptDescription,
|
||||||
|
String showpromptToolName,
|
||||||
|
String showpromptToolLabel,
|
||||||
|
String showpromptToolDescription
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private static final String DEFAULT_TOOL_NAME = "oracle.select_ai.data_text2sql";
|
private static final String DEFAULT_TOOL_NAME = "oracle.select_ai.data_text2sql";
|
||||||
@@ -18,6 +21,13 @@ public record McpProperties(
|
|||||||
+ "생성 SQL과 최대 100건의 조회 결과를 함께 반환하며 DDL/DML/잠금/패키지 호출은 실행하지 않습니다.";
|
+ "생성 SQL과 최대 100건의 조회 결과를 함께 반환하며 DDL/DML/잠금/패키지 호출은 실행하지 않습니다.";
|
||||||
private static final String DEFAULT_PROMPT_DESCRIPTION =
|
private static final String DEFAULT_PROMPT_DESCRIPTION =
|
||||||
"업무 데이터에서 조회할 내용을 자연어로 입력합니다.";
|
"업무 데이터에서 조회할 내용을 자연어로 입력합니다.";
|
||||||
|
private static final String DEFAULT_SHOWPROMPT_TOOL_NAME =
|
||||||
|
"oracle.select_ai.data_showprompt";
|
||||||
|
private static final String DEFAULT_SHOWPROMPT_TOOL_LABEL =
|
||||||
|
"업무 데이터 SHOWPROMPT";
|
||||||
|
private static final String DEFAULT_SHOWPROMPT_TOOL_DESCRIPTION =
|
||||||
|
"Select AI가 SQL 생성에 사용한 prompt를 조회하는 읽기 전용 진단 도구입니다. "
|
||||||
|
+ "생성 SQL이나 데이터 조회 SQL은 실행하지 않습니다.";
|
||||||
|
|
||||||
public String resolvedToolName() {
|
public String resolvedToolName() {
|
||||||
return requiredOrDefault(toolName, DEFAULT_TOOL_NAME);
|
return requiredOrDefault(toolName, DEFAULT_TOOL_NAME);
|
||||||
@@ -35,6 +45,18 @@ public record McpProperties(
|
|||||||
return requiredOrDefault(promptDescription, DEFAULT_PROMPT_DESCRIPTION);
|
return requiredOrDefault(promptDescription, DEFAULT_PROMPT_DESCRIPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String resolvedShowpromptToolName() {
|
||||||
|
return requiredOrDefault(showpromptToolName, DEFAULT_SHOWPROMPT_TOOL_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolvedShowpromptToolLabel() {
|
||||||
|
return requiredOrDefault(showpromptToolLabel, DEFAULT_SHOWPROMPT_TOOL_LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolvedShowpromptToolDescription() {
|
||||||
|
return requiredOrDefault(showpromptToolDescription, DEFAULT_SHOWPROMPT_TOOL_DESCRIPTION);
|
||||||
|
}
|
||||||
|
|
||||||
private String requiredOrDefault(String value, String fallback) {
|
private String requiredOrDefault(String value, String fallback) {
|
||||||
return value == null || value.isBlank() ? fallback : value.trim();
|
return value == null || value.isBlank() ? fallback : value.trim();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/** MCP boundary exposing a configured Select AI generation and read-only execution tool. */
|
/** MCP boundary exposing Select AI query and read-only prompt diagnostic tools. */
|
||||||
@Service
|
@Service
|
||||||
public class McpSseService {
|
public class McpSseService {
|
||||||
|
|
||||||
private static final String SELECT_AI_VPD_QUERY_PATH = "/mcp (tools/call)";
|
private static final String SELECT_AI_TOOL_PATH = "/mcp (tools/call)";
|
||||||
|
|
||||||
private final SelectAiService selectAiService;
|
private final SelectAiService selectAiService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
@@ -67,9 +67,9 @@ public class McpSseService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The only tool registered by this MCP server. */
|
/** Tools registered by this MCP server. */
|
||||||
public List<McpToolView> registeredTools() {
|
public List<McpToolView> registeredTools() {
|
||||||
return List.of(selectAiVpdQueryView());
|
return List.of(selectAiQueryView(), selectAiShowpromptView());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectNode initializeResult(String contextPath) {
|
private ObjectNode initializeResult(String contextPath) {
|
||||||
@@ -88,15 +88,16 @@ public class McpSseService {
|
|||||||
private ObjectNode toolsListResult() {
|
private ObjectNode toolsListResult() {
|
||||||
ObjectNode result = objectMapper.createObjectNode();
|
ObjectNode result = objectMapper.createObjectNode();
|
||||||
ArrayNode tools = objectMapper.createArrayNode();
|
ArrayNode tools = objectMapper.createArrayNode();
|
||||||
tools.add(selectAiVpdQueryTool());
|
tools.add(toolDefinition(selectAiQueryView()));
|
||||||
|
tools.add(toolDefinition(selectAiShowpromptView()));
|
||||||
result.set("tools", tools);
|
result.set("tools", tools);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectNode selectAiVpdQueryTool() {
|
private ObjectNode toolDefinition(McpToolView toolView) {
|
||||||
ObjectNode item = objectMapper.createObjectNode();
|
ObjectNode item = objectMapper.createObjectNode();
|
||||||
item.put("name", toolName());
|
item.put("name", toolView.name());
|
||||||
item.put("description", selectAiVpdQueryView().description());
|
item.put("description", toolView.description());
|
||||||
|
|
||||||
ObjectNode schema = objectMapper.createObjectNode();
|
ObjectNode schema = objectMapper.createObjectNode();
|
||||||
schema.put("type", "object");
|
schema.put("type", "object");
|
||||||
@@ -118,9 +119,11 @@ public class McpSseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ObjectNode toolsCallResult(JsonNode params, String vpdBearerToken) {
|
private ObjectNode toolsCallResult(JsonNode params, String vpdBearerToken) {
|
||||||
String toolName = params.path("name").asText("");
|
String calledToolName = params.path("name").asText("");
|
||||||
if (!toolName().equals(toolName)) {
|
boolean queryTool = toolName().equals(calledToolName);
|
||||||
throw new AppException("등록되지 않은 MCP tool입니다: " + toolName);
|
boolean showpromptTool = showpromptToolName().equals(calledToolName);
|
||||||
|
if (!queryTool && !showpromptTool) {
|
||||||
|
throw new AppException("등록되지 않은 MCP tool입니다: " + calledToolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode arguments = params.path("arguments");
|
JsonNode arguments = params.path("arguments");
|
||||||
@@ -130,15 +133,18 @@ public class McpSseService {
|
|||||||
}
|
}
|
||||||
JsonNode response;
|
JsonNode response;
|
||||||
try {
|
try {
|
||||||
response = selectAiService.generateAndExecute(token, arguments.path("prompt").asText(""));
|
String prompt = arguments.path("prompt").asText("");
|
||||||
|
response = queryTool
|
||||||
|
? selectAiService.generateAndExecute(token, prompt)
|
||||||
|
: selectAiService.generatePrompt(token, prompt);
|
||||||
} catch (VpdTokenAccessDeniedException ignored) {
|
} catch (VpdTokenAccessDeniedException ignored) {
|
||||||
return tokenAccessDeniedResult();
|
return tokenAccessDeniedResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectNode payload = objectMapper.createObjectNode();
|
ObjectNode payload = objectMapper.createObjectNode();
|
||||||
payload.put("toolName", toolName());
|
payload.put("toolName", calledToolName);
|
||||||
payload.put("profile", selectAiProfile());
|
payload.put("profile", selectAiProfile());
|
||||||
payload.put("ordsPath", SELECT_AI_VPD_QUERY_PATH);
|
payload.put("ordsPath", SELECT_AI_TOOL_PATH);
|
||||||
payload.set("response", response);
|
payload.set("response", response);
|
||||||
|
|
||||||
ObjectNode result = objectMapper.createObjectNode();
|
ObjectNode result = objectMapper.createObjectNode();
|
||||||
@@ -168,14 +174,25 @@ public class McpSseService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private McpToolView selectAiVpdQueryView() {
|
private McpToolView selectAiQueryView() {
|
||||||
String profile = selectAiProfile();
|
String profile = selectAiProfile();
|
||||||
return new McpToolView(
|
return new McpToolView(
|
||||||
toolName(),
|
toolName(),
|
||||||
profile + " 프로파일로 " + toolDescription(),
|
profile + " 프로파일로 " + toolDescription(),
|
||||||
-1L,
|
-1L,
|
||||||
toolLabel(),
|
toolLabel(),
|
||||||
SELECT_AI_VPD_QUERY_PATH
|
SELECT_AI_TOOL_PATH
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private McpToolView selectAiShowpromptView() {
|
||||||
|
String profile = selectAiProfile();
|
||||||
|
return new McpToolView(
|
||||||
|
showpromptToolName(),
|
||||||
|
profile + " 프로파일로 " + showpromptToolDescription(),
|
||||||
|
-1L,
|
||||||
|
showpromptToolLabel(),
|
||||||
|
SELECT_AI_TOOL_PATH
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +222,24 @@ public class McpSseService {
|
|||||||
return mcpProperties == null ? "업무 데이터에서 조회할 내용을 자연어로 입력합니다." : mcpProperties.resolvedPromptDescription();
|
return mcpProperties == null ? "업무 데이터에서 조회할 내용을 자연어로 입력합니다." : mcpProperties.resolvedPromptDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String showpromptToolName() {
|
||||||
|
return mcpProperties == null
|
||||||
|
? "oracle.select_ai.data_showprompt"
|
||||||
|
: mcpProperties.resolvedShowpromptToolName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String showpromptToolLabel() {
|
||||||
|
return mcpProperties == null
|
||||||
|
? "업무 데이터 SHOWPROMPT"
|
||||||
|
: mcpProperties.resolvedShowpromptToolLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String showpromptToolDescription() {
|
||||||
|
return mcpProperties == null
|
||||||
|
? "Select AI가 SQL 생성에 사용한 prompt를 조회하는 읽기 전용 진단 도구입니다."
|
||||||
|
: mcpProperties.resolvedShowpromptToolDescription();
|
||||||
|
}
|
||||||
|
|
||||||
private String pretty(Object value) {
|
private String pretty(Object value) {
|
||||||
try {
|
try {
|
||||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
|
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
|
||||||
|
|||||||
@@ -55,14 +55,9 @@ public class SelectAiService {
|
|||||||
public JsonNode generateAndExecute(String bearerToken, String prompt) {
|
public JsonNode generateAndExecute(String bearerToken, String prompt) {
|
||||||
requireActiveToken(bearerToken);
|
requireActiveToken(bearerToken);
|
||||||
String normalizedPrompt = requiredPrompt(prompt);
|
String normalizedPrompt = requiredPrompt(prompt);
|
||||||
BackofficeProperties.SelectAi selectAi = properties == null ? null : properties.selectAi();
|
BackofficeProperties.SelectAi selectAi = requiredSelectAi();
|
||||||
if (selectAi == null || !selectAi.configured()) {
|
|
||||||
throw new AppException("Select AI 연결 설정이 필요합니다. "
|
|
||||||
+ "BACKOFFICE_SELECT_AI_DB_URL, BACKOFFICE_SELECT_AI_DB_USERNAME, "
|
|
||||||
+ "BACKOFFICE_SELECT_AI_DB_PASSWORD를 확인하세요.");
|
|
||||||
}
|
|
||||||
|
|
||||||
String generatedSql = generate(selectAi, normalizedPrompt);
|
String generatedSql = generate(selectAi, normalizedPrompt, "showsql");
|
||||||
String normalizedSql = validateReadOnlySql(generatedSql);
|
String normalizedSql = validateReadOnlySql(generatedSql);
|
||||||
QueryExecution execution = executeReadOnly(selectAi, normalizedSql);
|
QueryExecution execution = executeReadOnly(selectAi, normalizedSql);
|
||||||
ObjectNode response = objectMapper.createObjectNode();
|
ObjectNode response = objectMapper.createObjectNode();
|
||||||
@@ -79,6 +74,30 @@ public class SelectAiService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the prompt Select AI assembled for SQL generation without executing generated SQL. */
|
||||||
|
public JsonNode generatePrompt(String bearerToken, String prompt) {
|
||||||
|
requireActiveToken(bearerToken);
|
||||||
|
String normalizedPrompt = requiredPrompt(prompt);
|
||||||
|
BackofficeProperties.SelectAi selectAi = requiredSelectAi();
|
||||||
|
String selectAiPrompt = generate(selectAi, normalizedPrompt, "showprompt");
|
||||||
|
|
||||||
|
ObjectNode response = objectMapper.createObjectNode();
|
||||||
|
response.put("status", "SHOWPROMPT");
|
||||||
|
response.put("profile", selectAi.profile());
|
||||||
|
response.put("selectAiPrompt", selectAiPrompt);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackofficeProperties.SelectAi requiredSelectAi() {
|
||||||
|
BackofficeProperties.SelectAi selectAi = properties == null ? null : properties.selectAi();
|
||||||
|
if (selectAi == null || !selectAi.configured()) {
|
||||||
|
throw new AppException("Select AI 연결 설정이 필요합니다. "
|
||||||
|
+ "BACKOFFICE_SELECT_AI_DB_URL, BACKOFFICE_SELECT_AI_DB_USERNAME, "
|
||||||
|
+ "BACKOFFICE_SELECT_AI_DB_PASSWORD를 확인하세요.");
|
||||||
|
}
|
||||||
|
return selectAi;
|
||||||
|
}
|
||||||
|
|
||||||
private void requireActiveToken(String bearerToken) {
|
private void requireActiveToken(String bearerToken) {
|
||||||
if (bearerToken == null || bearerToken.isBlank()) {
|
if (bearerToken == null || bearerToken.isBlank()) {
|
||||||
throw new VpdTokenAccessDeniedException();
|
throw new VpdTokenAccessDeniedException();
|
||||||
@@ -101,23 +120,29 @@ public class SelectAiService {
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generate(BackofficeProperties.SelectAi selectAi, String prompt) {
|
private String generate(
|
||||||
String sql = "SELECT DBMS_CLOUD_AI.GENERATE(?, ?, 'showsql') FROM dual";
|
BackofficeProperties.SelectAi selectAi,
|
||||||
|
String prompt,
|
||||||
|
String action
|
||||||
|
) {
|
||||||
|
String sql = "SELECT DBMS_CLOUD_AI.GENERATE(?, ?, ?) FROM dual";
|
||||||
try (Connection connection = DriverManager.getConnection(
|
try (Connection connection = DriverManager.getConnection(
|
||||||
selectAi.dbUrl(), selectAi.dbUsername(), selectAi.dbPassword());
|
selectAi.dbUrl(), selectAi.dbUsername(), selectAi.dbPassword());
|
||||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
statement.setString(1, prompt);
|
statement.setString(1, prompt);
|
||||||
statement.setString(2, selectAi.profile());
|
statement.setString(2, selectAi.profile());
|
||||||
|
statement.setString(3, action);
|
||||||
try (ResultSet resultSet = statement.executeQuery()) {
|
try (ResultSet resultSet = statement.executeQuery()) {
|
||||||
if (!resultSet.next() || resultSet.getString(1) == null) {
|
if (!resultSet.next() || resultSet.getString(1) == null) {
|
||||||
throw new AppException("Select AI가 생성 SQL을 반환하지 않았습니다.");
|
throw new AppException("Select AI가 " + action.toUpperCase() + " 결과를 반환하지 않았습니다.");
|
||||||
}
|
}
|
||||||
return resultSet.getString(1);
|
return resultSet.getString(1);
|
||||||
}
|
}
|
||||||
} catch (AppException exception) {
|
} catch (AppException exception) {
|
||||||
throw exception;
|
throw exception;
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new AppException("Select AI SHOWSQL 생성 실패: " + exception.getMessage());
|
throw new AppException("Select AI " + action.toUpperCase() + " 생성 실패: "
|
||||||
|
+ exception.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ backoffice:
|
|||||||
tool-label: ${BACKOFFICE_MCP_TOOL_LABEL:업무 데이터 Text2SQL}
|
tool-label: ${BACKOFFICE_MCP_TOOL_LABEL:업무 데이터 Text2SQL}
|
||||||
tool-description: ${BACKOFFICE_MCP_TOOL_DESCRIPTION:승인된 업무 데이터용 읽기 전용 SELECT/WITH SQL을 생성하고 검증 후 읽기 전용 트랜잭션에서 실행합니다. 생성 SQL과 최대 100건의 조회 결과를 함께 반환하며 DDL/DML/잠금/패키지 호출은 실행하지 않습니다.}
|
tool-description: ${BACKOFFICE_MCP_TOOL_DESCRIPTION:승인된 업무 데이터용 읽기 전용 SELECT/WITH SQL을 생성하고 검증 후 읽기 전용 트랜잭션에서 실행합니다. 생성 SQL과 최대 100건의 조회 결과를 함께 반환하며 DDL/DML/잠금/패키지 호출은 실행하지 않습니다.}
|
||||||
prompt-description: ${BACKOFFICE_MCP_PROMPT_DESCRIPTION:업무 데이터에서 조회할 내용을 자연어로 입력합니다.}
|
prompt-description: ${BACKOFFICE_MCP_PROMPT_DESCRIPTION:업무 데이터에서 조회할 내용을 자연어로 입력합니다.}
|
||||||
|
showprompt-tool-name: ${BACKOFFICE_MCP_SHOWPROMPT_TOOL_NAME:oracle.select_ai.data_showprompt}
|
||||||
|
showprompt-tool-label: ${BACKOFFICE_MCP_SHOWPROMPT_TOOL_LABEL:업무 데이터 SHOWPROMPT}
|
||||||
|
showprompt-tool-description: ${BACKOFFICE_MCP_SHOWPROMPT_TOOL_DESCRIPTION:Select AI가 SQL 생성에 사용한 prompt를 조회하는 읽기 전용 진단 도구입니다. 생성 SQL이나 데이터 조회 SQL은 실행하지 않습니다.}
|
||||||
masking:
|
masking:
|
||||||
policies: ${BACKOFFICE_MASKING_POLICIES:}
|
policies: ${BACKOFFICE_MASKING_POLICIES:}
|
||||||
security-sql-scripts:
|
security-sql-scripts:
|
||||||
|
|||||||
@@ -26,16 +26,19 @@ class McpSseServiceTest {
|
|||||||
"oracle.select_ai.test_data_text2sql",
|
"oracle.select_ai.test_data_text2sql",
|
||||||
"테스트 데이터 Text2SQL",
|
"테스트 데이터 Text2SQL",
|
||||||
"테스트 데이터용 읽기 전용 SQL을 생성하고 실행합니다.",
|
"테스트 데이터용 읽기 전용 SQL을 생성하고 실행합니다.",
|
||||||
"테스트 데이터의 조회 내용을 입력합니다."
|
"테스트 데이터의 조회 내용을 입력합니다.",
|
||||||
|
"oracle.select_ai.test_data_showprompt",
|
||||||
|
"테스트 데이터 SHOWPROMPT",
|
||||||
|
"테스트 데이터의 Select AI prompt를 조회합니다."
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void listsOnlyVpdSelectAiToolWithPromptInput() {
|
void listsQueryAndShowpromptToolsWithPromptInput() {
|
||||||
ObjectNode response = service.handle("default", request(1, "tools/list"));
|
ObjectNode response = service.handle("default", request(1, "tools/list"));
|
||||||
|
|
||||||
var tools = response.path("result").path("tools");
|
var tools = response.path("result").path("tools");
|
||||||
assertThat(tools).hasSize(1);
|
assertThat(tools).hasSize(2);
|
||||||
var selectAi = tools.get(0);
|
var selectAi = tools.get(0);
|
||||||
assertThat(selectAi.path("name").asText()).isEqualTo("oracle.select_ai.test_data_text2sql");
|
assertThat(selectAi.path("name").asText()).isEqualTo("oracle.select_ai.test_data_text2sql");
|
||||||
assertThat(selectAi.path("description").asText()).contains("SGMP_POC_OCI_GPT54MINI");
|
assertThat(selectAi.path("description").asText()).contains("SGMP_POC_OCI_GPT54MINI");
|
||||||
@@ -45,6 +48,36 @@ class McpSseServiceTest {
|
|||||||
assertThat(selectAi.path("inputSchema").path("properties").has("bearerToken")).isFalse();
|
assertThat(selectAi.path("inputSchema").path("properties").has("bearerToken")).isFalse();
|
||||||
assertThat(selectAi.path("inputSchema").path("properties").has("limit")).isFalse();
|
assertThat(selectAi.path("inputSchema").path("properties").has("limit")).isFalse();
|
||||||
assertThat(selectAi.path("inputSchema").path("properties").has("conversationId")).isFalse();
|
assertThat(selectAi.path("inputSchema").path("properties").has("conversationId")).isFalse();
|
||||||
|
|
||||||
|
var showprompt = tools.get(1);
|
||||||
|
assertThat(showprompt.path("name").asText())
|
||||||
|
.isEqualTo("oracle.select_ai.test_data_showprompt");
|
||||||
|
assertThat(showprompt.path("description").asText())
|
||||||
|
.contains("SGMP_POC_OCI_GPT54MINI")
|
||||||
|
.contains("Select AI prompt");
|
||||||
|
assertThat(showprompt.path("inputSchema").path("required"))
|
||||||
|
.extracting(node -> node.asText())
|
||||||
|
.contains("prompt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void callsShowpromptWithoutExecutingGeneratedSql() {
|
||||||
|
ObjectNode request = request(4, "tools/call");
|
||||||
|
ObjectNode params = (ObjectNode) request.putObject("params");
|
||||||
|
params.put("name", "oracle.select_ai.test_data_showprompt");
|
||||||
|
params.putObject("arguments").put("prompt", "버블리즈 유저 마스터 조회해줘.");
|
||||||
|
|
||||||
|
ObjectNode response = service.handle("default", request, "user-bearer");
|
||||||
|
|
||||||
|
CapturingSelectAiService agentService = (CapturingSelectAiService) selectAiService;
|
||||||
|
assertThat(agentService.bearerToken).isEqualTo("user-bearer");
|
||||||
|
assertThat(agentService.prompt).isEqualTo("버블리즈 유저 마스터 조회해줘.");
|
||||||
|
assertThat(agentService.showpromptCalled).isTrue();
|
||||||
|
assertThat(response.path("result").path("isError").asBoolean()).isFalse();
|
||||||
|
assertThat(response.path("result").path("content").get(0).path("text").asText())
|
||||||
|
.contains("oracle.select_ai.test_data_showprompt")
|
||||||
|
.contains("SHOWPROMPT")
|
||||||
|
.contains("assembled Select AI prompt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -99,6 +132,7 @@ class McpSseServiceTest {
|
|||||||
|
|
||||||
private String bearerToken;
|
private String bearerToken;
|
||||||
private String prompt;
|
private String prompt;
|
||||||
|
private boolean showpromptCalled;
|
||||||
|
|
||||||
private CapturingSelectAiService() {
|
private CapturingSelectAiService() {
|
||||||
super(null, null, null, new ObjectMapper());
|
super(null, null, null, new ObjectMapper());
|
||||||
@@ -117,5 +151,16 @@ class McpSseServiceTest {
|
|||||||
response.putArray("items").addObject().put("RESULT", 1);
|
response.putArray("items").addObject().put("RESULT", 1);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode generatePrompt(String bearerToken, String prompt) {
|
||||||
|
this.bearerToken = bearerToken;
|
||||||
|
this.prompt = prompt;
|
||||||
|
this.showpromptCalled = true;
|
||||||
|
return new ObjectMapper().createObjectNode()
|
||||||
|
.put("profile", "SGMP_POC_OCI_GPT54MINI")
|
||||||
|
.put("status", "SHOWPROMPT")
|
||||||
|
.put("selectAiPrompt", "assembled Select AI prompt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user