-
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
[be/fix] ai 피드백 생성, 조회 수정 #287
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
38bae40
[be/fix] ai Controller 엔드포인트 및 dto 변환 수정
Gwanghyeon-k 4dd51a2
[be/fix] aiFeedback 생성 및 조회 수정
Gwanghyeon-k 176d729
[be/fix] 모든 피드백 조회 로직 수정
Gwanghyeon-k cfd07b9
[be/fix] ai 응답값 수정
Gwanghyeon-k 84891e2
[be/fix] feedbackServiceTest 임시 비활성화
Gwanghyeon-k File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
61 changes: 42 additions & 19 deletions
61
...end/src/main/java/com/techeer/backend/api/aifeedback/controller/AIFeedbackController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,55 @@ | ||
package com.techeer.backend.api.aifeedback.controller; | ||
|
||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import com.techeer.backend.api.aifeedback.converter.AIFeedbackConverter; | ||
import com.techeer.backend.api.aifeedback.domain.AIFeedback; | ||
import com.techeer.backend.api.aifeedback.dto.AIFeedbackResponse; | ||
import com.techeer.backend.api.aifeedback.service.AIFeedbackService; | ||
|
||
import com.techeer.backend.global.common.response.CommonResponse; | ||
import com.techeer.backend.global.success.SuccessCode; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@Tag(name = "AIFeedback", description = "AIFeedback 받기") | ||
@Tag(name = "AIFeedback", description = "AI 피드백 받기") | ||
@RestController | ||
@RequestMapping("/api/v1/aifeedbacks") | ||
public class AIFeedbackController { | ||
private final AIFeedbackService aifeedbackService; | ||
|
||
public AIFeedbackController(AIFeedbackService aifeedbackService) { | ||
this.aifeedbackService = aifeedbackService; | ||
} | ||
private final AIFeedbackService aifeedbackService; | ||
|
||
public AIFeedbackController(AIFeedbackService aifeedbackService) { | ||
this.aifeedbackService = aifeedbackService; | ||
} | ||
|
||
@Operation(summary = "AI 피드백 생성", description = "본인 이력서에 대한 AI 피드백을 진행합니다.") | ||
@PostMapping("/{resume_id}") | ||
public CommonResponse<AIFeedbackResponse> createFeedbackFromS3(@PathVariable("resume_id") Long resumeId) { | ||
AIFeedback feedback = aifeedbackService.generateAIFeedbackFromS3(resumeId); | ||
AIFeedbackResponse feedbackResponse = AIFeedbackConverter.toResponse(feedback); | ||
return CommonResponse.of(SuccessCode.CREATED, feedbackResponse); | ||
} | ||
|
||
@Operation(summary = "단일 피드백 조회", description = "피드백 ID를 통해 단일 AI 피드백을 조회합니다.") | ||
@GetMapping("/{aifeedback_id}") | ||
public CommonResponse<AIFeedbackResponse> getFeedbackById(@PathVariable("aifeedback_id") Long aifeedbackId) { | ||
AIFeedback aifeedback = aifeedbackService.getFeedbackById(aifeedbackId); | ||
AIFeedbackResponse response = AIFeedbackConverter.toResponse(aifeedback); | ||
return CommonResponse.of(SuccessCode.OK, response); | ||
} | ||
|
||
@Operation(summary = "AIFeedback 생성", description = "본인 이력서에 대한 ai 피드백을 진행합니다.") | ||
@PostMapping("/{resume_id}") | ||
public ResponseEntity<AIFeedbackResponse> createFeedbackFromS3(@PathVariable("resume_id") Long resumeId) { | ||
AIFeedbackResponse feedbackResponse = aifeedbackService.generateAIFeedbackFromS3(resumeId); | ||
return ResponseEntity.status(HttpStatus.CREATED).body(feedbackResponse); | ||
} | ||
@Operation(summary = "이력서별 피드백 목록 조회", description = "특정 이력서에 대한 모든 AI 피드백을 조회합니다.") | ||
@GetMapping("/resume/{resume_id}") | ||
public CommonResponse<List<AIFeedbackResponse>> getFeedbacksByResumeId(@PathVariable("resume_id") Long resumeId) { | ||
List<AIFeedback> feedbacks = aifeedbackService.getFeedbacksByResumeId(resumeId); | ||
List<AIFeedbackResponse> responses = feedbacks.stream() | ||
.map(feedback -> AIFeedbackConverter.toResponse(feedback)) | ||
.collect(Collectors.toList()); | ||
return CommonResponse.of(SuccessCode.OK, responses); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
backend/src/main/java/com/techeer/backend/api/aifeedback/converter/AIFeedbackConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.techeer.backend.api.aifeedback.converter; | ||
|
||
import com.techeer.backend.api.aifeedback.domain.AIFeedback; | ||
import com.techeer.backend.api.aifeedback.dto.AIFeedbackResponse; | ||
|
||
public class AIFeedbackConverter { | ||
public static AIFeedbackResponse toResponse(AIFeedback aifeedback) { | ||
if (aifeedback == null) { | ||
return null; | ||
} | ||
return AIFeedbackResponse.builder() | ||
.id(aifeedback.getId()) | ||
.resumeId(aifeedback.getResumeId()) | ||
.feedback(aifeedback.getFeedback()) | ||
.build(); | ||
} | ||
} | ||
|
6 changes: 5 additions & 1 deletion
6
...end/src/main/java/com/techeer/backend/api/aifeedback/repository/AIFeedbackRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,15 @@ | ||
package com.techeer.backend.api.aifeedback.repository; | ||
|
||
import com.techeer.backend.api.aifeedback.domain.AIFeedback; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface AIFeedbackRepository extends JpaRepository<AIFeedback, Long> { | ||
Optional<AIFeedback> findByResumeId(Long resumeId); | ||
List<AIFeedback> findByResumeId(Long resumeId); | ||
|
||
Optional<AIFeedback> findById(Long feedbackId); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 50 additions & 44 deletions
94
backend/src/main/java/com/techeer/backend/api/aifeedback/service/OpenAIService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,83 @@ | ||
package com.techeer.backend.api.aifeedback.service; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import org.apache.http.HttpResponse; | ||
import org.apache.http.client.config.RequestConfig; | ||
import org.apache.http.client.methods.HttpPost; | ||
import org.apache.http.entity.ContentType; | ||
import org.apache.http.entity.StringEntity; | ||
import org.apache.http.impl.client.CloseableHttpClient; | ||
import org.apache.http.impl.client.HttpClients; | ||
import org.apache.http.util.EntityUtils; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Service; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
@Service | ||
public class OpenAIService { | ||
|
||
@Value("${chatgpt.api.key}") | ||
private String apiKey; | ||
@Value("${chatgpt.api.key}") | ||
private String apiKey; | ||
|
||
@Value("${chatgpt.api.url}") | ||
private String apiUrl; | ||
|
||
@Value("${chatgpt.api.url}") | ||
private String apiUrl; | ||
private final CloseableHttpClient httpClient; | ||
private static final int TIMEOUT = 30000; | ||
|
||
private final CloseableHttpClient httpClient; | ||
private static final int TIMEOUT = 30000; | ||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
public OpenAIService() { | ||
RequestConfig requestConfig = RequestConfig.custom() | ||
.setConnectTimeout(TIMEOUT) | ||
.setSocketTimeout(TIMEOUT) | ||
.build(); | ||
|
||
public OpenAIService() { | ||
RequestConfig requestConfig = RequestConfig.custom() | ||
.setConnectTimeout(TIMEOUT) | ||
.setSocketTimeout(TIMEOUT) | ||
.build(); | ||
this.httpClient = HttpClients.custom() | ||
.setDefaultRequestConfig(requestConfig) | ||
.build(); | ||
} | ||
|
||
this.httpClient = HttpClients.custom() | ||
.setDefaultRequestConfig(requestConfig) | ||
.build(); | ||
} | ||
public String getAIFeedback(String resumeText) throws IOException { | ||
HttpPost httpPost = new HttpPost(apiUrl); | ||
httpPost.setHeader("Content-Type", "application/json"); | ||
httpPost.setHeader("Authorization", "Bearer " + apiKey); | ||
|
||
public String getAIFeedback(String resumeText) throws IOException { | ||
HttpPost httpPost = new HttpPost(apiUrl); | ||
httpPost.setHeader("Content-Type", "application/json"); | ||
httpPost.setHeader("Authorization", "Bearer " + apiKey); | ||
String jsonBody = createRequestBody(resumeText); | ||
StringEntity entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON); | ||
httpPost.setEntity(entity); | ||
|
||
// JSON 요청 본문 생성 | ||
StringEntity entity = new StringEntity(createRequestBody(resumeText)); | ||
httpPost.setEntity(entity); | ||
HttpResponse response = httpClient.execute(httpPost); | ||
int statusCode = response.getStatusLine().getStatusCode(); | ||
String responseBody = EntityUtils.toString(response.getEntity()); | ||
|
||
HttpResponse response = httpClient.execute(httpPost); | ||
int statusCode = response.getStatusLine().getStatusCode(); | ||
if (statusCode != 200) { | ||
throw new IOException("OpenAI API 호출 실패: 상태 코드 " + statusCode + ", 응답: " + responseBody); | ||
} | ||
return responseBody; | ||
} | ||
|
||
if (statusCode != 200) { | ||
String errorMessage = EntityUtils.toString(response.getEntity()); | ||
throw new IOException("OpenAI API 호출 실패: 상태 코드 " + statusCode + ", 응답: " + errorMessage); | ||
} | ||
|
||
return EntityUtils.toString(response.getEntity()); | ||
} | ||
// ObjectMapper를 사용하여 JSON 요청 본문 생성 | ||
private String createRequestBody(String resumeText) throws IOException { | ||
Map<String, Object> requestBody = new HashMap<>(); | ||
requestBody.put("model", "gpt-4"); | ||
requestBody.put("temperature", 0.7); | ||
|
||
// ObjectMapper를 사용하여 JSON 요청 본문 생성 | ||
private String createRequestBody(String resumeText) throws IOException { | ||
Map<String, Object> requestBody = new HashMap<>(); | ||
requestBody.put("model", "gpt-4o"); | ||
Map<String, String> message = new HashMap<>(); | ||
message.put("role", "user"); | ||
// 불필요한 공백이나 개행이 문제를 일으킬 수 있으므로 trim()으로 정리합니다. | ||
message.put("content", ("이 이력서에서 잘 작성된 부분과 개선해야 할 부분을 구체적으로 지적해 주세요. " + | ||
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. 여기에 예를들어 gpt한테 너는 IT 빅테크 기업(구글, 페이스북, AWS)면접관이야. 이런식으로 role 부여해주면 대답이 더 좋아져. 이런거 찾아보고 role 추가해도 좋을거 같아. |
||
"특히, 내용의 명확성, 경험 기술의 구체성, 그리고 부족한 스킬이나 프로젝트가 있는지에 대한 피드백을 제공해 주세요.\n" + resumeText).trim()); | ||
|
||
Map<String, String> message = new HashMap<>(); | ||
message.put("role", "user"); | ||
message.put("content", "\"이 이력서에서 잘 작성된 부분과 개선해야 할 부분을 구체적으로 지적해 주세요. 특히, 내용의 명확성, 경험 기술의 구체성, 그리고 부족한 스킬이나 프로젝트가 있는지에 대한 피드백을 제공해 주세요.\": \n" + resumeText); | ||
// messages는 List 또는 배열 형태여야 합니다. | ||
requestBody.put("messages", new Object[]{message}); | ||
|
||
requestBody.put("messages", new Object[] {message}); | ||
// JSON 문자열 생성 후 로그 출력 (디버깅용) | ||
String jsonBody = objectMapper.writeValueAsString(requestBody); | ||
return jsonBody; | ||
} | ||
|
||
return objectMapper.writeValueAsString(requestBody); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
🛠️ Refactor suggestion
Remove redundant findById method.
This method is redundant since JpaRepository already provides a findById method with the same signature. The method can be safely removed to reduce code duplication.
- Optional<AIFeedback> findById(Long feedbackId);
📝 Committable suggestion