feat: OpenRouter 실존 무료 모델 교체 및 Gemini API 라우팅 전환
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 2m9s

- chat.html: 드롭다운을 검증된 무료 모델로 교체
  * Ling 3.0 Flash (inclusionai/ling-3.0-flash:free) 기본값으로 설정
  * GPT-OSS 20B, Gemma 4 31B, NVIDIA Nemotron Nano, Cohere North Mini 추가
  * 삭제된 모델(qwen-2-7b, llama-3-8b, openchat-7b 등) 제거
- ChatController.java: Gemini 직접 API(비정상 키) 제거
  * gemini-flash-latest 선택 시 OpenRouter Google Gemma 4 31B 로 라우팅
  * 불필요한 70여 라인 직접 HTTP 호출 코드 제거
- E2E 검증 완료: Ling 3.0 Flash 정상 응답 확인
This commit is contained in:
Gitea CI
2026-07-30 07:28:56 +09:00
parent 68e1bf95fd
commit 2c4c447a8f
2 changed files with 8 additions and 58 deletions

View File

@@ -79,60 +79,10 @@ public class ChatController {
selectedModel = "gemini-flash-latest"; selectedModel = "gemini-flash-latest";
} }
// 1. Google Gemini 무료 티어 직접 API 연동 (사용량 및 컴파일 충돌 차단용 Short-circuit) // 1. gemini-flash-latest 선택 시 OpenRouter의 Google Gemma 4 모델로 라우팅
if (selectedModel.equals("gemini-flash-latest")) { if (selectedModel.equals("gemini-flash-latest")) {
String geminiKey = "AQ.Ab8RN6KFZggsQf8iooY1v_3h3vp2TIjiYB54dV4Yay3vVKEMtg"; selectedModel = "google/gemma-4-31b-it:free";
String geminiUrl = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions"; log.info("[Real AI Chat] Gemini 직접 API → OpenRouter Google Gemma 4 31B 로 라우팅 전환");
Map<String, Object> body = new HashMap<>();
body.put("model", "gemini-1.5-flash");
body.put("stream", true);
body.put("messages", List.of(Map.of("role", "user", "content", message)));
try {
java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient();
java.net.http.HttpRequest httpRequest = java.net.http.HttpRequest.newBuilder()
.uri(java.net.URI.create(geminiUrl))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + geminiKey)
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))
.build();
httpClient.sendAsync(httpRequest, java.net.http.HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> {
response.body().forEach(line -> {
try {
if (line.startsWith("data: ")) {
String jsonStr = line.substring(6).trim();
if (!jsonStr.equals("[DONE]")) {
JsonNode node = objectMapper.readTree(jsonStr);
String text = node.path("choices").path(0).path("delta").path("content").asText("");
if (!text.isEmpty()) {
emitter.send(text);
}
}
}
} catch (Exception e) {
log.error("Gemini stream parse error: {}", e.getMessage());
}
});
emitter.complete();
})
.exceptionally(ex -> {
log.error("Gemini stream connection failed", ex);
try {
emitter.send("\n\n⚠ **Gemini API 호출에 실패했습니다.** (" + ex.getMessage() + ")");
} catch (Exception ignored) {}
emitter.completeWithError(ex);
return null;
});
log.info("[Real AI Chat] 구글 제미나이 다이렉트 API 스트리밍 개시 완료!");
return emitter;
} catch (Exception e) {
log.error("Gemini direct API setup error", e);
emitter.completeWithError(e);
return emitter;
}
} }
// 2. OpenRouter 무료 모델 처리 (Spring AI 빌드된 ChatClient 및 MCP 툴 호출 완벽 지원!) // 2. OpenRouter 무료 모델 처리 (Spring AI 빌드된 ChatClient 및 MCP 툴 호출 완벽 지원!)
@@ -167,7 +117,7 @@ public class ChatController {
} else if (errorMsg != null && errorMsg.contains("503")) { } else if (errorMsg != null && errorMsg.contains("503")) {
emitter.send("\n\n⚠ **AI 모델 서버 혼잡 (503)**: 현재 AI 모델을 제공하는 서버에 일시적으로 접속자가 많아 지연이 발생하고 있습니다. 잠시 후 다시 시도해 주세요."); emitter.send("\n\n⚠ **AI 모델 서버 혼잡 (503)**: 현재 AI 모델을 제공하는 서버에 일시적으로 접속자가 많아 지연이 발생하고 있습니다. 잠시 후 다시 시도해 주세요.");
} else if (errorMsg != null && errorMsg.contains("429")) { } else if (errorMsg != null && errorMsg.contains("429")) {
emitter.send("\n\n⚠ **API 사용량 초과 (429)**: 현재 사용 중인 Gemini API(무료 티어)의 일일 또는 분당 요청 한도를 초과했습니다. 잠시 후 다시 시도하시거나 API 플랜을 확인해 주세요."); emitter.send("\n\n⚠ **API 사용량 초과 (429)**: AI 모델 API(무료 티어 또는 오픈라우터)의 요청 한도를 초과했습니다. 잠시 후 다시 시도하시거나 API 키의 유효 한도를 확인해 주세요.");
} else { } else {
emitter.send("\n[에러 발생: " + errorMsg + "]"); emitter.send("\n[에러 발생: " + errorMsg + "]");
} }

View File

@@ -44,13 +44,13 @@
</div> </div>
</div> </div>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<!-- 모델 선택 Select Box 신설 -->
<select id="model-select" class="bg-[#1e2128] text-slate-300 text-xs px-3 py-1.5 rounded-lg border border-white/10 focus:outline-none focus:border-emerald-500/50 cursor-pointer"> <select id="model-select" class="bg-[#1e2128] text-slate-300 text-xs px-3 py-1.5 rounded-lg border border-white/10 focus:outline-none focus:border-emerald-500/50 cursor-pointer">
<option value="gemini-flash-latest">Gemini 1.5 Flash (기본 - Google)</option> <option value="inclusionai/ling-3.0-flash:free" selected>Ling 3.0 Flash (기본 - OpenRouter)</option>
<option value="google/gemma-4-31b-it:free">Gemma 4 31B (무료 - OpenRouter)</option>
<option value="inclusionai/ling-3.0-flash:free">Ling 3.0 Flash (무료 - OpenRouter)</option>
<option value="openai/gpt-oss-20b:free">GPT-OSS 20B (무료 - OpenRouter)</option> <option value="openai/gpt-oss-20b:free">GPT-OSS 20B (무료 - OpenRouter)</option>
<option value="google/gemma-4-31b-it:free">Gemma 4 31B (무료 - OpenRouter)</option>
<option value="nvidia/nemotron-3-nano-30b-a3b:free">NVIDIA Nemotron Nano 30B (무료 - OpenRouter)</option>
<option value="cohere/north-mini-code:free">Cohere North Mini (무료 - OpenRouter)</option> <option value="cohere/north-mini-code:free">Cohere North Mini (무료 - OpenRouter)</option>
<option value="gemini-flash-latest">Gemini 1.5 Flash (직접 API - Google)</option>
</select> </select>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">