Skip to content

Commit

Permalink
Merge branch 'develop' into iss-#40
Browse files Browse the repository at this point in the history
  • Loading branch information
choidongkuen authored Jan 13, 2024
2 parents 65e7087 + b2c20fc commit 648fdaa
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 47 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ apply from: "gradle/devtool.gradle"
apply from: "gradle/test.gradle"
apply from: "gradle/sonar.gradle"
apply from: "gradle/db.gradle"
apply from: "gradle/sentry.gradle"

allprojects {

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ public FriendsResponse findFriends(@PathVariable("userId") Long userId) {
}

@GetMapping("/interests")
public InterestQuestionResponse getInterestQuestion(@RequestParam("user-id") List<Long> userIds) {
return userService.getInterestQuestionByUserIds(userIds);
@ResponseStatus(HttpStatus.OK)
public InterestQuestionResponse getInterestQuestion(@RequestParam("user-id") List<Long> userIds,
@RequestParam("type") String balance) {
return userService.getInterestQuestionByUserIds(userIds, balance);
}

@DeleteMapping("/withdraw")
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/net/teumteum/user/domain/BalanceGameType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package net.teumteum.user.domain;

import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import net.teumteum.user.domain.response.InterestQuestionResponse;

public enum BalanceGameType {

BALANCE("balance", (users, interestQuestion) -> interestQuestion.getBalanceGame(users)),
STORY("story", (users, interestQuestion) -> interestQuestion.getStoryGame(users)),
;

private final String value;
private final BiFunction<List<User>, InterestQuestion, InterestQuestionResponse> behavior;

BalanceGameType(String value, BiFunction<List<User>, InterestQuestion, InterestQuestionResponse> behavior) {
this.value = value;
this.behavior = behavior;
}

public static BalanceGameType of(String value) {
return Arrays.stream(BalanceGameType.values())
.filter(type -> type.value.equals(value))
.findAny()
.orElseThrow(
() -> new IllegalArgumentException("\"" + value + "\" 에 해당하는 enum값을 찾을 수 없습니다.")
);
}

public InterestQuestionResponse getInterestQuestionResponse(List<User> users, InterestQuestion interestQuestion) {
return behavior.apply(users, interestQuestion);
}
}
8 changes: 5 additions & 3 deletions src/main/java/net/teumteum/user/domain/InterestQuestion.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package net.teumteum.user.domain;

import java.util.List;
import net.teumteum.user.domain.response.InterestQuestionResponse;
import net.teumteum.user.domain.response.BalanceQuestionResponse;
import net.teumteum.user.domain.response.StoryQuestionResponse;

@FunctionalInterface
public interface InterestQuestion {

InterestQuestionResponse getQuestion(List<User> users);
BalanceQuestionResponse getBalanceGame(List<User> users);

StoryQuestionResponse getStoryGame(List<User> users);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.teumteum.user.domain.response;

import java.util.List;

public record BalanceQuestionResponse(
String topic,
List<String> balanceQuestion
) implements InterestQuestionResponse {

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package net.teumteum.user.domain.response;

import java.util.List;

public record InterestQuestionResponse(
String topic,
List<String> balanceQuestion
) {
public interface InterestQuestionResponse {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.teumteum.user.domain.response;

public record StoryQuestionResponse(
String topic,
String story
) implements InterestQuestionResponse {

}
61 changes: 37 additions & 24 deletions src/main/java/net/teumteum/user/infra/GptInterestQuestion.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
import lombok.RequiredArgsConstructor;
import net.teumteum.user.domain.InterestQuestion;
import net.teumteum.user.domain.User;
import net.teumteum.user.domain.response.InterestQuestionResponse;
import net.teumteum.user.domain.response.BalanceQuestionResponse;
import net.teumteum.user.domain.response.StoryQuestionResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
Expand All @@ -32,16 +33,33 @@ public class GptInterestQuestion implements InterestQuestion {


@Override
public InterestQuestionResponse getQuestion(List<User> users) {
public BalanceQuestionResponse getBalanceGame(List<User> users) {
var interests = parseInterests(users);
var request = GptQuestionRequest.of(interests);
var request = GptQuestionRequest.balanceGame(interests);

return webClient.post()
.bodyValue(request)
.header(HttpHeaders.AUTHORIZATION, gptToken)
.exchangeToMono(response -> {
if (response.statusCode().is2xxSuccessful()) {
return response.bodyToMono(InterestQuestionResponse.class);
return response.bodyToMono(BalanceQuestionResponse.class);
}
return response.createError();
})
.retry(MAX_RETRY_COUNT)
.subscribeOn(Schedulers.fromExecutor(executorService))
.block(Duration.ofSeconds(5));
}

@Override
public StoryQuestionResponse getStoryGame(List<User> users) {
var interests = parseInterests(users);
var request = GptQuestionRequest.story(interests);

return webClient.post().bodyValue(request).header(HttpHeaders.AUTHORIZATION, gptToken)
.exchangeToMono(response -> {
if (response.statusCode().is2xxSuccessful()) {
return response.bodyToMono(StoryQuestionResponse.class);
}
return response.createError();
})
Expand Down Expand Up @@ -72,29 +90,24 @@ private record GptQuestionRequest(
private static final String LANGUAGE_MODEL = "gpt-3.5-turbo-1106";


private static GptQuestionRequest of(String interests) {
return new GptQuestionRequest(
LANGUAGE_MODEL,
List.of(Message.system(), Message.user(interests))
);
private static GptQuestionRequest balanceGame(String interests) {
return new GptQuestionRequest(LANGUAGE_MODEL, List.of(Message.balanceGame(), Message.user(interests)));
}

private record Message(
String role,
String content
) {
private static GptQuestionRequest story(String interests) {
return new GptQuestionRequest(LANGUAGE_MODEL, List.of(Message.story(), Message.user(interests)));
}

private static Message system() {
return new Message(
"system",
"You are a chatbot that receives the user's interests and creates common topics of interest"
+ " and balance games corresponding to the topics of interest in the form of sentences based on"
+ " the interests. At this time, only two choices for the balance game must be given, and the"
+ " choices must be separated by a comma The query results must be returned in JSON format"
+ " according to the form below and other The JSON value must be answered in Korean without"
+ " words. "
+ "{\\\"topic\\\": Topic of common interest, \\\"balanceQuestion\\\": [Balance game options]}"
);
private record Message(String role, String content) {

private static Message balanceGame() {
return new Message("system",
"당신은 사용자의 관심사들을 입력받아 관심사 게임을 응답하는 챗봇입니다.관심사 게임은 \"공통 관심 주제\"\"밸런스 게임의 질문 선택지\" 로 이루어져 있습니다. \"밸런스 게임의 질문 선택지\"는 문장형태로 이루어지며 상반된 각각 하나의 질문으로 무조건 2개 응답되어야 합니다. 이때, \"밸런스 게임의 질문 선택지\"는 각각 36자 이하로 생성되어야 합니다. 응답은 다음 JSON 형태로 응답해주세요. {\"topic\": 공통 관심 주제, \"balanceQuestion\": [밸런스 게임의 질문 선택지 2개]} 이때, 부가적인 설명없이 JSON만 응답해야하며, JSON의 VALUE는 모두 한국어로 응답해주세요.");
}

private static Message story() {
return new Message("system",
"당신은 사용자의 관심사들을 입력받아 관심사 게임을 응답하는 챗봇입니다. 관심사 게임은 \"공통 관심 주제\"\"관심 주제와 연관되는 질문\" 로 이루어져 있습니다.이때 \"관심 주제와 연관되는 질문\" 은 최대 76자로 제한합니다. 응답은 다음 JSON 형태로 형태로 응답해주세요. {\"topic\": 공통 관심 주제, \"story\": 관심 주제와 연관되는 질문} 이때, 부가적인 설명없이 JSON만 응답해야하며, JSON의 VALUE는 모두 한국어로 응답해주세요.");
}

private static Message user(String interests) {
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/net/teumteum/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import net.teumteum.core.security.service.RedisService;
import net.teumteum.user.domain.BalanceGameType;
import net.teumteum.user.domain.InterestQuestion;
import net.teumteum.user.domain.User;
import net.teumteum.user.domain.UserRepository;
Expand Down Expand Up @@ -77,15 +78,15 @@ private User getUser(Long userId) {
.orElseThrow(() -> new IllegalArgumentException("userId에 해당하는 user를 찾을 수 없습니다. \"" + userId + "\""));
}

public InterestQuestionResponse getInterestQuestionByUserIds(List<Long> userIds) {
public InterestQuestionResponse getInterestQuestionByUserIds(List<Long> userIds, String type) {
var users = userRepository.findAllById(userIds);
Assert.isTrue(users.size() >= 2,
() -> {
throw new IllegalArgumentException("userIds는 2개 이상 주어져야 합니다.");
}
);

return interestQuestion.getQuestion(users);
return BalanceGameType.of(type).getInterestQuestionResponse(users, interestQuestion);
}

private void deleteUser(User user) {
Expand Down
46 changes: 36 additions & 10 deletions src/test/java/net/teumteum/user/infra/GptInterestQuestionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import java.util.List;
import net.teumteum.user.domain.InterestQuestion;
import net.teumteum.user.domain.UserFixture;
import net.teumteum.user.domain.response.InterestQuestionResponse;
import net.teumteum.user.domain.response.BalanceQuestionResponse;
import net.teumteum.user.domain.response.StoryQuestionResponse;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
Expand All @@ -24,17 +25,16 @@ class GptInterestQuestionTest {
@Autowired
private InterestQuestion interestQuestion;


@Nested
@DisplayName("getQuestion 메소드는")
class GetQuestion_method {
@DisplayName("getBalanceGame 메소드는")
class GetBalanceGame_method {

@Test
@DisplayName("user 목록을 받아서, 관심 질문을 반환한다.")
@DisplayName("user 목록을 받아서, 밸런스 게임을 반환한다.")
void Return_balance_game_when_receive_user_list() {
// given
var users = List.of(UserFixture.getDefaultUser(), UserFixture.getDefaultUser());
var expected = new InterestQuestionResponse(
var expected = new BalanceQuestionResponse(
"프로그래머",
List.of("프론트엔드", "백엔드")
);
Expand All @@ -43,7 +43,7 @@ void Return_balance_game_when_receive_user_list() {
gptTestServer.enqueue(expected);

// when
var result = interestQuestion.getQuestion(users);
var result = interestQuestion.getBalanceGame(users);

// then
Assertions.assertThat(expected).isEqualTo(result);
Expand All @@ -54,7 +54,7 @@ void Return_balance_game_when_receive_user_list() {
void Do_retry_when_gpt_server_cannot_receive_interests_lists() {
// given
var users = List.of(UserFixture.getDefaultUser(), UserFixture.getDefaultUser());
var expected = new InterestQuestionResponse(
var expected = new BalanceQuestionResponse(
"프로그래머",
List.of("프론트엔드", "백엔드")
);
Expand All @@ -66,7 +66,7 @@ void Do_retry_when_gpt_server_cannot_receive_interests_lists() {
gptTestServer.enqueue(expected);

// when
var result = interestQuestion.getQuestion(users);
var result = interestQuestion.getBalanceGame(users);

// then
Assertions.assertThat(expected).isEqualTo(result);
Expand All @@ -84,10 +84,36 @@ void Throw_illegal_state_exception_exceed_5_time_to_get_common_interests() {
gptTestServer.enqueue400();

// when
var result = Assertions.catchException(() -> interestQuestion.getQuestion(users));
var result = Assertions.catchException(() -> interestQuestion.getBalanceGame(users));

// then
Assertions.assertThat(result.getClass()).isEqualTo(IllegalStateException.class);
}
}

@Nested
@DisplayName("getStoryGame 메소드는")
class GetStoryGame_method {

@Test
@DisplayName("user 목록을 받아서, 밸런스 게임을 반환한다.")
void Return_story_game_when_receive_user_list() {
// given
var users = List.of(UserFixture.getDefaultUser(), UserFixture.getDefaultUser());
var expected = new StoryQuestionResponse(
"프로그래머",
"어떤 프로그래머가 좋은 프로그래머 일까요?"
);

gptTestServer.enqueue400();
gptTestServer.enqueue400();
gptTestServer.enqueue(expected);

// when
var result = interestQuestion.getStoryGame(users);

// then
Assertions.assertThat(result).isEqualTo(expected);
}
}
}

0 comments on commit 648fdaa

Please sign in to comment.