[Developer] #424 use GPT 5.4 mini for VPD explanations

This commit is contained in:
devmrko
2026-06-25 13:31:01 +09:00
parent 20978fa48d
commit f1e20f2769
5 changed files with 25 additions and 6 deletions

View File

@@ -32,7 +32,7 @@ export BACKOFFICE_ORDS_TIMEOUT_SECONDS="10"
# --- (2c) OpenAI 호환 AI 호출 (MCP-style Reasoning 탭) ---
export BACKOFFICE_AI_ENABLED="false"
export BACKOFFICE_AI_BASE_URL="" # 예: https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
export BACKOFFICE_AI_MODEL="" # 예: meta.llama-3.3-70b-instruct
export BACKOFFICE_AI_MODEL="" # 예: openai.gpt-5.4-mini
export BACKOFFICE_AI_API_KEY=""
export BACKOFFICE_AI_TIMEOUT_SECONDS="30"

View File

@@ -2,6 +2,7 @@ package com.cloudhandson.vpdbackoffice.domain.vpd;
public record VpdPolicyExplanation(
String status,
String modelName,
String answer,
String prompt,
VpdPolicyDetail detail,

View File

@@ -41,6 +41,11 @@ public class OpenAiCompatibleClient {
&& hasText(ai.apiKey());
}
public String modelName() {
BackofficeProperties.Ai ai = properties.ai();
return ai == null ? "" : ai.model();
}
public String chat(String systemPrompt, String userPrompt) {
if (!configured()) {
throw new AppException("AI 호출 설정이 없습니다.");
@@ -50,7 +55,11 @@ public class OpenAiCompatibleClient {
ObjectNode request = objectMapper.createObjectNode();
request.put("model", ai.model());
request.put("temperature", 0.1);
request.put("max_tokens", 1200);
if (usesCompletionTokenLimit(ai.model())) {
request.put("max_completion_tokens", 1200);
} else {
request.put("max_tokens", 1200);
}
ArrayNode messages = request.putArray("messages");
messages.add(message("system", systemPrompt));
messages.add(message("user", userPrompt));
@@ -91,6 +100,10 @@ public class OpenAiCompatibleClient {
return URI.create(withoutSlash + "/v1/chat/completions");
}
private boolean usesCompletionTokenLimit(String model) {
return model != null && model.startsWith("openai.gpt-5.4");
}
private String extractAnswer(String body) {
try {
JsonNode root = objectMapper.readTree(body);

View File

@@ -57,6 +57,7 @@ public class VpdPolicyService {
if (!aiClient.configured()) {
return new VpdPolicyExplanation(
"AI_NOT_CONFIGURED",
aiClient.modelName(),
"AI base URL/API Key 설정이 없어 모델 호출은 건너뛰었습니다. 아래 Prompt와 function source를 기준으로 policy/filter 내용을 확인하세요.",
prompt,
detail,
@@ -65,10 +66,11 @@ public class VpdPolicyService {
}
try {
String answer = aiClient.chat(vpdSystemPrompt(), prompt);
return new VpdPolicyExplanation("SUCCESS", answer, prompt, detail, functionSource);
return new VpdPolicyExplanation("SUCCESS", aiClient.modelName(), answer, prompt, detail, functionSource);
} catch (Exception e) {
return new VpdPolicyExplanation(
"AI_CALL_FAILED",
aiClient.modelName(),
"AI 호출은 실패했지만 policy/filter 근거는 수집했습니다. 상세: " + e.getMessage(),
prompt,
detail,

View File

@@ -6,9 +6,12 @@
<div th:if="${explanation}">
<div class="section-heading mb-2">
<h2>LLM 설명</h2>
<span class="badge"
th:classappend="${explanation.status() == 'SUCCESS'} ? ' text-bg-success' : ' text-bg-warning'"
th:text="${explanation.status()}">SUCCESS</span>
<div class="action-stack">
<span class="badge text-bg-secondary" th:text="${explanation.modelName()} ?: 'model not configured'">model</span>
<span class="badge"
th:classappend="${explanation.status() == 'SUCCESS'} ? ' text-bg-success' : ' text-bg-warning'"
th:text="${explanation.status()}">SUCCESS</span>
</div>
</div>
<section class="ai-answer">
<h3>Answer</h3>