From 9f9d8a66c53ee37aafb96aeeb0f265db72f994a3 Mon Sep 17 00:00:00 2001 From: devmrko Date: Thu, 25 Jun 2026 13:03:37 +0900 Subject: [PATCH] [Developer] #424 add LLM explanation for VPD policies --- .../domain/vpd/VpdPolicyExplanation.java | 10 +++ .../service/VpdPolicyService.java | 89 ++++++++++++++++++- .../web/VpdPolicyController.java | 18 ++++ .../fragments/vpd-policy-explanation.html | 31 +++++++ .../resources/templates/vpd-policies.html | 16 +++- 5 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java create mode 100644 src/main/resources/templates/fragments/vpd-policy-explanation.html diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java b/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java new file mode 100644 index 0000000..2273d12 --- /dev/null +++ b/src/main/java/com/cloudhandson/vpdbackoffice/domain/vpd/VpdPolicyExplanation.java @@ -0,0 +1,10 @@ +package com.cloudhandson.vpdbackoffice.domain.vpd; + +public record VpdPolicyExplanation( + String status, + String answer, + String prompt, + VpdPolicyDetail detail, + VpdFunctionSource functionSource +) { +} diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java index 51a7aae..6bb9163 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/VpdPolicyService.java @@ -2,6 +2,7 @@ package com.cloudhandson.vpdbackoffice.service; import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionSource; import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyDetail; +import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyExplanation; import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView; import com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper; import java.util.List; @@ -12,9 +13,11 @@ import org.springframework.stereotype.Service; public class VpdPolicyService { private final VpdPolicyMapper mapper; + private final OpenAiCompatibleClient aiClient; - public VpdPolicyService(VpdPolicyMapper mapper) { + public VpdPolicyService(VpdPolicyMapper mapper, OpenAiCompatibleClient aiClient) { this.mapper = mapper; + this.aiClient = aiClient; } public List findPolicies() { @@ -42,6 +45,90 @@ public class VpdPolicyService { return new VpdPolicyDetail(policy, buildAddPolicyBlock(policy)); } + public VpdPolicyExplanation explainPolicy(String objectOwner, String objectName, String policyName) { + VpdPolicyDetail detail = findPolicyDetail(objectOwner, objectName, policyName); + VpdPolicyView policy = detail.policy(); + VpdFunctionSource functionSource = findFunctionSource( + policy.functionOwner(), + policy.packageName(), + policy.functionName() + ); + String prompt = buildExplanationPrompt(detail, functionSource); + if (!aiClient.configured()) { + return new VpdPolicyExplanation( + "AI_NOT_CONFIGURED", + "AI base URL/API Key 설정이 없어 모델 호출은 건너뛰었습니다. 아래 Prompt와 function source를 기준으로 policy/filter 내용을 확인하세요.", + prompt, + detail, + functionSource + ); + } + try { + String answer = aiClient.chat(vpdSystemPrompt(), prompt); + return new VpdPolicyExplanation("SUCCESS", answer, prompt, detail, functionSource); + } catch (Exception e) { + return new VpdPolicyExplanation( + "AI_CALL_FAILED", + "AI 호출은 실패했지만 policy/filter 근거는 수집했습니다. 상세: " + e.getMessage(), + prompt, + detail, + functionSource + ); + } + } + + private String buildExplanationPrompt(VpdPolicyDetail detail, VpdFunctionSource functionSource) { + VpdPolicyView policy = detail.policy(); + return """ + 다음 Oracle VPD policy와 policy function source를 근거로 설명해줘. + + Policy Metadata: + - Object: %s + - Policy Group: %s + - Policy Name: %s + - Function: %s + - Statement Types: %s + - Enabled: %s + - Policy Type: %s + - Check Option: %s + - Static Policy: %s + - Long Predicate: %s + + DBMS_RLS.ADD_POLICY: + %s + + Policy Function Source: + %s + + 답변 요구사항: + - 한국어로 답변한다. + - 이 policy가 어느 객체의 어떤 SQL 동작에 적용되는지 설명한다. + - filter predicate가 어떤 조건을 만들고 어떤 행이 보이거나 제외되는지 설명한다. + - source에 명시되지 않은 동작은 추측하지 않는다. + - 운영자가 확인할 포인트를 짧게 정리한다. + """.formatted( + policy.objectDisplayName(), + policy.policyGroup(), + policy.policyName(), + policy.functionDisplayName(), + blankToDefault(policy.statementTypes(), "-"), + policy.enabled(), + policy.policyType(), + policy.checkOption(), + policy.staticPolicy(), + policy.longPredicate(), + detail.ddl(), + functionSource.found() ? functionSource.source() : "ALL_SOURCE에서 조회 가능한 source가 없습니다." + ); + } + + private String vpdSystemPrompt() { + return """ + 당신은 Oracle ADB VPD(DBMS_RLS), RLS policy function, ORDS 권한 검증을 설명하는 보조자입니다. + 제공된 policy metadata와 source만 근거로 설명하고, 없는 정보를 추측하지 마세요. + """; + } + private String buildAddPolicyBlock(VpdPolicyView policy) { return """ BEGIN diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java b/src/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java index 4bd374d..20dfe07 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/web/VpdPolicyController.java @@ -65,4 +65,22 @@ public class VpdPolicyController { } return "fragments/vpd-policy-detail :: detail"; } + + @GetMapping("/vpd-policies/policy-explanation") + public String policyExplanation( + @RequestParam String objectOwner, + @RequestParam String objectName, + @RequestParam String policyName, + Model model + ) { + try { + model.addAttribute("explanation", vpdPolicyService.explainPolicy(objectOwner, objectName, policyName)); + } catch (AppException exception) { + model.addAttribute("errorMessage", exception.getMessage()); + } catch (DataAccessException exception) { + RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception); + model.addAttribute("errorMessage", message.message()); + } + return "fragments/vpd-policy-explanation :: explanation"; + } } diff --git a/src/main/resources/templates/fragments/vpd-policy-explanation.html b/src/main/resources/templates/fragments/vpd-policy-explanation.html new file mode 100644 index 0000000..4e5a0f5 --- /dev/null +++ b/src/main/resources/templates/fragments/vpd-policy-explanation.html @@ -0,0 +1,31 @@ + + + +
+
+
+
+

LLM 설명

+ SUCCESS +
+
+

Answer

+
answer
+
+
+
+

Policy / Filter Prompt

+
prompt
+
+
+

Policy Function Source

+

+        
ALL_SOURCE에서 조회 가능한 source가 없습니다.
+
+
+
+
+ + diff --git a/src/main/resources/templates/vpd-policies.html b/src/main/resources/templates/vpd-policies.html index d65ef32..9ddce0c 100644 --- a/src/main/resources/templates/vpd-policies.html +++ b/src/main/resources/templates/vpd-policies.html @@ -31,6 +31,7 @@ Status Type Options + Explain @@ -70,17 +71,26 @@ Static: NO Long: NO + + + - +
- Policy 또는 policy function을 클릭하면 상세 내역이 표시됩니다. + Policy, policy function, LLM 설명을 클릭하면 상세 내역이 표시됩니다.
- + 등록된 보호 객체에 적용된 VPD policy가 없습니다. 테이블/뷰 관리의 보호 객체 등록 상태와 DB policy 적용 상태를 확인하세요.