Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import com.shyashyashya.refit.domain.qnaset.service.QnaSetService;
import com.shyashyashya.refit.domain.qnaset.service.StarAnalysisAsyncService;
import com.shyashyashya.refit.global.dto.ApiResponse;
import com.shyashyashya.refit.global.gemini.GeminiEmbeddingResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -123,4 +125,14 @@ public ResponseEntity<ApiResponse<Page<QnaSetScrapFolderResponse>>> getScrapFold
var response = ApiResponse.success(COMMON200, body);
return ResponseEntity.ok(response);
}

// TODO API 삭제: Gemini Embedding 생성 테스트용 임시 메소드
@Operation(summary = "임베딩 생성 테스트")
@PostMapping("/test-embedding")
public CompletableFuture<ResponseEntity<ApiResponse<GeminiEmbeddingResponse>>> getGeminiEmbedding(
@RequestBody @NotBlank String text) {
return starAnalysisAsyncService
.getTextEmbedding(text)
.thenApply(rsp -> ResponseEntity.ok(ApiResponse.success(COMMON200, rsp)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.shyashyashya.refit.domain.qnaset.constant.StarAnalysisConstant.STAR_ANALYSIS_CREATE_REQUEST_TIMEOUT_SEC;
import static com.shyashyashya.refit.global.exception.ErrorCode.STAR_ANALYSIS_CREATE_FAILED;
import static com.shyashyashya.refit.global.exception.ErrorCode.STAR_ANALYSIS_PARSING_FAILED;
import static com.shyashyashya.refit.global.exception.ErrorCode.TEXT_EMBED_FAILED;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -12,8 +13,11 @@
import com.shyashyashya.refit.domain.qnaset.model.StarAnalysis;
import com.shyashyashya.refit.global.exception.CustomException;
import com.shyashyashya.refit.global.gemini.GeminiClient;
import com.shyashyashya.refit.global.gemini.GeminiEmbeddingRequest;
import com.shyashyashya.refit.global.gemini.GeminiEmbeddingResponse;
import com.shyashyashya.refit.global.gemini.GeminiGenerateRequest;
import com.shyashyashya.refit.global.gemini.GeminiGenerateResponse;
import com.shyashyashya.refit.global.gemini.GenerateModel;
import com.shyashyashya.refit.global.gemini.StarAnalysisGeminiResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
Expand Down Expand Up @@ -43,7 +47,10 @@ public CompletableFuture<StarAnalysisDto> createStarAnalysis(Long qnaSetId) {

log.info("Send star analysis generate request to gemini. qnaSetId: {}", qnaSetId);
CompletableFuture<GeminiGenerateResponse> reqFuture =
geminiClient.sendAsyncRequest(requestBody, STAR_ANALYSIS_CREATE_REQUEST_TIMEOUT_SEC);
// geminiClient.sendAsyncRequest(requestBody, GenerateModel.GEMINI_2_5_FLASH_LITE,
// STAR_ANALYSIS_CREATE_REQUEST_TIMEOUT_SEC);
geminiClient.sendAsyncGenerateRequest(
requestBody, GenerateModel.GEMMA_3_27B_IT, STAR_ANALYSIS_CREATE_REQUEST_TIMEOUT_SEC);

return reqFuture
.thenApplyAsync(
Expand All @@ -60,6 +67,28 @@ public CompletableFuture<StarAnalysisDto> createStarAnalysis(Long qnaSetId) {
});
}

// TODO 메소드 삭제: Gemini Embedding 생성 테스트용 임시 메소드
public CompletableFuture<GeminiEmbeddingResponse> getTextEmbedding(String text) {

GeminiEmbeddingRequest requestBody = GeminiEmbeddingRequest.from(
text, GeminiEmbeddingRequest.TaskType.CLUSTERING, GeminiEmbeddingRequest.OutputDimensionality.D128);

CompletableFuture<GeminiEmbeddingResponse> reqFuture =
geminiClient.sendAsyncEmbeddingRequest(requestBody, STAR_ANALYSIS_CREATE_REQUEST_TIMEOUT_SEC);

return reqFuture
.thenApplyAsync(
response -> {
log.info("Embedding request result: ");
return response;
},
geminiPostProcessExecutor)
.exceptionally(e -> {
log.error(e.getMessage(), e);
throw new CustomException(TEXT_EMBED_FAILED);
});
}

private StarAnalysisDto processSuccessRequest(
Long starAnalysisId, Long qnaSetId, GeminiGenerateResponse geminiResponse) {
log.info("Receive star analysis generate response from gemini. qnaSetId: {} \n{}", qnaSetId, geminiResponse);
Expand All @@ -72,6 +101,12 @@ private StarAnalysisDto processSuccessRequest(

private StarAnalysisGeminiResponse parseStarAnalysisGeminiResponse(String text) {
try {
if (text.startsWith("```json\n")) {
text = text.substring("```json\n".length());
}
if (text.endsWith("```")) {
text = text.substring(0, text.length() - "```".length());
}
Comment on lines +79 to +84
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 이 파트가 전에 말씀하셨던 모델별 응답 형식의 차이? 와 관련된 로직일까요? 아니면 테스트 용으로 로직을 작성하는 과정에서 필요하여 작성된 로직일까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

특정 모델이 응답에 ```json ```을 붙이는 것을 해결하기 위한 로직입니다!

return objectMapper.readValue(text, StarAnalysisGeminiResponse.class);
} catch (JsonProcessingException e) {
throw new CustomException(STAR_ANALYSIS_PARSING_FAILED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public enum ErrorCode {
STAR_ANALYSIS_CREATE_FAILED(INTERNAL_SERVER_ERROR, "스타 분석 생성 중 오류가 발생하였습니다."),
STAR_ANALYSIS_COMPLETE_FAILED(INTERNAL_SERVER_ERROR, "스타 분석 업데이트 중 오류가 발생하였습니다."),
STAR_ANALYSIS_DELETE_NOT_ALLOWED_STATUS(BAD_REQUEST, "진행 중(IN_PROGRESS)인 스타 분석만 삭제할 수 있습니다."),
;

TEXT_EMBED_FAILED(INTERNAL_SERVER_ERROR, "임베딩 생성에 실패하였습니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,11 @@ public class GeminiClient {
private final GeminiProperty geminiProperty;
private final WebClient webClient;

// TODO 요청 URL 상수로 분리, 임베딩 요청 고려
private static final String GEMINI_API_URL =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent";
// "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
// "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent";

public CompletableFuture<GeminiGenerateResponse> sendAsyncRequest(
GeminiGenerateRequest requestBody, Long timeoutSec) {
public CompletableFuture<GeminiGenerateResponse> sendAsyncGenerateRequest(
GeminiGenerateRequest requestBody, GenerateModel model, Long timeoutSec) {
return webClient
.post()
.uri(GEMINI_API_URL)
.uri(model.endpoint())
.header("x-goog-api-key", geminiProperty.apiKey())
.accept(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
Expand All @@ -34,4 +28,21 @@ public CompletableFuture<GeminiGenerateResponse> sendAsyncRequest(
.timeout(Duration.ofSeconds(timeoutSec))
.toFuture();
}

private static final String EMBEDDING_ENDPOINT =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:embedContent";

public CompletableFuture<GeminiEmbeddingResponse> sendAsyncEmbeddingRequest(
GeminiEmbeddingRequest requestBody, Long timeoutSec) {
return webClient
.post()
.uri(EMBEDDING_ENDPOINT)
.header("x-goog-api-key", geminiProperty.apiKey())
.accept(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.retrieve()
.bodyToMono(GeminiEmbeddingResponse.class)
.timeout(Duration.ofSeconds(timeoutSec))
.toFuture();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.shyashyashya.refit.global.gemini;

import com.fasterxml.jackson.annotation.JsonValue;
import java.util.List;

public record GeminiEmbeddingRequest(TaskType taskType, Content content, OutputDimensionality outputDimensionality) {

public enum TaskType {
SEMANTIC_SIMILARITY,
CLASSIFICATION,
CLUSTERING
}

public enum OutputDimensionality {
D2048(2048),
D1536(1536),
D768(768),
D512(512),
D256(256),
D128(128);

private final int value;

OutputDimensionality(int value) {
this.value = value;
}

@JsonValue
public int value() {
return value;
}
}

public record Content(List<Part> parts) {}

public record Part(String text) {}

public static GeminiEmbeddingRequest from(
String text, TaskType taskType, OutputDimensionality outputDimensionality) {
if (text == null || text.isBlank()) {
throw new IllegalArgumentException("text must not be blank");
}

Content content = new Content(List.of(new Part(text)));
return new GeminiEmbeddingRequest(taskType, content, outputDimensionality);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.shyashyashya.refit.global.gemini;

import java.util.List;

public record GeminiEmbeddingResponse(Embedding embedding) {

public record Embedding(List<Float> values) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.shyashyashya.refit.global.gemini;

public enum GenerateModel {
GEMINI_2_5_PRO("gemini-2.5-pro"),
GEMINI_2_5_FLASH_LITE("gemini-2.5-flash-lite"),
GEMINI_2_5_FLASH("gemini-2.5-flash"),
GEMINI_3_FLASH("gemini-3-flash-preview"),
GEMINI_3_PRO("gemini-3-pro-preview"),

GEMMA_3_1B_IT("gemma-3-1b-it"),
GEMMA_3_4B_IT("gemma-3-4b-it"),
GEMMA_3_12B_IT("gemma-3-12b-it"),
GEMMA_3_27B_IT("gemma-3-27b-it");

private static final String PREFIX = "https://generativelanguage.googleapis.com/v1beta/models/";
private static final String SUFFIX = ":generateContent";

private final String id;
private final String endpoint;

GenerateModel(String id) {
this.id = id;
this.endpoint = PREFIX + id + SUFFIX;
}

public String id() {
return id;
}

public String endpoint() {
return endpoint;
}
}
Loading