-
Notifications
You must be signed in to change notification settings - Fork 0
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
[refactor] : Chatbot 관련 리팩토링을 진행한다 #85
Changes from 7 commits
07d14f0
8b2be66
f3bf9fe
e29ac37
caf8071
c867ca7
926f685
e4b0ee2
0075e43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,24 @@ | ||
package kusitms.backend.chatbot.application; | ||
|
||
import kusitms.backend.chatbot.dto.request.ClovaRequest; | ||
import kusitms.backend.chatbot.dto.request.ChatbotRequest; | ||
import kusitms.backend.chatbot.dto.response.GetClovaChatbotAnswerResponse; | ||
import kusitms.backend.chatbot.infrastructure.ClovaApiClient; | ||
import kusitms.backend.chatbot.infrastructure.ChatbotApiClient; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import reactor.core.publisher.Mono; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ClovaService { | ||
private final ClovaApiClient clovaApiClient; | ||
private final ChatbotApiClient chatbotApiClient; | ||
private final ClovaRequestFactory clovaRequestFactory; | ||
private final MessageFactory messageFactory; | ||
|
||
// Clova 챗봇 답변을 가져오는 메서드 | ||
public GetClovaChatbotAnswerResponse getClovaChatbotAnswer(String message) { | ||
ClovaRequest request = clovaRequestFactory.createClovaRequest(); | ||
request.messages().add(messageFactory.createUserMessage(message)); | ||
String answer = clovaApiClient.requestClova(request); | ||
public Mono<GetClovaChatbotAnswerResponse> getClovaChatbotAnswer(String message) { | ||
ChatbotRequest request = clovaRequestFactory.createClovaRequest(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GetClovaChatbotAnswerResponseDto로 바꿔주세요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 변경 완료! |
||
request.getMessages().add(messageFactory.createUserMessage(message)); | ||
|
||
return GetClovaChatbotAnswerResponse.of(answer); | ||
return chatbotApiClient.requestChatbot(request) | ||
.map(GetClovaChatbotAnswerResponse::of); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,15 @@ | ||
package kusitms.backend.chatbot.domain.enums; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum Role { | ||
system, // 시스템 | ||
user, // 사용자 | ||
assistant; // 어시스턴트 | ||
} | ||
SYSTEM("system"), // 시스템 | ||
USER("user"), // 사용자 | ||
ASSISTANT("assistant") // 어시스턴트 | ||
; | ||
|
||
private final String role; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package kusitms.backend.chatbot.dto.request; | ||
|
||
import java.util.List; | ||
|
||
public interface ChatbotRequest { | ||
List<Message> getMessages(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,18 @@ | ||
package kusitms.backend.chatbot.dto.request; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public record ClovaRequest( | ||
ArrayList<Message> messages, | ||
double topP, | ||
double temperature, | ||
int maxTokens, | ||
double repeatPenalty | ||
) { | ||
} | ||
) implements ChatbotRequest { | ||
|
||
@Override | ||
public List<Message> getMessages() { | ||
return messages; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,7 @@ | ||
package kusitms.backend.chatbot.dto.request; | ||
|
||
import kusitms.backend.chatbot.domain.enums.Role; | ||
|
||
public record Message( | ||
Role role, | ||
String role, | ||
String content | ||
) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package kusitms.backend.chatbot.infrastructure; | ||
|
||
import kusitms.backend.chatbot.dto.request.ChatbotRequest; | ||
import reactor.core.publisher.Mono; | ||
|
||
public interface ChatbotApiClient { | ||
/** | ||
* 외부 챗봇 API와 통신하여 답변을 가져옵니다. | ||
* | ||
* @param request 추상 챗봇 요청 객체 | ||
* @return 챗봇의 응답 메시지 | ||
*/ | ||
Mono<String> requestChatbot(ChatbotRequest request); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,38 @@ | ||
package kusitms.backend.chatbot.infrastructure; | ||
|
||
import kusitms.backend.chatbot.dto.request.ChatbotRequest; | ||
import kusitms.backend.chatbot.dto.request.ClovaRequest; | ||
import kusitms.backend.chatbot.dto.response.ClovaChatbotAnswer; | ||
import kusitms.backend.chatbot.status.ChatbotErrorStatus; | ||
import kusitms.backend.global.exception.CustomException; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.reactive.function.client.WebClient; | ||
import org.springframework.web.reactive.function.client.WebClientResponseException; | ||
import reactor.core.publisher.Mono; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class ClovaApiClient { | ||
public class ClovaApiClient implements ChatbotApiClient { | ||
private final WebClient webClient; | ||
|
||
// 외부 CLOVA API와 통신하는 메서드 | ||
public String requestClova(ClovaRequest request) { | ||
ClovaChatbotAnswer clovaChatbotAnswer = webClient.post() | ||
.bodyValue(request) | ||
@Override | ||
public Mono<String> requestChatbot(ChatbotRequest request) { | ||
if (!(request instanceof ClovaRequest clovaRequest)) { | ||
return Mono.error(new CustomException(ChatbotErrorStatus._INVALID_CHATBOT_REQUEST)); | ||
} | ||
|
||
return webClient.post() | ||
.bodyValue(clovaRequest) | ||
.retrieve() | ||
.bodyToMono(ClovaChatbotAnswer.class) | ||
.block(); | ||
|
||
return clovaChatbotAnswer.result().message().content(); | ||
.flatMap(clovaChatbotAnswer -> { | ||
if (clovaChatbotAnswer == null || clovaChatbotAnswer.result() == null || clovaChatbotAnswer.result().message() == null) { | ||
return Mono.error(new CustomException(ChatbotErrorStatus._NOT_FOUND_GUIDE_CHATBOT_ANSWER)); | ||
} | ||
return Mono.just(clovaChatbotAnswer.result().message().content()); | ||
}) | ||
.onErrorMap(WebClientResponseException.class, e -> new CustomException(ChatbotErrorStatus._CHATBOT_API_COMMUNICATION_ERROR)) | ||
.onErrorMap(Exception.class, e -> new CustomException(ChatbotErrorStatus._CHATBOT_API_COMMUNICATION_ERROR)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,23 +16,33 @@ public class ApiResponse<T> { | |
@JsonInclude(JsonInclude.Include.NON_NULL) | ||
private final T payload; | ||
|
||
// 성공 응답 - 페이로드 포함 | ||
public static <T> ResponseEntity<ApiResponse<T>> onSuccess(BaseCode code, T payload) { | ||
ApiResponse<T> response = new ApiResponse<>(true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), payload); | ||
return ResponseEntity.status(code.getReasonHttpStatus().getHttpStatus()).body(response); | ||
} | ||
|
||
// 성공 응답 - 페이로드 없음 | ||
public static <T> ResponseEntity<ApiResponse<T>> onSuccess(BaseCode code) { | ||
ApiResponse<T> response = new ApiResponse<>(true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), null); | ||
return ResponseEntity.status(code.getReasonHttpStatus().getHttpStatus()).body(response); | ||
} | ||
|
||
// 실패 응답 - 기본 메시지 | ||
public static <T> ResponseEntity<ApiResponse<T>> onFailure(BaseErrorCode code) { | ||
ApiResponse<T> response = new ApiResponse<>(false, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), null); | ||
return ResponseEntity.status(code.getReasonHttpStatus().getHttpStatus()).body(response); | ||
} | ||
|
||
public static <T> ResponseEntity<Object> onFailure(BaseErrorCode code, String message) { | ||
// 실패 응답 - 커스텀 메시지 (페이로드 없음) | ||
public static <T> ResponseEntity<ApiResponse<T>> onFailureWithCustomMessage(BaseErrorCode code, String customMessage) { | ||
ApiResponse<T> response = new ApiResponse<>(false, code.getReasonHttpStatus().getCode(), customMessage, null); | ||
return ResponseEntity.status(code.getReasonHttpStatus().getHttpStatus()).body(response); | ||
} | ||
|
||
// 실패 응답 - 오버라이드 메서드용 | ||
public static <T> ResponseEntity<Object> onFailureForOverrideMethod(BaseErrorCode code, String message) { | ||
ApiResponse<T> response = new ApiResponse<>(false, code.getReasonHttpStatus().getCode(), message, null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
여기 onFailureForOverrideMethod메서드도 ApiResponse로 바꾸면 좋을 것 같아요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아 이부분은 // HttpMessageNotReadableException 처리 (잘못된 JSON 형식)
@Override
public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
String errorMessage = "요청 본문을 읽을 수 없습니다. 올바른 JSON 형식이어야 합니다.";
logError("HttpMessageNotReadableException", ex);
return ApiResponse.onFailureForOverrideMethod(ErrorStatus._BAD_REQUEST, errorMessage);
} 위처럼 |
||
return ResponseEntity.status(code.getReasonHttpStatus().getHttpStatus()).body(response); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 키워드에 따른 결과 찾을 때 stream 많이 사용했는데 사용하기 간편하고 좋더라구요
filter, and, or 조건으로 쉽게 조건 붙일 수 있어서 사용하는 데 익숙해지면 좋을 것 같아요!!