From b79c9f77bc0c41f740f46f5c5f4f7b01363a23f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=B8=EC=A7=84?= Date: Fri, 25 Oct 2024 01:14:17 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=8A=A4=EC=BC=80=EC=A4=84=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=ED=94=84=EB=A1=AC=ED=94=84=ED=8A=B8=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 --- .../com/splanet/splanet/gpt/ChatResponse.java | 9 ----- .../java/com/splanet/splanet/gpt/Choice.java | 8 ---- .../java/com/splanet/splanet/gpt/Message.java | 9 ++--- .../splanet/splanet/gpt/OpenAiChatClient.java | 36 ++++++++++++------ .../com/splanet/splanet/gpt/RequestBody.java | 13 +++++++ .../splanet/gpt/SchedulePromptGenerator.java | 38 +++++++++++++++++++ .../splanet/splanet/gpt/ScheduleRequest.java | 19 ++++++++++ .../splanet/splanet/gpt/ScheduleResponse.java | 33 ++++++++++++++++ 8 files changed, 131 insertions(+), 34 deletions(-) delete mode 100644 src/main/java/com/splanet/splanet/gpt/ChatResponse.java delete mode 100644 src/main/java/com/splanet/splanet/gpt/Choice.java create mode 100644 src/main/java/com/splanet/splanet/gpt/RequestBody.java create mode 100644 src/main/java/com/splanet/splanet/gpt/SchedulePromptGenerator.java create mode 100644 src/main/java/com/splanet/splanet/gpt/ScheduleRequest.java create mode 100644 src/main/java/com/splanet/splanet/gpt/ScheduleResponse.java diff --git a/src/main/java/com/splanet/splanet/gpt/ChatResponse.java b/src/main/java/com/splanet/splanet/gpt/ChatResponse.java deleted file mode 100644 index 25d92c71..00000000 --- a/src/main/java/com/splanet/splanet/gpt/ChatResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.splanet.splanet.gpt; - -import lombok.Getter; -import java.util.List; - -@Getter -public class ChatResponse { - private List choices; // Choice 객체 리스트 -} \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/gpt/Choice.java b/src/main/java/com/splanet/splanet/gpt/Choice.java deleted file mode 100644 index be2070a0..00000000 --- a/src/main/java/com/splanet/splanet/gpt/Choice.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.splanet.splanet.gpt; - -import lombok.Getter; - -@Getter -public class Choice { - private Message message; // Message 객체 -} \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/gpt/Message.java b/src/main/java/com/splanet/splanet/gpt/Message.java index 55b59650..c64ff08c 100644 --- a/src/main/java/com/splanet/splanet/gpt/Message.java +++ b/src/main/java/com/splanet/splanet/gpt/Message.java @@ -1,12 +1,11 @@ package com.splanet.splanet.gpt; +import lombok.AllArgsConstructor; import lombok.Getter; @Getter -public class Message { +@AllArgsConstructor +class Message { + private String role; // 메시지의 역할 (user, assistant 등) private String content; // 메시지 내용 - - public Message(String content) { - this.content = content; - } } \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/gpt/OpenAiChatClient.java b/src/main/java/com/splanet/splanet/gpt/OpenAiChatClient.java index 363e30d7..e1249a10 100644 --- a/src/main/java/com/splanet/splanet/gpt/OpenAiChatClient.java +++ b/src/main/java/com/splanet/splanet/gpt/OpenAiChatClient.java @@ -1,5 +1,7 @@ package com.splanet.splanet.gpt; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; @@ -11,35 +13,45 @@ public class OpenAiChatClient { private final WebClient webClient; private final OpenAiProperties openAiProperties; + private final SchedulePromptGenerator promptGenerator; - public OpenAiChatClient(WebClient.Builder webClientBuilder, OpenAiProperties openAiProperties) { + public OpenAiChatClient(WebClient.Builder webClientBuilder, OpenAiProperties openAiProperties, SchedulePromptGenerator promptGenerator) { this.webClient = webClientBuilder.baseUrl("https://api.openai.com/v1").build(); this.openAiProperties = openAiProperties; + this.promptGenerator = new SchedulePromptGenerator(); } - public String call(String message) { - Message userMessage = new Message(message); - Prompt prompt = new Prompt(List.of(userMessage)); + // 스케줄 생성 요청 처리 메소드 + public ScheduleResponse createSchedule(ScheduleRequest scheduleRequest) throws JsonProcessingException { + String jsonResponse = call(scheduleRequest); + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(jsonResponse, ScheduleResponse.class); + } + + private String call(ScheduleRequest scheduleRequest) throws JsonProcessingException { + String jsonRequest = new ObjectMapper().writeValueAsString(new RequestBody("gpt-4o-mini", List.of( + new Message("user", promptGenerator.generateSchedulePrompt(scheduleRequest)) + ))); - ChatResponse response = webClient.post() + // OpenAI API 호출 + String responseJson = webClient.post() .uri("/chat/completions") .header("Authorization", "Bearer " + openAiProperties.getApiKey()) - .bodyValue(prompt) + .bodyValue(jsonRequest) .retrieve() - .bodyToMono(ChatResponse.class) + .bodyToMono(String.class) .block(); - return response != null && response.getChoices() != null && !response.getChoices().isEmpty() - ? response.getChoices().get(0).getMessage().getContent() - : "No response"; + return responseJson; } - public Flux stream(Prompt prompt) { + // 스트리밍 메소드 + public Flux stream(Prompt prompt) { return webClient.post() .uri("/chat/completions") .header("Authorization", "Bearer " + openAiProperties.getApiKey()) .bodyValue(prompt) .retrieve() - .bodyToFlux(ChatResponse.class); + .bodyToFlux(ScheduleResponse.class); } } \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/gpt/RequestBody.java b/src/main/java/com/splanet/splanet/gpt/RequestBody.java new file mode 100644 index 00000000..35983b26 --- /dev/null +++ b/src/main/java/com/splanet/splanet/gpt/RequestBody.java @@ -0,0 +1,13 @@ +package com.splanet.splanet.gpt; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +@Getter +@AllArgsConstructor +public class RequestBody { + private String model; // 모델 이름 + private List messages; // 메시지 리스트 +} \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/gpt/SchedulePromptGenerator.java b/src/main/java/com/splanet/splanet/gpt/SchedulePromptGenerator.java new file mode 100644 index 00000000..40e09d7e --- /dev/null +++ b/src/main/java/com/splanet/splanet/gpt/SchedulePromptGenerator.java @@ -0,0 +1,38 @@ +package com.splanet.splanet.gpt; + +import org.springframework.stereotype.Component; + +@Component +public class SchedulePromptGenerator { + + public String generateSchedulePrompt(ScheduleRequest request) { + StringBuilder prompt = new StringBuilder(); + + prompt.append("splanet은 사용자가 입력한 스케줄 정보에 맞춰 맞춤형 플래너를 제공하는 서비스입니다. 사용자가 음성으로 입력한 정보를 분석하여 최적의 스케줄을 제시해야 합니다.\n\n") + .append("사용자가 다음과 같은 정보를 입력했습니다. 이 정보를 바탕으로 3개의 서로 다른 스케줄을 추천해 주세요. 각 스케줄은 고유한 컨셉을 가져야 하며, 하루 24시간을 30분 단위로 쪼개고, 각 업무의 시작 시간과 종료 시간을 포함해야 합니다.\n\n") + .append("요청 정보:\n") + .append("- 스케줄 기간: \"").append(request.getSchedulePeriod()).append("\"\n") + .append("- 업무 목록: ").append(request.getTaskList()).append("\n") + .append("- 업무 소요 시간: ").append(request.getTaskDurations()).append("\n") + .append("- 우선순위: ").append(request.getPriority()).append("\n") + .append("- 스케줄 컨셉: \"").append(request.getScheduleConcepts().get(0)).append("\", \"") + .append(request.getScheduleConcepts().get(1)).append("\", \"") + .append(request.getScheduleConcepts().get(2)).append("\"\n") // 각 컨셉을 따로 넣도록 + .append("- 하루를 30분 단위로 쪼갠 시간 목록: ").append(request.getTimeSlots()).append("\n") + .append("위 정보를 바탕으로 최적의 플래너 스케줄을 추천할 때 다음 사항을 준수해주세요:\n") + .append("1. 요청된 모든 일정을 사용해야 합니다.\n") + .append("2. 각 스케줄은 고유한 컨셉을 가져야 하며, 3개의 서로 다른 스케줄을 추천합니다.\n") + .append("3. 추천 예시: 사용자가 선택할 수 있도록 3개의 추천 스케줄을 제공합니다.\n") + .append("4. 응답 형식: 답변은 JSON 형식으로만 제공해야 하며, 다음과 같은 구조를 따라야 합니다:\n") + .append(" { \"schedules\": [{ \"concept\": \"스케줄 컨셉\", \"schedule\": [{ \"date\": \"MM-DD\", \"tasks\": [{ \"task\": \"업무명\", \"duration\": \"소요시간\", \"priority\": \"우선순위\", \"startTime\": \"시작시간\", \"endTime\": \"종료시간\" }] }] }] }] }\n") + .append("5. 형식 규칙:\n") + .append(" - 날짜는 MM-DD 형식으로, 시간은 24시간(30분 단위) 형식이어야 합니다.\n") + .append(" - 입력받은 업무를 지정된 일정 안에 모두 포함시켜야 합니다.\n") + .append(" - 우선순위는 고유한 정수 값이어야 합니다.\n") + .append(" - 업무 시간은 주어진 스케줄 기간 안에만 분할하여 채울 수 있습니다.\n") + .append(" - 입력받은 날짜 각각의 일정을 구현해야 합니다.\n") + .append("6. 제외할 내용: 일정 JSON 외에는 다른 내용이 포함되지 않아야 하며, 스케줄링과 관련 없는 질문에는 \"이와 관련된 질문에는 답변할 수 없습니다.\"라고 응답해야 합니다.\n"); + + return prompt.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/gpt/ScheduleRequest.java b/src/main/java/com/splanet/splanet/gpt/ScheduleRequest.java new file mode 100644 index 00000000..e6f254e0 --- /dev/null +++ b/src/main/java/com/splanet/splanet/gpt/ScheduleRequest.java @@ -0,0 +1,19 @@ +package com.splanet.splanet.gpt; + +import lombok.*; + +import java.util.List; +import java.util.Map; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleRequest { + private String schedulePeriod; // 스케줄 기간 + private List taskList; // 업무 목록 + private Map taskDurations; // 업무 소요 시간 (업무명: 소요시간) + private Map priority; // 업무 우선순위 (업무명: 우선순위) + private List scheduleConcepts; // 스케줄 컨셉 목록 + private List timeSlots; // 30분 단위 시간 목록 +} diff --git a/src/main/java/com/splanet/splanet/gpt/ScheduleResponse.java b/src/main/java/com/splanet/splanet/gpt/ScheduleResponse.java new file mode 100644 index 00000000..86b4dada --- /dev/null +++ b/src/main/java/com/splanet/splanet/gpt/ScheduleResponse.java @@ -0,0 +1,33 @@ +package com.splanet.splanet.gpt; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class ScheduleResponse { + private List schedules; // 스케줄 리스트 + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class Schedule { + private String concept; // 스케줄 컨셉 + private List tasks; // 업무 리스트 + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class Task { + private String task; // 업무명 + private String duration; // 소요 시간 + private int priority; // 우선순위 + private String startTime; // 시작 시간 + private String endTime; // 종료 시간 + } + } +} \ No newline at end of file