diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/config/WebClientConfig.java b/backend/src/main/java/com/isp/backend/domain/gpt/config/WebClientConfig.java index f6500b65..b78c3b53 100644 --- a/backend/src/main/java/com/isp/backend/domain/gpt/config/WebClientConfig.java +++ b/backend/src/main/java/com/isp/backend/domain/gpt/config/WebClientConfig.java @@ -17,8 +17,8 @@ public WebClient webClient() { HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000) .doOnConnected(conn -> conn - .addHandlerLast(new ReadTimeoutHandler(10)) - .addHandlerLast(new WriteTimeoutHandler(10))); + .addHandlerLast(new ReadTimeoutHandler(30)) + .addHandlerLast(new WriteTimeoutHandler(30))); return WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/Coordinate.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/Coordinate.java new file mode 100644 index 00000000..f3bb8bc7 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/Coordinate.java @@ -0,0 +1,16 @@ +package com.isp.backend.domain.gpt.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class Coordinate { + private Double latitude; + private Double longitude; + + public Coordinate(Double latitude, Double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptSchedule.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptSchedule.java index 4a82d134..c78e099d 100644 --- a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptSchedule.java +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptSchedule.java @@ -9,10 +9,10 @@ @NoArgsConstructor public class GptSchedule { private String date; - private List scheduleDetail; + private List scheduleDetail; - public GptSchedule(String date, List scheduleDetail) { + public GptSchedule(String date, List scheduleDetail) { this.date = date; this.scheduleDetail = scheduleDetail; } -} +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleDetail.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleDetail.java new file mode 100644 index 00000000..99781771 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleDetail.java @@ -0,0 +1,16 @@ +package com.isp.backend.domain.gpt.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class GptScheduleDetail { + private String detail; + private Coordinate coordinate; + + public GptScheduleDetail(String detail, Coordinate coordinate) { + this.detail = detail; + this.coordinate = coordinate; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleParser.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleParser.java index ffb4306e..64a06dc0 100644 --- a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleParser.java +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleParser.java @@ -15,30 +15,70 @@ public class GptScheduleParser { public List parseScheduleText(String scheduleText) { List schedules = new ArrayList<>(); - Pattern datePattern = Pattern.compile(ParsingConstants.DATE_REGEX); - List lines = List.of(scheduleText.split(ParsingConstants.NEW_LINE_REGEX)); - List currentScheduleDetail = new ArrayList<>(); + List currentScheduleDetail = new ArrayList<>(); String currentDate = ParsingConstants.CURRENT_DATE; for (String line : lines) { - Matcher dateMatcher = datePattern.matcher(line); - if (dateMatcher.find()) { - if (!currentDate.isEmpty()) { - schedules.add(new GptSchedule(currentDate, currentScheduleDetail)); - currentScheduleDetail = new ArrayList<>(); - } - currentDate = dateMatcher.group(ParsingConstants.GROUP_MATCH); - } else if (!line.trim().isEmpty() && ParsingConstants.FILTER_STRINGS.stream().noneMatch(line::contains)) { - currentScheduleDetail.add(line.trim().substring(ParsingConstants.BEGIN_INDEX)); // Remove leading index - } + currentDate = processLine(line, currentDate, currentScheduleDetail, schedules); } if (!currentDate.isEmpty()) { - schedules.add(new GptSchedule(currentDate, currentScheduleDetail)); + addSchedule(schedules, currentDate, currentScheduleDetail); } return schedules; } -} + private String processLine(String line, String currentDate, List currentScheduleDetail, List schedules) { + if (isDateLine(line)) { + if (!currentDate.isEmpty()) { + addSchedule(schedules, currentDate, currentScheduleDetail); + currentScheduleDetail.clear(); + } + return extractDate(line); + } else if (shouldProcessLine(line)) { + addDetail(line, currentScheduleDetail); + } + return currentDate; + } + + private boolean isDateLine(String line) { + Pattern datePattern = Pattern.compile(ParsingConstants.DATE_REGEX); + Matcher dateMatcher = datePattern.matcher(line); + return dateMatcher.find(); + } + + private String extractDate(String line) { + Pattern datePattern = Pattern.compile(ParsingConstants.DATE_REGEX); + Matcher dateMatcher = datePattern.matcher(line); + if (dateMatcher.find()) { + return dateMatcher.group(ParsingConstants.GROUP_MATCH); + } + return ParsingConstants.CURRENT_DATE; + } + + private boolean shouldProcessLine(String line) { + return !line.trim().isEmpty() && ParsingConstants.FILTER_STRINGS.stream().noneMatch(line::contains); + } + + private void addDetail(String line, List currentScheduleDetail) { + Pattern detailPattern = Pattern.compile("^(?:\\d+\\.\\s*)?(.*?)(\\d+\\.\\d{1,8}),\\s*(\\d+\\.\\d{1,8})$"); + Matcher detailMatcher = detailPattern.matcher(line.trim()); + if (detailMatcher.find()) { + String detail = detailMatcher.group(1).trim(); + Double latitude = Double.valueOf(detailMatcher.group(2)); + Double longitude = Double.valueOf(detailMatcher.group(3)); + + String formattedLatitude = String.format("%.8f", latitude); + String formattedLongitude = String.format("%.8f", longitude); + + Coordinate coordinate = new Coordinate(Double.parseDouble(formattedLatitude), Double.parseDouble(formattedLongitude)); + currentScheduleDetail.add(new GptScheduleDetail(detail, coordinate)); + } + } + + private void addSchedule(List schedules, String date, List details) { + schedules.add(new GptSchedule(date, details)); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/service/GptService.java b/backend/src/main/java/com/isp/backend/domain/gpt/service/GptService.java index 56104236..cbb9fbb2 100644 --- a/backend/src/main/java/com/isp/backend/domain/gpt/service/GptService.java +++ b/backend/src/main/java/com/isp/backend/domain/gpt/service/GptService.java @@ -13,7 +13,6 @@ import com.isp.backend.domain.gpt.entity.GptScheduleParser; import com.isp.backend.domain.schedule.service.ScheduleService; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -30,10 +29,10 @@ import java.util.concurrent.Executors; import java.util.stream.Collectors; -@Slf4j -@RequiredArgsConstructor @Service +@RequiredArgsConstructor public class GptService { + private final GptScheduleParser gptScheduleParser; private final ScheduleService scheduleService; private final WebClient webClient; @@ -42,7 +41,6 @@ public class GptService { private String apiKey; public GptScheduleResponse getResponse(HttpHeaders headers, GptRequest gptRequest) { - GptResponse response = webClient.post() .uri(GptConfig.CHAT_URL) .headers(h -> h.addAll(headers)) @@ -52,78 +50,67 @@ public GptScheduleResponse getResponse(HttpHeaders headers, GptRequest gptReques .bodyToMono(GptResponse.class) .block(); - List gptSchedules = gptScheduleParser.parseScheduleText(getScheduleText(response)); + List gptSchedules = gptScheduleParser.parseScheduleText(extractScheduleText(response)); return new GptScheduleResponse(gptSchedules); } - private String getScheduleText(GptResponse gptResponse) { - return getGptMessage(gptResponse).toString(); - } - - private GptMessage getGptMessage(GptResponse gptResponse) { - return gptResponse.getChoices().get(0).getMessage(); + private String extractScheduleText(GptResponse gptResponse) { + return gptResponse.getChoices().get(0).getMessage().getContent(); } @Async - public CompletableFuture askQuestion(GptScheduleRequest questionRequestDTO) { - String question = makeQuestion(questionRequestDTO); - List messages = Collections.singletonList( - GptMessage.builder() - .role(GptConfig.ROLE) - .content(question) - .build() - ); - - Country country = scheduleService.validateCountry(questionRequestDTO.getDestination()); + public CompletableFuture askQuestion(GptScheduleRequest questionRequest) { + String question = formatQuestion(questionRequest); + List messages = Collections.singletonList(createMessage(question)); + Country country = scheduleService.validateCountry(questionRequest.getDestination()); String countryImage = country.getImageUrl(); ExecutorService executorService = Executors.newFixedThreadPool(5); List> futures = Arrays.asList( - sendRequestAsync(apiKey, messages, executorService), - sendRequestAsync(apiKey, messages, executorService), - sendRequestAsync(apiKey, messages, executorService) + sendRequestAsync(messages, executorService), + sendRequestAsync(messages, executorService), + sendRequestAsync(messages, executorService) ); CompletableFuture> combinedFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .thenApply(v -> futures.stream() - .map(CompletableFuture::join) - .collect(Collectors.toList())); + .thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList())); return combinedFuture.thenApply(schedules -> new GptSchedulesResponse(countryImage, schedules)); } - private CompletableFuture sendRequestAsync(String apiKey, List messages, ExecutorService executor) { + private CompletableFuture sendRequestAsync(List messages, ExecutorService executor) { HttpHeaders headers = buildHttpHeaders(apiKey); - GptRequest request = getGptRequest(messages); + GptRequest request = createGptRequest(messages); return CompletableFuture.supplyAsync(() -> getResponse(headers, request), executor); } - private GptRequest getGptRequest(List messages) { - return new GptRequest( - GptConfig.CHAT_MODEL, - GptConfig.MAX_TOKEN, - GptConfig.TEMPERATURE, - GptConfig.STREAM, - messages - ); + private GptRequest createGptRequest(List messages) { + return new GptRequest(GptConfig.CHAT_MODEL, GptConfig.MAX_TOKEN, GptConfig.TEMPERATURE, GptConfig.STREAM, messages); } private HttpHeaders buildHttpHeaders(String key) { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add(GptConfig.AUTHORIZATION, GptConfig.BEARER + key); - return httpHeaders; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.add(HttpHeaders.AUTHORIZATION, GptConfig.BEARER + key); + return headers; } - private String makeQuestion(GptScheduleRequest questionRequestDTO) { + private String formatQuestion(GptScheduleRequest questionRequest) { return String.format(GptConfig.PROMPT, - questionRequestDTO.getDestination(), - questionRequestDTO.getPurpose(), - questionRequestDTO.getIncludedActivities(), - questionRequestDTO.getExcludedActivities(), - questionRequestDTO.getDepartureDate(), - questionRequestDTO.getReturnDate(), - String.join(ParsingConstants.COMMA, questionRequestDTO.getPurpose()) + questionRequest.getDestination(), + questionRequest.getPurpose(), + questionRequest.getIncludedActivities(), + questionRequest.getExcludedActivities(), + questionRequest.getDepartureDate(), + questionRequest.getReturnDate(), + String.join(ParsingConstants.COMMA, questionRequest.getPurpose()) ); } + + private GptMessage createMessage(String content) { + return GptMessage.builder() + .role(GptConfig.ROLE) + .content(content) + .build(); + } } \ No newline at end of file