ux #463: streamline MCP reasoning flow
This commit is contained in:
@@ -114,7 +114,7 @@ public class McpReasoningService {
|
||||
|
||||
private String buildPrompt(String question, McpToolView tool, String evidenceJson) {
|
||||
String normalizedQuestion = question == null || question.isBlank()
|
||||
? "이 ORDS/VPD 검증 결과를 권한 관점에서 요약해줘."
|
||||
? "요약부터 작성해줘. 이 ORDS/VPD 검증 결과에서 조회 행 수, 주요 식별자, NULL 처리 여부, 권한 범위, 다음 확인 조치를 정리해줘."
|
||||
: question.trim();
|
||||
return """
|
||||
질문:
|
||||
@@ -131,8 +131,11 @@ public class McpReasoningService {
|
||||
답변 요구사항:
|
||||
- 한국어로 답변한다.
|
||||
- 증거 JSON에 없는 데이터는 추측하지 않는다.
|
||||
- 첫 섹션은 "## 요약"으로 시작하고 3줄 이내로 쓴다.
|
||||
- 그 다음 "## 판단 근거" 섹션에 표를 사용해 rowCount, maskedColumns, status, errorCode를 정리한다.
|
||||
- 그 다음 "## 상세" 섹션에서 반환 행과 권한 범위를 설명한다.
|
||||
- 마지막 "## 다음 조치" 섹션은 운영자가 확인할 항목만 짧게 쓴다.
|
||||
- VPD 행 필터, 컬럼 NULL 처리, ORDS 오류 여부를 구분한다.
|
||||
- 운영자가 다음에 확인할 액션이 있으면 짧게 제시한다.
|
||||
""".formatted(normalizedQuestion, tool.name(), tool.displayName(), tool.ordsPath(), evidenceJson);
|
||||
}
|
||||
|
||||
@@ -145,12 +148,53 @@ public class McpReasoningService {
|
||||
|
||||
private String fallbackAnswer(String question, ProbeResult result) {
|
||||
if (result.status() == ProbeStatus.SUCCESS) {
|
||||
return "AI base URL/API Key 설정이 없어 모델 호출은 건너뛰었습니다. ORDS 호출은 성공했고 "
|
||||
+ result.rowCount() + "건이 반환되었습니다. 질문: " + safeQuestion(question);
|
||||
return """
|
||||
## 요약
|
||||
AI base URL/API Key 설정이 없어 모델 호출은 건너뛰었습니다.
|
||||
ORDS 호출은 성공했고 %d건이 반환되었습니다.
|
||||
|
||||
## 판단 근거
|
||||
| 항목 | 값 |
|
||||
| --- | --- |
|
||||
| status | %s |
|
||||
| rowCount | %d |
|
||||
| maskedColumns | %s |
|
||||
|
||||
## 상세
|
||||
질문: %s
|
||||
|
||||
## 다음 조치
|
||||
- 모델 설명이 필요하면 AI 설정을 확인하세요.
|
||||
- 행/컬럼 결과는 아래 Tool Evidence JSON과 Response Body를 기준으로 확인하세요.
|
||||
""".formatted(
|
||||
result.rowCount(),
|
||||
result.status().name(),
|
||||
result.rowCount(),
|
||||
result.maskedColumns().isEmpty() ? "-" : String.join(", ", result.maskedColumns()),
|
||||
safeQuestion(question)
|
||||
);
|
||||
}
|
||||
return "AI base URL/API Key 설정이 없어 모델 호출은 건너뛰었습니다. ORDS 도구 실행 상태는 "
|
||||
+ result.status().name() + "입니다. 오류: "
|
||||
+ (result.errorMessage() == null ? "없음" : result.errorMessage());
|
||||
return """
|
||||
## 요약
|
||||
AI base URL/API Key 설정이 없어 모델 호출은 건너뛰었습니다.
|
||||
ORDS 도구 실행은 성공 상태가 아닙니다.
|
||||
|
||||
## 판단 근거
|
||||
| 항목 | 값 |
|
||||
| --- | --- |
|
||||
| status | %s |
|
||||
| errorMessage | %s |
|
||||
|
||||
## 상세
|
||||
request/response 증거를 확인해 ORDS path, token, handler 오류를 구분하세요.
|
||||
|
||||
## 다음 조치
|
||||
- ORDS path와 token 만료/폐기 상태를 확인하세요.
|
||||
- handler 오류면 Response Body의 ORDS error를 확인하세요.
|
||||
""".formatted(
|
||||
result.status().name(),
|
||||
result.errorMessage() == null ? "없음" : result.errorMessage()
|
||||
);
|
||||
}
|
||||
|
||||
private String safeQuestion(String question) {
|
||||
|
||||
@@ -13,11 +13,14 @@
|
||||
<span>Tool: <code th:text="${result.toolName()}">tool</code></span>
|
||||
<span>ORDS Status: <strong th:text="${result.probeResult().status()}">SUCCESS</strong></span>
|
||||
<span>Rows: <strong th:text="${result.probeResult().rowCount()}">0</strong></span>
|
||||
<span th:if="${!#lists.isEmpty(result.probeResult().maskedColumns())}">
|
||||
Masked: <code th:text="${#strings.listJoin(result.probeResult().maskedColumns(), ', ')}"></code>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<section class="ai-answer">
|
||||
<h3>Answer</h3>
|
||||
<pre th:text="${result.answer()}">answer</pre>
|
||||
<h3>요약 및 판단</h3>
|
||||
<div class="markdown-view" data-markdown-view th:text="${result.answer()}">answer</div>
|
||||
</section>
|
||||
|
||||
<div class="probe-exchange-grid">
|
||||
|
||||
@@ -43,23 +43,23 @@
|
||||
<label class="span-2">
|
||||
질문
|
||||
<textarea class="form-control" id="mcp-reasoning-question" name="question" rows="3"
|
||||
placeholder="비워도 기본 질문으로 실행됩니다."></textarea>
|
||||
placeholder="비워도 조회 행, NULL 처리, 권한 범위, 다음 조치를 요약합니다."></textarea>
|
||||
</label>
|
||||
<div class="question-presets span-2" aria-label="질문 예시">
|
||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
||||
data-question="이 토큰으로 조회 가능한 행 수와 주요 값을 먼저 요약하고, 어떤 VPD 조건이 적용된 결과인지 추정해줘.">
|
||||
조회 결과 요약
|
||||
data-question="요약부터 작성해줘. 이 토큰으로 조회 가능한 행 수, 주요 식별자, NULL 처리 여부, 권한 범위를 표로 정리하고 상세 근거를 이어서 설명해줘.">
|
||||
기본 분석
|
||||
</button>
|
||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
||||
data-question="반환된 행에서 NULL 처리된 컬럼이 있는지 확인하고, 민감 컬럼 권한 관점에서 설명해줘.">
|
||||
NULL 처리 확인
|
||||
data-question="반환된 컬럼과 maskedColumns를 기준으로 민감 컬럼이 NULL 처리 또는 미노출됐는지 먼저 요약하고, role/permission 설정에서 확인할 항목을 정리해줘.">
|
||||
민감 컬럼 점검
|
||||
</button>
|
||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
||||
data-question="이 토큰이 전체 조회 권한인지, 부서/본인/조건 제한 권한인지 결과 증거를 기준으로 판단해줘.">
|
||||
data-question="반환된 행 수와 행의 부서/사번 값을 근거로 이 토큰이 전체 조회, 부서 제한, 본인 제한, 조건 제한 중 어디에 가까운지 판단해줘. 추정이면 추정이라고 표시해줘.">
|
||||
권한 범위 판단
|
||||
</button>
|
||||
<button class="btn rw-btn-secondary question-preset" type="button"
|
||||
data-question="운영자가 확인해야 할 이상 징후나 설정 불일치 가능성을 짧게 정리해줘.">
|
||||
data-question="운영자가 확인해야 할 이상 징후를 먼저 bullet로 요약하고, ORDS path, VPD policy, permission rule, token 상태 중 어디를 봐야 하는지 제시해줘.">
|
||||
운영 점검 요약
|
||||
</button>
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@
|
||||
<td>
|
||||
<button class="btn btn-sm rw-btn-secondary reasoning-object-preset"
|
||||
type="button"
|
||||
th:attr="data-object-id=${tool.objectId()},data-question=${tool.displayName() + '에서 이 토큰으로 조회 가능한 행과 NULL 처리 컬럼을 요약해줘.'}">
|
||||
th:attr="data-object-id=${tool.objectId()},data-question=${tool.displayName() + '에서 이 토큰으로 조회 가능한 행, NULL 처리 컬럼, 권한 범위를 요약 먼저 표로 정리해줘.'}">
|
||||
선택
|
||||
</button>
|
||||
</td>
|
||||
@@ -104,7 +104,7 @@
|
||||
</section>
|
||||
|
||||
<section id="mcp-result" class="content-band">
|
||||
<div class="text-muted">Reasoning 결과가 여기에 표시됩니다.</div>
|
||||
<div class="text-muted">실행하면 ORDS 호출 결과, 모델 요약, request/response 증거가 한 번에 표시됩니다.</div>
|
||||
</section>
|
||||
</main>
|
||||
<script>
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
</div>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<h2>ORDS 호출</h2>
|
||||
<a class="btn btn-sm rw-btn-secondary" href="/mcp-reasoning">MCP Reasoning으로 해석</a>
|
||||
</div>
|
||||
<form hx-post="/probe" hx-target="#probe-result" hx-swap="innerHTML" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
|
||||
Reference in New Issue
Block a user