[Developer] #424 use GPT 5.4 mini for VPD explanations
This commit is contained in:
@@ -32,7 +32,7 @@ export BACKOFFICE_ORDS_TIMEOUT_SECONDS="10"
|
|||||||
# --- (2c) OpenAI 호환 AI 호출 (MCP-style Reasoning 탭) ---
|
# --- (2c) OpenAI 호환 AI 호출 (MCP-style Reasoning 탭) ---
|
||||||
export BACKOFFICE_AI_ENABLED="false"
|
export BACKOFFICE_AI_ENABLED="false"
|
||||||
export BACKOFFICE_AI_BASE_URL="" # 예: https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
|
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_API_KEY=""
|
||||||
export BACKOFFICE_AI_TIMEOUT_SECONDS="30"
|
export BACKOFFICE_AI_TIMEOUT_SECONDS="30"
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cloudhandson.vpdbackoffice.domain.vpd;
|
|||||||
|
|
||||||
public record VpdPolicyExplanation(
|
public record VpdPolicyExplanation(
|
||||||
String status,
|
String status,
|
||||||
|
String modelName,
|
||||||
String answer,
|
String answer,
|
||||||
String prompt,
|
String prompt,
|
||||||
VpdPolicyDetail detail,
|
VpdPolicyDetail detail,
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ public class OpenAiCompatibleClient {
|
|||||||
&& hasText(ai.apiKey());
|
&& hasText(ai.apiKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String modelName() {
|
||||||
|
BackofficeProperties.Ai ai = properties.ai();
|
||||||
|
return ai == null ? "" : ai.model();
|
||||||
|
}
|
||||||
|
|
||||||
public String chat(String systemPrompt, String userPrompt) {
|
public String chat(String systemPrompt, String userPrompt) {
|
||||||
if (!configured()) {
|
if (!configured()) {
|
||||||
throw new AppException("AI 호출 설정이 없습니다.");
|
throw new AppException("AI 호출 설정이 없습니다.");
|
||||||
@@ -50,7 +55,11 @@ public class OpenAiCompatibleClient {
|
|||||||
ObjectNode request = objectMapper.createObjectNode();
|
ObjectNode request = objectMapper.createObjectNode();
|
||||||
request.put("model", ai.model());
|
request.put("model", ai.model());
|
||||||
request.put("temperature", 0.1);
|
request.put("temperature", 0.1);
|
||||||
|
if (usesCompletionTokenLimit(ai.model())) {
|
||||||
|
request.put("max_completion_tokens", 1200);
|
||||||
|
} else {
|
||||||
request.put("max_tokens", 1200);
|
request.put("max_tokens", 1200);
|
||||||
|
}
|
||||||
ArrayNode messages = request.putArray("messages");
|
ArrayNode messages = request.putArray("messages");
|
||||||
messages.add(message("system", systemPrompt));
|
messages.add(message("system", systemPrompt));
|
||||||
messages.add(message("user", userPrompt));
|
messages.add(message("user", userPrompt));
|
||||||
@@ -91,6 +100,10 @@ public class OpenAiCompatibleClient {
|
|||||||
return URI.create(withoutSlash + "/v1/chat/completions");
|
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) {
|
private String extractAnswer(String body) {
|
||||||
try {
|
try {
|
||||||
JsonNode root = objectMapper.readTree(body);
|
JsonNode root = objectMapper.readTree(body);
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public class VpdPolicyService {
|
|||||||
if (!aiClient.configured()) {
|
if (!aiClient.configured()) {
|
||||||
return new VpdPolicyExplanation(
|
return new VpdPolicyExplanation(
|
||||||
"AI_NOT_CONFIGURED",
|
"AI_NOT_CONFIGURED",
|
||||||
|
aiClient.modelName(),
|
||||||
"AI base URL/API Key 설정이 없어 모델 호출은 건너뛰었습니다. 아래 Prompt와 function source를 기준으로 policy/filter 내용을 확인하세요.",
|
"AI base URL/API Key 설정이 없어 모델 호출은 건너뛰었습니다. 아래 Prompt와 function source를 기준으로 policy/filter 내용을 확인하세요.",
|
||||||
prompt,
|
prompt,
|
||||||
detail,
|
detail,
|
||||||
@@ -65,10 +66,11 @@ public class VpdPolicyService {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String answer = aiClient.chat(vpdSystemPrompt(), prompt);
|
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) {
|
} catch (Exception e) {
|
||||||
return new VpdPolicyExplanation(
|
return new VpdPolicyExplanation(
|
||||||
"AI_CALL_FAILED",
|
"AI_CALL_FAILED",
|
||||||
|
aiClient.modelName(),
|
||||||
"AI 호출은 실패했지만 policy/filter 근거는 수집했습니다. 상세: " + e.getMessage(),
|
"AI 호출은 실패했지만 policy/filter 근거는 수집했습니다. 상세: " + e.getMessage(),
|
||||||
prompt,
|
prompt,
|
||||||
detail,
|
detail,
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
<div th:if="${explanation}">
|
<div th:if="${explanation}">
|
||||||
<div class="section-heading mb-2">
|
<div class="section-heading mb-2">
|
||||||
<h2>LLM 설명</h2>
|
<h2>LLM 설명</h2>
|
||||||
|
<div class="action-stack">
|
||||||
|
<span class="badge text-bg-secondary" th:text="${explanation.modelName()} ?: 'model not configured'">model</span>
|
||||||
<span class="badge"
|
<span class="badge"
|
||||||
th:classappend="${explanation.status() == 'SUCCESS'} ? ' text-bg-success' : ' text-bg-warning'"
|
th:classappend="${explanation.status() == 'SUCCESS'} ? ' text-bg-success' : ' text-bg-warning'"
|
||||||
th:text="${explanation.status()}">SUCCESS</span>
|
th:text="${explanation.status()}">SUCCESS</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<section class="ai-answer">
|
<section class="ai-answer">
|
||||||
<h3>Answer</h3>
|
<h3>Answer</h3>
|
||||||
<pre th:text="${explanation.answer()}">answer</pre>
|
<pre th:text="${explanation.answer()}">answer</pre>
|
||||||
|
|||||||
Reference in New Issue
Block a user