Skip to content
Merged
Changes from all 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
54 changes: 16 additions & 38 deletions src/main/java/com/spots/domain/ai/service/RecommendLLMService.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package com.spots.domain.ai.service;

import static com.spots.global.exception.Code.INVALID_JSON_RESPONSE;

import static com.spots.global.exception.Code.JSON_CONVERSION_ERROR;
import static com.spots.global.exception.Code.LLM_INTERRUPT_ERROR;
import static com.spots.global.exception.Code.PROMPT_LOADING_ERROR;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.spots.domain.ai.dto.request.RecommendLLMRequest;
import com.spots.domain.ai.dto.response.WeeklyRecommendResponse;
import com.spots.global.exception.CustomException;
import java.io.InputStream;
import java.util.concurrent.Semaphore;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;
Expand All @@ -28,7 +26,11 @@ public class RecommendLLMService {

private final ChatClient chatClient;
private final ObjectMapper objectMapper;
private final Semaphore llmSemaphore = new Semaphore(5);
private final int MAX_CONCURRENT_LLM_CALLS = 5;
private final Semaphore llmSemaphore = new Semaphore(MAX_CONCURRENT_LLM_CALLS);

@Value("classpath:prompt/routineV4.prompt")
private Resource systemPromptResource;
Comment on lines +32 to +33

public WeeklyRecommendResponse createWeeklyPlan(RecommendLLMRequest request) {
StopWatch stopWatch = new StopWatch("LLM_Generation_Task");
Expand All @@ -39,40 +41,32 @@ public WeeklyRecommendResponse createWeeklyPlan(RecommendLLMRequest request) {
stopWatch.stop();

stopWatch.start("2. Prompt & JSON Prep");
String systemMessage = loadPrompt("prompt/routineV4.prompt");
String userJson = toJson(request);
String userMessage = """
아래는 사용자 정보와 후보 운동 프로그램 목록입니다.
이를 기반으로 일주일 운동 루틴 포토카드를 만들 JSON을 생성해주세요.

<user_data>
%s
</user_data>
""".formatted(toJson(request));
""".formatted(userJson);
stopWatch.stop();

stopWatch.start("3. LLM API Call (External)");
String llmResponse = chatClient
.prompt()
.system(systemMessage)
.user(userMessage)
.call()
.content();
stopWatch.stop();

stopWatch.start("4. Response Parsing");
WeeklyRecommendResponse response = objectMapper.readValue(sanitize(llmResponse), WeeklyRecommendResponse.class);
WeeklyRecommendResponse response = chatClient
.prompt()
.system(s -> s.text(systemPromptResource))
.user(userMessage)
.call()
.entity(WeeklyRecommendResponse.class);
stopWatch.stop();

log.info(stopWatch.prettyPrint());

return response;

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new CustomException(LLM_INTERRUPT_ERROR);
} catch (JsonProcessingException e) {
log.error("JSON 파싱 실패. Raw Response: {}", e.getMessage());
throw new CustomException(INVALID_JSON_RESPONSE);
} catch (Exception e) {
log.error("LLM 호출 중 알 수 없는 에러 발생", e);
throw new CustomException(LLM_INTERRUPT_ERROR);
Comment on lines 55 to 72
Expand All @@ -81,27 +75,11 @@ public WeeklyRecommendResponse createWeeklyPlan(RecommendLLMRequest request) {
}
}

private String loadPrompt(String filename) {
try {
InputStream in = getClass().getClassLoader().getResourceAsStream(filename);
return new String(in.readAllBytes(), UTF_8);
} catch (Exception e) {
throw new CustomException(PROMPT_LOADING_ERROR);
}
}

private String toJson(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (Exception e) {
throw new CustomException(JSON_CONVERSION_ERROR);
}
}

private String sanitize(String raw) {
return raw
.replaceAll("(?i)```json", "")
.replaceAll("```", "")
.trim();
}
}
Loading