Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend/feature/95 add location coordinate #96

Merged
merged 6 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
@NoArgsConstructor
public class GptSchedule {
private String date;
private List<String> scheduleDetail;
private List<GptScheduleDetail> scheduleDetail;

public GptSchedule(String date, List<String> scheduleDetail) {
public GptSchedule(String date, List<GptScheduleDetail> scheduleDetail) {
this.date = date;
this.scheduleDetail = scheduleDetail;
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,70 @@ public class GptScheduleParser {

public List<GptSchedule> parseScheduleText(String scheduleText) {
List<GptSchedule> schedules = new ArrayList<>();
Pattern datePattern = Pattern.compile(ParsingConstants.DATE_REGEX);

List<String> lines = List.of(scheduleText.split(ParsingConstants.NEW_LINE_REGEX));
List<String> currentScheduleDetail = new ArrayList<>();
List<GptScheduleDetail> 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<GptScheduleDetail> currentScheduleDetail, List<GptSchedule> 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<GptScheduleDetail> 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<GptSchedule> schedules, String date, List<GptScheduleDetail> details) {
schedules.add(new GptSchedule(date, details));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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))
Expand All @@ -52,78 +50,67 @@ public GptScheduleResponse getResponse(HttpHeaders headers, GptRequest gptReques
.bodyToMono(GptResponse.class)
.block();

List<GptSchedule> gptSchedules = gptScheduleParser.parseScheduleText(getScheduleText(response));
List<GptSchedule> 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<GptSchedulesResponse> askQuestion(GptScheduleRequest questionRequestDTO) {
String question = makeQuestion(questionRequestDTO);
List<GptMessage> messages = Collections.singletonList(
GptMessage.builder()
.role(GptConfig.ROLE)
.content(question)
.build()
);

Country country = scheduleService.validateCountry(questionRequestDTO.getDestination());
public CompletableFuture<GptSchedulesResponse> askQuestion(GptScheduleRequest questionRequest) {
String question = formatQuestion(questionRequest);
List<GptMessage> messages = Collections.singletonList(createMessage(question));
Country country = scheduleService.validateCountry(questionRequest.getDestination());
String countryImage = country.getImageUrl();

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<CompletableFuture<GptScheduleResponse>> 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<List<GptScheduleResponse>> 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<GptScheduleResponse> sendRequestAsync(String apiKey, List<GptMessage> messages, ExecutorService executor) {
private CompletableFuture<GptScheduleResponse> sendRequestAsync(List<GptMessage> 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<GptMessage> messages) {
return new GptRequest(
GptConfig.CHAT_MODEL,
GptConfig.MAX_TOKEN,
GptConfig.TEMPERATURE,
GptConfig.STREAM,
messages
);
private GptRequest createGptRequest(List<GptMessage> 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();
}
}
Loading