diff --git a/src/main/java/UMC/career_mate/domain/chatgpt/dto/request/ChatGPTRequestDTO.java b/src/main/java/UMC/career_mate/domain/chatgpt/dto/request/ChatGPTRequestDTO.java deleted file mode 100644 index 8ecf1b5..0000000 --- a/src/main/java/UMC/career_mate/domain/chatgpt/dto/request/ChatGPTRequestDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package UMC.career_mate.domain.chatgpt.dto.request; - -import lombok.Builder; - -// TODO: 더미데이터용, 삭제 예정 -@Builder -public record ChatGPTRequestDTO( - String name, - String content -) { - -} diff --git a/src/main/java/UMC/career_mate/domain/chatgpt/service/ChatGptService.java b/src/main/java/UMC/career_mate/domain/chatgpt/service/ChatGptService.java index 75de3b9..a10df2b 100644 --- a/src/main/java/UMC/career_mate/domain/chatgpt/service/ChatGptService.java +++ b/src/main/java/UMC/career_mate/domain/chatgpt/service/ChatGptService.java @@ -1,7 +1,7 @@ package UMC.career_mate.domain.chatgpt.service; import UMC.career_mate.domain.chatgpt.dto.api.response.ChatCompletionResponse; -import UMC.career_mate.domain.chatgpt.dto.request.ChatGPTRequestDTO; +import UMC.career_mate.domain.recruit.dto.MemberTemplateAnswerDTO; import UMC.career_mate.domain.chatgpt.dto.api.request.GptRequest; import UMC.career_mate.domain.chatgpt.dto.api.request.GptRequest.Message; import UMC.career_mate.domain.recruit.enums.RecruitKeyword; @@ -36,19 +36,18 @@ public class ChatGptService { private static final String GPT_REQUEST_FORMAT_POSTFIX_FOR_RECRUIT_KEYWORD = "이 사람의 직무를 내가 제시한 보기들 중에서 하나만 골라서 답변해줘.\n" + - "직무 보기 :'BACKEND', 'BACKEND_SPRING', 'BACKEND_NODE', 'BACKEND_DJANGO', 'FRONTEND', 'DESIGNER', 'PM', " - + + "직무 보기 :'BACKEND', 'BACKEND_SPRING', 'BACKEND_NODE', 'BACKEND_DJANGO', 'FRONTEND', 'DESIGNER', 'PM', " + "이 중에서 적절한 직무가 없다면 'MISMATCH'로 답변해줘.\n" + "앞뒤 설명하지 말고 직무만 답변해줘."; - private static final String GPT_REQUEST_FORMAT_PREFIX_FOR_COMMENT = - " = 사용자 이름이고, 다음 이력서 데이터를 기반으로 사용자의 강점을 어필할 수 있고, " + - "어떤 포지션이 어울리는지 어떤 경험을 어필하면 좋을지 그런 내용들로 추천 조언 문구를 생성해줘. " + - "'~~한 경험이 있는 000님, ~~한 경험을 어필해보면 어때요?', 또는 '~~포지에 지원해보 건 어떨까요? ~~에 강점을 드러낼 수 있을 것 같아요.'" - + - "와 비슷한 형식이지만 꼭 이런 형식이 아니더라도 너만의 스타일로 문구를 생성해줘." + - "말투는 \"입니다\"같은 딱딱말 말투는 사용하지 말고, \"요.\"같이 부드럽게 표현해줘. " + - "답변은 문구만 답변해줘. 문구를 생성할 때 이력서 데이터의 회사 이름은 제외해줘. 답변에서 엔터나 - 같은건 빼줘."; + private static final String GPT_REQUEST_FORMAT_FOR_COMMENT = + "위 경험 데이터를 기반으로 사용자의 이름을 문구에 포함해서 사용자의 강점을 어필할 수 있고, " + + "친근한 말투로 어떤 포지션이 어울리는지, 어떤 경험을 어필하면 좋을지와 같은 내용으로 조언 문구를 생성해서 답변해줘. " + + "답변은 문구만 답변해줘. 문구를 생성할 때 내용에는 회사 이름은 제외해줘. " + + "답변에서 '-'는 빼줘."; + + private static final String GPT_SYSTEM_ROLE = "너는 취업 전문가로서 내가 보낸 경험 데이터를 기반으로 '~~한 경험이 있는 000님, ~~한 경험을 어필해보면 어때요?' 라는 느낌으로 사용자 맞춤형 추천 문구를 작성한다. " + + "문구의 말투는 '-니다'체를 사용하는 것이 아니라, '-요'체를 사용한다."; public int getCareerYear(String chatGptRequestContent) { GptRequest gptRequest = createGptRequest( @@ -69,19 +68,23 @@ public RecruitKeyword getRecruitKeyword(String chatGptRequestContent) { ObjectMapper om = new ObjectMapper(); String gptAnswer = getGptAnswer(om, gptRequest); - log.info("\ngptRequest : {}, gptAnswer : {} ", gptRequest, gptAnswer); + log.info("\ngptRequest : {}\ngptAnswer : {} ", gptRequest, gptAnswer); return RecruitKeyword.getRecruitKeywordFromGptAnswerJob(gptAnswer); } - public String getComment(ChatGPTRequestDTO chatGPTRequestDTO) { - GptRequest gptRequest = createGptRequest( - chatGPTRequestDTO.name() + GPT_REQUEST_FORMAT_PREFIX_FOR_COMMENT - + chatGPTRequestDTO.content()); + public String getComment(MemberTemplateAnswerDTO memberTemplateAnswerDTO) { + GptRequest gptRequest = createGptRequestWithSystemRole( + memberTemplateAnswerDTO.name() + "\n" + memberTemplateAnswerDTO.content() + + GPT_REQUEST_FORMAT_FOR_COMMENT + memberTemplateAnswerDTO.content()); ObjectMapper om = new ObjectMapper(); - return getGptAnswer(om, gptRequest); + String gptAnswer = getGptAnswer(om, gptRequest); + + log.info("\ngptRequest : {}\ngptAnswer : {} ", gptRequest, gptAnswer); + + return gptAnswer; } private GptRequest createGptRequest(String content) { @@ -97,6 +100,24 @@ private GptRequest createGptRequest(String content) { .build(); } + private GptRequest createGptRequestWithSystemRole(String content) { + Message systemMessage = Message.builder() + .role("system") + .content(GPT_SYSTEM_ROLE) + .build(); + + Message userMessage = Message.builder() + .role("user") + .content(content) + .build(); + + return GptRequest.builder() + .model("gpt-3.5-turbo") + .stream(false) + .messages(List.of(systemMessage, userMessage)) + .build(); + } + private String getGptAnswer(ObjectMapper om, GptRequest gptRequest) { String requestBody = createRequestBody(om, gptRequest); diff --git a/src/main/java/UMC/career_mate/domain/recruit/converter/RecruitConverter.java b/src/main/java/UMC/career_mate/domain/recruit/converter/RecruitConverter.java index f15fc95..1065363 100644 --- a/src/main/java/UMC/career_mate/domain/recruit/converter/RecruitConverter.java +++ b/src/main/java/UMC/career_mate/domain/recruit/converter/RecruitConverter.java @@ -1,7 +1,9 @@ package UMC.career_mate.domain.recruit.converter; +import UMC.career_mate.domain.member.Member; import UMC.career_mate.domain.recruit.Recruit; import UMC.career_mate.domain.recruit.dto.FilterConditionDTO; +import UMC.career_mate.domain.recruit.dto.MemberTemplateAnswerDTO; import UMC.career_mate.domain.recruit.dto.api.SaraminResponseDTO.Job; import UMC.career_mate.domain.recruit.dto.response.RecommendRecruitDTO; import UMC.career_mate.domain.recruit.dto.response.RecruitInfoDTO; @@ -87,6 +89,13 @@ public static FilterConditionDTO toFilterConditionDTO(RecruitKeyword recruitKeyw .build(); } + public static MemberTemplateAnswerDTO toMemberTemplateAnswerDTO(Member member, String content) { + return MemberTemplateAnswerDTO.builder() + .name("이름 : " + member.getName()) + .content(content) + .build(); + } + private static String formatDeadLine(Recruit recruit) { LocalDate today = LocalDate.now(); LocalDate targetDate = recruit.getDeadLine().toLocalDate(); diff --git a/src/main/java/UMC/career_mate/domain/recruit/dto/MemberTemplateAnswerDTO.java b/src/main/java/UMC/career_mate/domain/recruit/dto/MemberTemplateAnswerDTO.java new file mode 100644 index 0000000..90afb2a --- /dev/null +++ b/src/main/java/UMC/career_mate/domain/recruit/dto/MemberTemplateAnswerDTO.java @@ -0,0 +1,11 @@ +package UMC.career_mate.domain.recruit.dto; + +import lombok.Builder; + +@Builder +public record MemberTemplateAnswerDTO( + String name, + String content +) { + +} diff --git a/src/main/java/UMC/career_mate/domain/recruit/service/RecruitQueryService.java b/src/main/java/UMC/career_mate/domain/recruit/service/RecruitQueryService.java index eec9ee8..1c61ef4 100644 --- a/src/main/java/UMC/career_mate/domain/recruit/service/RecruitQueryService.java +++ b/src/main/java/UMC/career_mate/domain/recruit/service/RecruitQueryService.java @@ -3,7 +3,7 @@ import UMC.career_mate.domain.answer.Answer; import UMC.career_mate.domain.answer.repository.AnswerRepository; import UMC.career_mate.domain.chatgpt.GptAnswer; -import UMC.career_mate.domain.chatgpt.dto.request.ChatGPTRequestDTO; +import UMC.career_mate.domain.recruit.dto.MemberTemplateAnswerDTO; import UMC.career_mate.domain.chatgpt.service.GptAnswerCommandService; import UMC.career_mate.domain.recruit.dto.FilterConditionDTO; import UMC.career_mate.domain.chatgpt.repository.GptAnswerRepository; @@ -58,6 +58,7 @@ public class RecruitQueryService { private final static String INTERN_PREFIX = "인턴 경험 데이터 : "; private final static String PROJECT_QUESTION_CONTENT_PERIOD = "기간"; private final static String INTERN_QUESTION_CONTENT_PERIOD = "근무기간"; + private final static String TEMPLATE_ANSWER_IS_NULL_OR_EMPTY_COMMENT = "프로젝트 또는 인턴 템플릿에 대해 답변을 작성하고, Chat GPT의 코멘트를 받아보세요!"; public PageResponseDTO> getRecommendRecruitList(int page, int size, RecruitSortType recruitSortType, Member member) { @@ -80,10 +81,7 @@ public RecruitInfoDTO findRecruitInfo(Member member, Long recruitId) { Recruit recruit = recruitRepository.findById(recruitId) .orElseThrow(() -> new GeneralException(CommonErrorCode.NOT_FOUND_RECRUIT)); - // TODO: db에서 사용자의 템플릿 데이터 가져오는걸로 수정 - ChatGPTRequestDTO chatGPTRequestDTO = createDummyData(member); - - String comment = chatGptService.getComment(chatGPTRequestDTO); + String comment = createComment(member); return RecruitConverter.toRecruitInfoDTO(comment, recruit); } @@ -204,6 +202,39 @@ private RecruitKeyword calculateRecruitKeyword(Member member) { return recruitKeyword; } + private String createComment(Member member) { + Job job = member.getJob(); + + Map> templateTypeByAnswers = getTemplateTypeByAnswers(member, + job); + + List projectAnswers = templateTypeByAnswers.getOrDefault( + TemplateType.PROJECT_EXPERIENCE, List.of()); + List internAnswers = templateTypeByAnswers.getOrDefault( + TemplateType.INTERN_EXPERIENCE, List.of()); + + if (projectAnswers.isEmpty() && internAnswers.isEmpty()) { + log.info("(프로젝트, 인턴) 템플릿 답변 없이, 채용 공고 요약 페이지 조회하는 경우 -> answer 생성 안된 경우"); + return TEMPLATE_ANSWER_IS_NULL_OR_EMPTY_COMMENT; + } + + StringBuilder contentBuilder = new StringBuilder(); + int projectEnterCnt = createChatGptRequestContent(contentBuilder, projectAnswers, + PROJECT_PREFIX, PROJECT_QUESTION_CONTENT_PERIOD); + int internEnterCnt = createChatGptRequestContent(contentBuilder, internAnswers, + INTERN_PREFIX, INTERN_QUESTION_CONTENT_PERIOD); + + if (isEmptyContentBuilder(contentBuilder, projectEnterCnt, internEnterCnt)) { + log.info("최종 데이터가 기본 틀 데이터를 제외하고 빈 문자열인 경우"); + return TEMPLATE_ANSWER_IS_NULL_OR_EMPTY_COMMENT; + } + + MemberTemplateAnswerDTO memberTemplateAnswerDTO = RecruitConverter.toMemberTemplateAnswerDTO( + member, contentBuilder.toString()); + + return chatGptService.getComment(memberTemplateAnswerDTO); + } + private Map> getTemplateTypeByAnswers(Member member, Job job) { List findAnswers = answerRepository.findByMemberAndTemplateTypesAndJob(member, List.of(TemplateType.PROJECT_EXPERIENCE, TemplateType.INTERN_EXPERIENCE), job); @@ -253,12 +284,4 @@ private PageResponseDTO> createPageResponseDTO(int pag .result(recommendRecruitDTOList) .build(); } - - private ChatGPTRequestDTO createDummyData(Member member) { - return ChatGPTRequestDTO.builder() - .name(member.getName()) - .content( - "네이버 / 백엔드 개발팀, 백엔드 개발자, 2023.06.01~2023.12.01, Spring 대규모 데이터 처리 및 최적화 과정을 통해 성능 개선의 중요성을 배웠습니다., 사용자 데이터를 기반으로 한 개인화 서비스 개발에 참여하며, 데이터의 가치를 깊이 이해할 수 있었습니다., 대규모 시스템 아키텍처 설계 경험을 통해 기술적인 자신감이 크게 향상되었습니다.") - .build(); - } }