Skip to content

Commit

Permalink
Merge pull request #112 from teamSynapse6/develop
Browse files Browse the repository at this point in the history
deploy: GPT를 사용하여 질문 비교, 그룹핑하여 담당자에게 전송
  • Loading branch information
gyehwan24 authored Jun 8, 2024
2 parents 9806703 + 004b8df commit 0cbcf83
Show file tree
Hide file tree
Showing 19 changed files with 463 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public interface AnswerRepository extends JpaRepository<Answer, Long>, AnswerQue
Integer countByQuestionIdAndAnswerType(Long questionId, QAType answerType);

List<Answer> findByQuestionId(Long questionId);

Answer findByQuestionIdAndAnswerType(Long questionId, QAType answerType);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.startingblock.domain.answer.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

public class AnswerRequestDto {

@Getter
@Builder
public static class AnswerRequest {
@Schema(type = "long", example = "1", description = "질문의 ID입니다.")
private Long questionId;
Expand Down
127 changes: 127 additions & 0 deletions src/main/java/com/startingblock/domain/gpt/application/GptService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.startingblock.domain.gpt.application;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.startingblock.domain.gpt.dto.DuplicateReq;
import com.startingblock.domain.gpt.dto.GptMessage;
import com.startingblock.domain.gpt.dto.GptRes;
import com.startingblock.domain.gpt.dto.GroupingQuestionReq;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static com.startingblock.domain.gpt.constant.GptConstant.*;


@Service
@RequiredArgsConstructor
@Slf4j
public class GptService {

private final RestTemplate restTemplate;

@Value(value = "${gpt.api-key}")
private String apiKey;

public Long checkDuplicateQuestion(final DuplicateReq duplicateReq) throws JsonProcessingException {
List<GptMessage> messages = new ArrayList<>();

// gpt 역할(프롬프트) 설정
messages.add(GptMessage.builder()
.role(SYSTEM)
.content(CHECK_DUPLICATE_PROMPT)
.build());

// 실제 요청
messages.add(GptMessage.builder()
.role(USER)
.content(duplicateReq.toJson())
.build());

log.info("Request Messages: {}", messages);

HashMap<String, Object> requestBody = createRequestBody(messages);

GptRes chatGptRes = getResponse(createHttpEntity(requestBody));

String response = chatGptRes.getChoices().get(0).getMessage().getContent();
log.info("Response: {}", response);

return Long.valueOf(response);
}

public String groupingQuestions(final GroupingQuestionReq questionList) throws JsonProcessingException {
List<GptMessage> messages = new ArrayList<>();

// gpt 역할(프롬프트) 설정
messages.add(GptMessage.builder()
.role(SYSTEM)
.content(CHECK_SIMILARITY_PROMPT)
.build());

// 실제 요청
messages.add(GptMessage.builder()
.role(USER)
.content(questionList.toJson())
.build());

log.info("Request Messages: {}", messages);

HashMap<String, Object> requestBody = createRequestBody(messages);

GptRes chatGptRes = getResponse(createHttpEntity(requestBody));

String response = chatGptRes.getChoices().get(0).getMessage().getContent();
log.info("Response: {}", response);

return response;
}

// GPT 에 요청할 파라미터를 만드는 메서드
private static HashMap<String, Object> createRequestBody(final List<GptMessage> messages) {
HashMap<String, Object> requestBody = new HashMap<>();
requestBody.put("model", CHAT_MODEL);
requestBody.put("messages", messages);
requestBody.put("max_tokens", MAX_TOKEN);
requestBody.put("temperature", TEMPERATURE);
return requestBody;
}

// api 호출에 필요한 Http Header를 만들고 HTTP 객체를 만드는 메서드
public HttpEntity<HashMap<String, Object>> createHttpEntity(final HashMap<String, Object> chatGptRequest){

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.parseMediaType(MEDIA_TYPE));
httpHeaders.add(AUTHORIZATION, BEARER + apiKey);
return new HttpEntity<>(chatGptRequest, httpHeaders);
}

// GPT API 요청후 response body를 받아오는 메서드
public GptRes getResponse(final HttpEntity<HashMap<String, Object>> httpEntity){

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
// 답변이 길어질 경우 TimeOut Error 발생하므로 time 설정
requestFactory.setConnectTimeout(60000);
requestFactory.setReadTimeout(60000); // 1min

restTemplate.setRequestFactory(requestFactory);
ResponseEntity<GptRes> responseEntity = restTemplate.exchange(
CHAT_URL,
HttpMethod.POST,
httpEntity,
GptRes.class);

return responseEntity.getBody();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.startingblock.domain.gpt.constant;


public final class GptConstant {

private GptConstant(){}
public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer ";
public static final String CHAT_MODEL = "gpt-4o";
public static final Integer MAX_TOKEN = 1000;
public static final String USER = "user";
public static final String SYSTEM = "system";
public static final Double TEMPERATURE = 0.3;
public static final String MEDIA_TYPE = "application/json; charset=UTF-8";
public static final String CHAT_URL = "https://api.openai.com/v1/chat/completions";

// 프롬프트
public static final String CHECK_DUPLICATE_PROMPT =
"""
당신은 창업 지원사업에 대한 질문을 기존의 질문들과 비교해주는 사람입니다.\s
[기존 질문]은 JSON 형태로, "questionId"는 질문의 ID값, "content"는 질문의 내용입니다.
newQuestion 을 oldQuestions 리스트의 "content"와 비교해서 유사한 질문이 있다면 유사한 질문의 "questionId"를, 없다면 0을 숫자만 출력하세요.
""";

public static final String CHECK_SIMILARITY_PROMPT =
"""
당신은 창업 지원사업에 대한 여러가지 질문들을 유사한 질문들끼리 묶어주는 사람입니다.
질문들은 JSON 형태로, "questionId"는 질문의 ID값, "content"는 질문의 내용입니다.
질문 리스트의 "content" 끼리 비교해서 유사한 질문이 있다면 하나의 질문으로 묶어주세요.
유사한 질문의 내용을 정리해서 "content"에 담아주고, 이때 사용한 질문들의 "questionId"를 "questionId" 배열에 담아서 JSON 형태로 출력하세요.
제가 의도하는 출력 형식은 다음과 같습니다. 반드시 아래 형식을 지키세요.
[{"questionId":[10001, 10002], "content":"제출일이 언제인가요?"},
{"questionId":[10003], "content":"마감기한 이후에 제출 시 어떻게 하나요?"}]
""";
}
25 changes: 25 additions & 0 deletions src/main/java/com/startingblock/domain/gpt/dto/DuplicateReq.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.startingblock.domain.gpt.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
@Builder
public class DuplicateReq {

@JsonProperty("oldQuestions")
private List<SimpleGPTQuestionReq> oldQuestions;

@JsonProperty("newQuestion")
private String newQuestion;

public String toJson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(this);
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/startingblock/domain/gpt/dto/GptMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.startingblock.domain.gpt.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GptMessage {

private String role;

private String content;
}
38 changes: 38 additions & 0 deletions src/main/java/com/startingblock/domain/gpt/dto/GptReq.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.startingblock.domain.gpt.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@NoArgsConstructor
public class GptReq implements Serializable {

private String model;

@JsonProperty("max_tokens")
private Integer maxTokens;

private Double temperature;

private Boolean stream;

private List<GptMessage> messages;

@JsonProperty("top_p")
private Double topP;

@Builder
public GptReq(String model, Integer maxTokens, Double temperature,
Boolean stream, List<GptMessage> messages) {
this.model = model;
this.maxTokens = maxTokens;
this.temperature = temperature;
this.stream = stream;
this.messages = messages;
}
}
46 changes: 46 additions & 0 deletions src/main/java/com/startingblock/domain/gpt/dto/GptRes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.startingblock.domain.gpt.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;

@Data
@NoArgsConstructor
public class GptRes {

private String id;

private String object;

private long created;

private String model;

private Usage usage;

private List<Choice> choices;

@Getter
@Setter
public static class Usage {
@JsonProperty("prompt_tokens")
private int promptTokens;
@JsonProperty("completion_tokens")
private int completionTokens;
@JsonProperty("total_tokens")
private int totalTokens;
}

@Getter
@Setter
public static class Choice {
private GptMessage message;
@JsonProperty("finish_reason")
private String finishReason;
private int index;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.startingblock.domain.gpt.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.startingblock.domain.question.dto.QuestionResponseDto;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
@Builder
public class GroupingQuestionReq {

@JsonProperty("questions")
private List<QuestionResponseDto.QuestionSimpleResponse> questions;

public String toJson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.startingblock.domain.gpt.dto;

import lombok.Getter;

import java.util.List;

@Getter
public class GroupingQuestionRes {

private List<Long> questionId;
private String content;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.startingblock.domain.gpt.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;

import java.io.Serializable;

@Getter
@Builder
public class SimpleGPTQuestionReq implements Serializable {

@JsonProperty("qid")
private Long questionId;

@JsonProperty("content")
private String content;
}
Loading

0 comments on commit 0cbcf83

Please sign in to comment.