From a6725c508c91f9b98f52a2ba1c82fe31889a9699 Mon Sep 17 00:00:00 2001 From: sonsumin Date: Sat, 23 Nov 2024 00:57:32 +0900 Subject: [PATCH] =?UTF-8?q?[#36]=E2=9C=A8Feat:=20GPT=20DTO=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/gpt/dto/ChatRequestMsgDTO.java | 22 +++ .../domain/gpt/dto/CompletionDTO.java | 33 ++++ .../gpt/service/impl/ChatGPTServiceImpl.java | 169 ++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 src/main/java/univ/yesummit/domain/gpt/dto/ChatRequestMsgDTO.java create mode 100644 src/main/java/univ/yesummit/domain/gpt/dto/CompletionDTO.java create mode 100644 src/main/java/univ/yesummit/domain/gpt/service/impl/ChatGPTServiceImpl.java diff --git a/src/main/java/univ/yesummit/domain/gpt/dto/ChatRequestMsgDTO.java b/src/main/java/univ/yesummit/domain/gpt/dto/ChatRequestMsgDTO.java new file mode 100644 index 0000000..af6213c --- /dev/null +++ b/src/main/java/univ/yesummit/domain/gpt/dto/ChatRequestMsgDTO.java @@ -0,0 +1,22 @@ +package univ.yesummit.domain.gpt.dto; + +import lombok.*; + +/** + * ChatGPT 신규 모델의 Request + */ +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ChatRequestMsgDTO { + + private String role; + + private String content; + + @Builder + public ChatRequestMsgDTO(String role, String content) { + this.role = role; + this.content = content; + } +} diff --git a/src/main/java/univ/yesummit/domain/gpt/dto/CompletionDTO.java b/src/main/java/univ/yesummit/domain/gpt/dto/CompletionDTO.java new file mode 100644 index 0000000..09bc0c2 --- /dev/null +++ b/src/main/java/univ/yesummit/domain/gpt/dto/CompletionDTO.java @@ -0,0 +1,33 @@ +package univ.yesummit.domain.gpt.dto; + +import lombok.*; + +/** + * 프롬프트 요청 DTO : + * gpt-3.5-turbo-instruct, babbage-002, davinci-002 + */ +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CompletionDTO { + + // 사용할 모델 + private String model; + + // 사용할 프롬프트 명령어 + private String prompt; + + // 프롬프트의 다양성을 조절할 명령어(default : 1) + private float temperature = 1; + + // 최대 사용할 토큰(default : 16) + private int max_tokens = 16; + + @Builder + public CompletionDTO(String model, String prompt, float temperature, int max_tokens) { + this.model = model; + this.prompt = prompt; + this.temperature = temperature; + this.max_tokens = max_tokens; + } +} diff --git a/src/main/java/univ/yesummit/domain/gpt/service/impl/ChatGPTServiceImpl.java b/src/main/java/univ/yesummit/domain/gpt/service/impl/ChatGPTServiceImpl.java new file mode 100644 index 0000000..46a1ee2 --- /dev/null +++ b/src/main/java/univ/yesummit/domain/gpt/service/impl/ChatGPTServiceImpl.java @@ -0,0 +1,169 @@ +package univ.yesummit.domain.gpt.service.impl; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import univ.yesummit.domain.gpt.config.ChatGPTConfig; +import univ.yesummit.domain.gpt.dto.ChatCompletionDTO; +import univ.yesummit.domain.summit.repository.SummitRepository; +import univ.yesummit.domain.gpt.service.ChatGPTService; +import univ.yesummit.domain.member.repository.MemberRepository; + +import java.util.HashMap; +import java.util.Map; + +/** + * ChatGPT Service 구현체 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ChatGPTServiceImpl implements ChatGPTService { + + private final ChatGPTConfig chatGPTConfig; + private final ObjectMapper objectMapper; + private final SummitRepository summitRepository; + private final MemberRepository memberRepository; + + @Value("${openai.url.model}") + private String modelUrl; + + @Value("${openai.url.model-list}") + private String modelListUrl; + + @Value("${openai.url.prompt}") + private String promptUrl; + + @Value("${openai.url.legacy-prompt}") + private String legacyPromptUrl; + + + /** + * 모델이 유효한지 확인하는 비즈니스 로직 + */ + @Override + public Map isValidModel(String modelName) { + log.debug("[+] 모델이 유효한지 조회합니다. 모델 : " + modelName); + Map result = new HashMap<>(); + + // 토큰 정보가 포함된 Header를 가져온다. + HttpHeaders headers = chatGPTConfig.httpHeaders(); + + // 통신을 위한 RestTemplate을 구성 + ResponseEntity response = chatGPTConfig + .restTemplate() + .exchange(modelListUrl + "/" + modelName, HttpMethod.GET, new HttpEntity<>(headers), String.class); + try { + // Jackson을 기반으로 응답값을 가져온다. + result = objectMapper.readValue(response.getBody(), new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + log.debug("JsonMappingException :: " + e.getMessage()); + } catch (RuntimeException e) { + log.debug("RuntimeException :: " + e.getMessage()); + } + return result; + } + + + /** + * 최신 모델에 대한 프롬프트 + */ + @Override + public Map prompt(ChatCompletionDTO chatCompletionDto) { + log.debug("[+] 신규 프롬프트를 수행합니다."); + + Map resultMap = new HashMap<>(); + + // 토큰 정보가 포함된 Header를 가져온다. + HttpHeaders headers = chatGPTConfig.httpHeaders(); + + // 통신을 위한 RestTemplate을 구성 + HttpEntity requestEntity = new HttpEntity<>(chatCompletionDto, headers); + ResponseEntity response = chatGPTConfig + .restTemplate() + .exchange(promptUrl, HttpMethod.POST, requestEntity, String.class); + try { + // String -> HashMap 역직렬화를 구성 + resultMap = objectMapper.readValue(response.getBody(), new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + log.debug("JsonMappingException :: " + e.getMessage()); + } catch (RuntimeException e) { + log.debug("RuntimeException :: " + e.getMessage()); + } + return resultMap; + } + +// @Override +// public Map createSummit() { +// String prompt = "지속 가능한 미래를 이끄는 건, 결국 청년과 그들의 새로운 시각입니다. " + +// "이 점에서 청년들이 그들의 반짝이는 아이디어를 실현하고 세상에 선보일 수 있도록 돕는 것은 " + +// "청년의 창업 문제 해결을 통해 지속 가능한 미래를 만들 수 있습니다. 다만 예비 청년 창업가들에게는 " + +// "자신의 창업 아이템을 전문적으로 소개할 창구가 부족합니다. 그래서 Young Entrepreneur들이 " + +// "주인공인 Summit 플랫폼을 만들고자 합니다. 이를 바탕으로 써밋 주제를 5줄 정도로 만들어주세요. " + +// "제목과 본문으로 구성해 주세요."; +// +// ChatCompletionDTO chatCompletionDto = ChatCompletionDTO.builder() +// .model("gpt-3.5-turbo") +// .messages(List.of( +// ChatRequestMsgDTO.builder() +// .role("user") +// .content(prompt) +// .build() +// )) +// .build(); +// +// // ChatGPT API 호출 +// Map gptResponse = prompt(chatCompletionDto); +// +// // 응답 데이터 검증 +// if (gptResponse == null || !gptResponse.containsKey("choices")) { +// throw new RuntimeException("ChatGPT 응답이 유효하지 않습니다."); +// } +// +// List> choices = (List>) gptResponse.get("choices"); +// if (choices == null || choices.isEmpty()) { +// throw new RuntimeException("ChatGPT 응답에서 choices가 비어 있습니다."); +// } +// +// Map firstChoice = choices.get(0); +// if (firstChoice == null || !firstChoice.containsKey("text")) { +// throw new RuntimeException("ChatGPT 응답의 텍스트가 없습니다."); +// } +// +// String responseText = (String) firstChoice.get("text"); +// if (responseText == null || responseText.isEmpty()) { +// throw new RuntimeException("ChatGPT 응답의 텍스트가 비어 있습니다."); +// } +// +// // 텍스트 파싱 +// String[] splitText = responseText.split("\n", 2); +// if (splitText.length < 2) { +// throw new RuntimeException("ChatGPT 응답 형식이 올바르지 않습니다."); +// } +// +// String title = splitText[0].replace("제목:", "").trim(); +// String content = splitText[1].replace("본문:", "").trim(); +// +// // Summit 엔티티 생성 및 저장 +// Summit newSummit = new Summit(title, content); +// Summit savedSummit = summitRepository.save(newSummit); +// +// return Map.of( +// "message", "Summit has been created successfully", +// "summitId", savedSummit.getId(), +// "title", savedSummit.getTitle(), +// "content", savedSummit.getContent() +// ); +// } +} \ No newline at end of file