GenAI: chat 모델 OpenAI(gpt-5.6) 호환 + 노트 하단 사용모델 표기

- OciGenAiService.chat(): 모델 인지 분기. openai.* 모델은 maxTokens/
  temperature(0.3)를 거부하므로 maxCompletionTokens 사용 + temperature 생략.
  gemini/cohere 등 기존 모델은 maxTokens+temperature 유지(하위호환).
- NoteController: 노트 최종 결과 하단에 사용 모델 푸터 표기
  (음성변환 모델 + 교정·요약 모델). 모델명은 설정값에서 조회해 자동 반영.
- STT(음성→텍스트)는 OpenRouter Gemini 유지, 변경 없음.
- OCI_GENAI_MODEL은 .env에서 openai.gpt-5.6-terra로 전환(커밋 제외).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 07:34:55 +00:00
parent 446969b862
commit e1e71dc954
2 changed files with 35 additions and 12 deletions

View File

@@ -133,7 +133,8 @@ public class NoteController {
noteRepository.updateContent(id, polished + "\n\n--- 요약 생성 중... ---");
String summary = summarizeTranscription(polished);
String result = "# 요약\n\n" + summary + "\n\n---\n\n# 전문\n\n" + polished;
String result = "# 요약\n\n" + summary + "\n\n---\n\n# 전문\n\n" + polished
+ modelFooter(null);
String newTitle = generateAudioTitle(summary, java.time.LocalDateTime.now());
noteRepository.update(id, null, newTitle, result, null);
noteRepository.updateNoteType(id, isAudio ? "AUDIO" : "TEXT");
@@ -202,11 +203,13 @@ public class NoteController {
private void transcribeAsync(String noteId, Path audioFile, String inputTitle) throws IOException, InterruptedException {
// === Step 1: STT (OpenRouter Gemini) ===
String rawResult = null;
String sttModel = null;
if (openRouterApiKey != null && !openRouterApiKey.isBlank()) {
try {
noteRepository.updateContent(noteId, "Gemini로 음성 변환 중...");
rawResult = transcribeWithOpenRouter(audioFile);
sttModel = openRouterModel;
log.info("OpenRouter STT: {} chars", rawResult != null ? rawResult.length() : 0);
} catch (Exception e) {
log.warn("OpenRouter STT failed: {}", e.getMessage());
@@ -219,6 +222,7 @@ public class NoteController {
try {
noteRepository.updateContent(noteId, "Gemma로 음성 변환 중...");
rawResult = transcribeWithGemma(audioFile);
sttModel = "gemma4:e4b";
} catch (Exception e) {
log.error("All STT failed for note {}", noteId, e);
noteRepository.updateContent(noteId, "모든 음성 변환 실패: " + e.getMessage());
@@ -257,7 +261,8 @@ public class NoteController {
log.info("Summary complete: {} chars", summary.length());
// 최종 결과 저장
String result = "# 요약\n\n" + summary + "\n\n---\n\n# 전문\n\n" + polished;
String result = "# 요약\n\n" + summary + "\n\n---\n\n# 전문\n\n" + polished
+ modelFooter(sttModel);
String finalTitle = inputTitle.equals("음성 변환 중...")
? generateAudioTitle(summary, java.time.LocalDateTime.now())
: inputTitle;
@@ -271,8 +276,18 @@ public class NoteController {
}
/**
* Gemma 4 E4B를 사용하여 오디오 파일을 텍스트로 변환
* 노트 하단에 사용 모델을 표기하는 푸터를 만든다.
* sttModel이 있으면 음성변환 모델도 함께 표기하고, 없으면 교정·요약 모델만 표기한다.
* 모델명은 genAiService.getDefaultModel()/openRouterModel에서 가져오므로 설정 변경 시 자동 반영된다.
*/
private String modelFooter(String sttModel) {
String polishModel = genAiService.getDefaultModel();
if (sttModel != null && !sttModel.isBlank()) {
return "\n\n---\n\n*🤖 음성변환: " + sttModel + " · 교정·요약: " + polishModel + "*";
}
return "\n\n---\n\n*🤖 교정·요약: " + polishModel + "*";
}
/**
* 오디오 파일을 wav로 변환한다 (Ollama 호환성).
*/

View File

@@ -13,6 +13,7 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -84,21 +85,28 @@ public class OciGenAiService {
modelId = defaultModel;
}
Map<String, Object> chatRequest = new LinkedHashMap<>();
chatRequest.put("apiFormat", "GENERIC");
chatRequest.put("messages", List.of(
Map.of("role", "SYSTEM", "content", List.of(Map.of("type", "TEXT", "text", systemMessage))),
Map.of("role", "USER", "content", List.of(Map.of("type", "TEXT", "text", userMessage)))
));
// OpenAI 계열(openai.gpt-5.x 등)은 maxTokens / temperature(0.3)를 거부한다.
// maxCompletionTokens를 사용하고, temperature는 기본값(1)만 허용되므로 생략한다.
if (modelId.startsWith("openai.")) {
chatRequest.put("maxCompletionTokens", 65536);
} else {
chatRequest.put("maxTokens", 65536);
chatRequest.put("temperature", 0.3);
}
Map<String, Object> payload = Map.of(
"compartmentId", compartment,
"servingMode", Map.of(
"servingType", "ON_DEMAND",
"modelId", modelId
),
"chatRequest", Map.of(
"apiFormat", "GENERIC",
"messages", List.of(
Map.of("role", "SYSTEM", "content", List.of(Map.of("type", "TEXT", "text", systemMessage))),
Map.of("role", "USER", "content", List.of(Map.of("type", "TEXT", "text", userMessage)))
),
"maxTokens", 65536,
"temperature", 0.3
)
"chatRequest", chatRequest
);
String body = objectMapper.writeValueAsString(payload);