Skip to content

Commit 004b8df

Browse files
authored
Merge pull request #111 from teamSynapse6/feature/109-web
feat: gpt를 사용하여 기존 질문과 비교, 그룹핑하여 담당자에게 전송 기능을 구현
2 parents 291ace7 + 978ea21 commit 004b8df

File tree

19 files changed

+463
-12
lines changed

19 files changed

+463
-12
lines changed

src/main/java/com/startingblock/domain/answer/domain/repository/AnswerRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ public interface AnswerRepository extends JpaRepository<Answer, Long>, AnswerQue
1111
Integer countByQuestionIdAndAnswerType(Long questionId, QAType answerType);
1212

1313
List<Answer> findByQuestionId(Long questionId);
14+
15+
Answer findByQuestionIdAndAnswerType(Long questionId, QAType answerType);
1416
}

src/main/java/com/startingblock/domain/answer/dto/AnswerRequestDto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.startingblock.domain.answer.dto;
22

33
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.Builder;
45
import lombok.Getter;
56

67
import java.util.List;
78

89
public class AnswerRequestDto {
910

1011
@Getter
12+
@Builder
1113
public static class AnswerRequest {
1214
@Schema(type = "long", example = "1", description = "질문의 ID입니다.")
1315
private Long questionId;
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.startingblock.domain.gpt.application;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.startingblock.domain.gpt.dto.DuplicateReq;
5+
import com.startingblock.domain.gpt.dto.GptMessage;
6+
import com.startingblock.domain.gpt.dto.GptRes;
7+
import com.startingblock.domain.gpt.dto.GroupingQuestionReq;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.http.HttpEntity;
12+
import org.springframework.http.HttpHeaders;
13+
import org.springframework.http.HttpMethod;
14+
import org.springframework.http.MediaType;
15+
import org.springframework.http.ResponseEntity;
16+
import org.springframework.http.client.SimpleClientHttpRequestFactory;
17+
import org.springframework.stereotype.Service;
18+
import org.springframework.web.client.RestTemplate;
19+
20+
import java.util.ArrayList;
21+
import java.util.HashMap;
22+
import java.util.List;
23+
24+
import static com.startingblock.domain.gpt.constant.GptConstant.*;
25+
26+
27+
@Service
28+
@RequiredArgsConstructor
29+
@Slf4j
30+
public class GptService {
31+
32+
private final RestTemplate restTemplate;
33+
34+
@Value(value = "${gpt.api-key}")
35+
private String apiKey;
36+
37+
public Long checkDuplicateQuestion(final DuplicateReq duplicateReq) throws JsonProcessingException {
38+
List<GptMessage> messages = new ArrayList<>();
39+
40+
// gpt 역할(프롬프트) 설정
41+
messages.add(GptMessage.builder()
42+
.role(SYSTEM)
43+
.content(CHECK_DUPLICATE_PROMPT)
44+
.build());
45+
46+
// 실제 요청
47+
messages.add(GptMessage.builder()
48+
.role(USER)
49+
.content(duplicateReq.toJson())
50+
.build());
51+
52+
log.info("Request Messages: {}", messages);
53+
54+
HashMap<String, Object> requestBody = createRequestBody(messages);
55+
56+
GptRes chatGptRes = getResponse(createHttpEntity(requestBody));
57+
58+
String response = chatGptRes.getChoices().get(0).getMessage().getContent();
59+
log.info("Response: {}", response);
60+
61+
return Long.valueOf(response);
62+
}
63+
64+
public String groupingQuestions(final GroupingQuestionReq questionList) throws JsonProcessingException {
65+
List<GptMessage> messages = new ArrayList<>();
66+
67+
// gpt 역할(프롬프트) 설정
68+
messages.add(GptMessage.builder()
69+
.role(SYSTEM)
70+
.content(CHECK_SIMILARITY_PROMPT)
71+
.build());
72+
73+
// 실제 요청
74+
messages.add(GptMessage.builder()
75+
.role(USER)
76+
.content(questionList.toJson())
77+
.build());
78+
79+
log.info("Request Messages: {}", messages);
80+
81+
HashMap<String, Object> requestBody = createRequestBody(messages);
82+
83+
GptRes chatGptRes = getResponse(createHttpEntity(requestBody));
84+
85+
String response = chatGptRes.getChoices().get(0).getMessage().getContent();
86+
log.info("Response: {}", response);
87+
88+
return response;
89+
}
90+
91+
// GPT 에 요청할 파라미터를 만드는 메서드
92+
private static HashMap<String, Object> createRequestBody(final List<GptMessage> messages) {
93+
HashMap<String, Object> requestBody = new HashMap<>();
94+
requestBody.put("model", CHAT_MODEL);
95+
requestBody.put("messages", messages);
96+
requestBody.put("max_tokens", MAX_TOKEN);
97+
requestBody.put("temperature", TEMPERATURE);
98+
return requestBody;
99+
}
100+
101+
// api 호출에 필요한 Http Header를 만들고 HTTP 객체를 만드는 메서드
102+
public HttpEntity<HashMap<String, Object>> createHttpEntity(final HashMap<String, Object> chatGptRequest){
103+
104+
HttpHeaders httpHeaders = new HttpHeaders();
105+
httpHeaders.setContentType(MediaType.parseMediaType(MEDIA_TYPE));
106+
httpHeaders.add(AUTHORIZATION, BEARER + apiKey);
107+
return new HttpEntity<>(chatGptRequest, httpHeaders);
108+
}
109+
110+
// GPT API 요청후 response body를 받아오는 메서드
111+
public GptRes getResponse(final HttpEntity<HashMap<String, Object>> httpEntity){
112+
113+
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
114+
// 답변이 길어질 경우 TimeOut Error 발생하므로 time 설정
115+
requestFactory.setConnectTimeout(60000);
116+
requestFactory.setReadTimeout(60000); // 1min
117+
118+
restTemplate.setRequestFactory(requestFactory);
119+
ResponseEntity<GptRes> responseEntity = restTemplate.exchange(
120+
CHAT_URL,
121+
HttpMethod.POST,
122+
httpEntity,
123+
GptRes.class);
124+
125+
return responseEntity.getBody();
126+
}
127+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.startingblock.domain.gpt.constant;
2+
3+
4+
public final class GptConstant {
5+
6+
private GptConstant(){}
7+
public static final String AUTHORIZATION = "Authorization";
8+
public static final String BEARER = "Bearer ";
9+
public static final String CHAT_MODEL = "gpt-4o";
10+
public static final Integer MAX_TOKEN = 1000;
11+
public static final String USER = "user";
12+
public static final String SYSTEM = "system";
13+
public static final Double TEMPERATURE = 0.3;
14+
public static final String MEDIA_TYPE = "application/json; charset=UTF-8";
15+
public static final String CHAT_URL = "https://api.openai.com/v1/chat/completions";
16+
17+
// 프롬프트
18+
public static final String CHECK_DUPLICATE_PROMPT =
19+
"""
20+
당신은 창업 지원사업에 대한 질문을 기존의 질문들과 비교해주는 사람입니다.\s
21+
[기존 질문]은 JSON 형태로, "questionId"는 질문의 ID값, "content"는 질문의 내용입니다.
22+
newQuestion 을 oldQuestions 리스트의 "content"와 비교해서 유사한 질문이 있다면 유사한 질문의 "questionId"를, 없다면 0을 숫자만 출력하세요.
23+
""";
24+
25+
public static final String CHECK_SIMILARITY_PROMPT =
26+
"""
27+
당신은 창업 지원사업에 대한 여러가지 질문들을 유사한 질문들끼리 묶어주는 사람입니다.
28+
질문들은 JSON 형태로, "questionId"는 질문의 ID값, "content"는 질문의 내용입니다.
29+
질문 리스트의 "content" 끼리 비교해서 유사한 질문이 있다면 하나의 질문으로 묶어주세요.
30+
유사한 질문의 내용을 정리해서 "content"에 담아주고, 이때 사용한 질문들의 "questionId"를 "questionId" 배열에 담아서 JSON 형태로 출력하세요.
31+
제가 의도하는 출력 형식은 다음과 같습니다. 반드시 아래 형식을 지키세요.
32+
[{"questionId":[10001, 10002], "content":"제출일이 언제인가요?"},
33+
{"questionId":[10003], "content":"마감기한 이후에 제출 시 어떻게 하나요?"}]
34+
""";
35+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.startingblock.domain.gpt.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
9+
import java.util.List;
10+
11+
@Getter
12+
@Builder
13+
public class DuplicateReq {
14+
15+
@JsonProperty("oldQuestions")
16+
private List<SimpleGPTQuestionReq> oldQuestions;
17+
18+
@JsonProperty("newQuestion")
19+
private String newQuestion;
20+
21+
public String toJson() throws JsonProcessingException {
22+
ObjectMapper mapper = new ObjectMapper();
23+
return mapper.writeValueAsString(this);
24+
}
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.startingblock.domain.gpt.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
8+
@Data
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
@Builder
12+
public class GptMessage {
13+
14+
private String role;
15+
16+
private String content;
17+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.startingblock.domain.gpt.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
8+
import java.io.Serializable;
9+
import java.util.List;
10+
11+
@Data
12+
@NoArgsConstructor
13+
public class GptReq implements Serializable {
14+
15+
private String model;
16+
17+
@JsonProperty("max_tokens")
18+
private Integer maxTokens;
19+
20+
private Double temperature;
21+
22+
private Boolean stream;
23+
24+
private List<GptMessage> messages;
25+
26+
@JsonProperty("top_p")
27+
private Double topP;
28+
29+
@Builder
30+
public GptReq(String model, Integer maxTokens, Double temperature,
31+
Boolean stream, List<GptMessage> messages) {
32+
this.model = model;
33+
this.maxTokens = maxTokens;
34+
this.temperature = temperature;
35+
this.stream = stream;
36+
this.messages = messages;
37+
}
38+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.startingblock.domain.gpt.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Data;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
import lombok.Setter;
8+
9+
import java.util.List;
10+
11+
@Data
12+
@NoArgsConstructor
13+
public class GptRes {
14+
15+
private String id;
16+
17+
private String object;
18+
19+
private long created;
20+
21+
private String model;
22+
23+
private Usage usage;
24+
25+
private List<Choice> choices;
26+
27+
@Getter
28+
@Setter
29+
public static class Usage {
30+
@JsonProperty("prompt_tokens")
31+
private int promptTokens;
32+
@JsonProperty("completion_tokens")
33+
private int completionTokens;
34+
@JsonProperty("total_tokens")
35+
private int totalTokens;
36+
}
37+
38+
@Getter
39+
@Setter
40+
public static class Choice {
41+
private GptMessage message;
42+
@JsonProperty("finish_reason")
43+
private String finishReason;
44+
private int index;
45+
}
46+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.startingblock.domain.gpt.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.startingblock.domain.question.dto.QuestionResponseDto;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
10+
import java.util.List;
11+
12+
@Getter
13+
@Builder
14+
public class GroupingQuestionReq {
15+
16+
@JsonProperty("questions")
17+
private List<QuestionResponseDto.QuestionSimpleResponse> questions;
18+
19+
public String toJson() throws JsonProcessingException {
20+
ObjectMapper mapper = new ObjectMapper();
21+
return mapper.writeValueAsString(this);
22+
}
23+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.startingblock.domain.gpt.dto;
2+
3+
import lombok.Getter;
4+
5+
import java.util.List;
6+
7+
@Getter
8+
public class GroupingQuestionRes {
9+
10+
private List<Long> questionId;
11+
private String content;
12+
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.startingblock.domain.gpt.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
import java.io.Serializable;
8+
9+
@Getter
10+
@Builder
11+
public class SimpleGPTQuestionReq implements Serializable {
12+
13+
@JsonProperty("qid")
14+
private Long questionId;
15+
16+
@JsonProperty("content")
17+
private String content;
18+
}

0 commit comments

Comments
 (0)