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:
@@ -133,7 +133,8 @@ public class NoteController {
|
|||||||
noteRepository.updateContent(id, polished + "\n\n--- 요약 생성 중... ---");
|
noteRepository.updateContent(id, polished + "\n\n--- 요약 생성 중... ---");
|
||||||
String summary = summarizeTranscription(polished);
|
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());
|
String newTitle = generateAudioTitle(summary, java.time.LocalDateTime.now());
|
||||||
noteRepository.update(id, null, newTitle, result, null);
|
noteRepository.update(id, null, newTitle, result, null);
|
||||||
noteRepository.updateNoteType(id, isAudio ? "AUDIO" : "TEXT");
|
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 {
|
private void transcribeAsync(String noteId, Path audioFile, String inputTitle) throws IOException, InterruptedException {
|
||||||
// === Step 1: STT (OpenRouter Gemini) ===
|
// === Step 1: STT (OpenRouter Gemini) ===
|
||||||
String rawResult = null;
|
String rawResult = null;
|
||||||
|
String sttModel = null;
|
||||||
|
|
||||||
if (openRouterApiKey != null && !openRouterApiKey.isBlank()) {
|
if (openRouterApiKey != null && !openRouterApiKey.isBlank()) {
|
||||||
try {
|
try {
|
||||||
noteRepository.updateContent(noteId, "Gemini로 음성 변환 중...");
|
noteRepository.updateContent(noteId, "Gemini로 음성 변환 중...");
|
||||||
rawResult = transcribeWithOpenRouter(audioFile);
|
rawResult = transcribeWithOpenRouter(audioFile);
|
||||||
|
sttModel = openRouterModel;
|
||||||
log.info("OpenRouter STT: {} chars", rawResult != null ? rawResult.length() : 0);
|
log.info("OpenRouter STT: {} chars", rawResult != null ? rawResult.length() : 0);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("OpenRouter STT failed: {}", e.getMessage());
|
log.warn("OpenRouter STT failed: {}", e.getMessage());
|
||||||
@@ -219,6 +222,7 @@ public class NoteController {
|
|||||||
try {
|
try {
|
||||||
noteRepository.updateContent(noteId, "Gemma로 음성 변환 중...");
|
noteRepository.updateContent(noteId, "Gemma로 음성 변환 중...");
|
||||||
rawResult = transcribeWithGemma(audioFile);
|
rawResult = transcribeWithGemma(audioFile);
|
||||||
|
sttModel = "gemma4:e4b";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("All STT failed for note {}", noteId, e);
|
log.error("All STT failed for note {}", noteId, e);
|
||||||
noteRepository.updateContent(noteId, "모든 음성 변환 실패: " + e.getMessage());
|
noteRepository.updateContent(noteId, "모든 음성 변환 실패: " + e.getMessage());
|
||||||
@@ -257,7 +261,8 @@ public class NoteController {
|
|||||||
log.info("Summary complete: {} chars", summary.length());
|
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("음성 변환 중...")
|
String finalTitle = inputTitle.equals("음성 변환 중...")
|
||||||
? generateAudioTitle(summary, java.time.LocalDateTime.now())
|
? generateAudioTitle(summary, java.time.LocalDateTime.now())
|
||||||
: inputTitle;
|
: 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 호환성).
|
* 오디오 파일을 wav로 변환한다 (Ollama 호환성).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.net.http.HttpRequest;
|
|||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -84,21 +85,28 @@ public class OciGenAiService {
|
|||||||
modelId = defaultModel;
|
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(
|
Map<String, Object> payload = Map.of(
|
||||||
"compartmentId", compartment,
|
"compartmentId", compartment,
|
||||||
"servingMode", Map.of(
|
"servingMode", Map.of(
|
||||||
"servingType", "ON_DEMAND",
|
"servingType", "ON_DEMAND",
|
||||||
"modelId", modelId
|
"modelId", modelId
|
||||||
),
|
),
|
||||||
"chatRequest", Map.of(
|
"chatRequest", chatRequest
|
||||||
"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
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
String body = objectMapper.writeValueAsString(payload);
|
String body = objectMapper.writeValueAsString(payload);
|
||||||
|
|||||||
Reference in New Issue
Block a user