[REFACTOR] 프롬프트 수정#356
Conversation
Summary by CodeRabbit릴리스 노트
Walkthrough이 PR은 GPT/Bedrock 클라이언트의 온도 설정을 3개 필드로 분산하고, 응답 처리를 함수 호출 기반으로 전환하며, 평가 피드백 구조를 문자열에서 구조화된 객체로 재설계합니다. Changes설정 및 기본 클라이언트 구조
Bedrock Converse 클라이언트 강화
GPT 클라이언트 생성자 및 온도 통합
함수 호출 기반 요청/응답 구조
피드백 구조 재설계 및 응답 처리
프롬프트 템플릿 분해 및 재구성
Bedrock 클라이언트 온도 통합 및 응답 처리
Resume 평가 도메인 및 서비스 계층 변경
AnswerFeedback 요청 구조 및 테스트 픽스처
🎯 3 (Moderate) | ⏱️ ~25 minutesPossibly Related PRs
Suggested Reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Test Results 51 files 51 suites 1m 24s ⏱️ Results for commit 76b49e0. ♻️ This comment has been updated with latest results. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이번 PR은 LLM과의 상호작용을 더 안정적이고 유연하게 만들기 위한 프롬프트 리팩토링과 설정 최적화를 수행했습니다. 특히 프롬프트의 파편화된 부분을 관리하기 쉽게 분리하고, 모델의 출력 제어를 위해 단계별 temperature 설정을 도입했습니다. 또한 이력서 평가 데이터의 저장 구조를 JSON으로 변경하여 데이터 활용도를 높이고, 평가 로직을 응답 객체 내부에 정의하여 비즈니스 로직의 응집도를 높였습니다. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. 프롬프트 흩어진 조각 모아 구조를 잡으니 깔끔하네 온도 조절로 답변 다듬고 데이터는 JSON으로 담으니 코드의 흐름이 한결 가볍네 Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request refactors the LLM integration for GPT and Bedrock, transitioning to structured function calling and introducing granular temperature controls for evaluation, generation, and feedback. Prompts are now managed through modular fragments, and the database schema has been updated to store evaluation details as JSON arrays using a new JPA converter. Reviewer feedback highlights a critical migration risk when converting existing text columns to JSON, a maintenance concern regarding duplicated scoring weights, and a potential runtime issue when using immutable lists in the JPA converter.
| ALTER TABLE resume_evaluation | ||
| MODIFY COLUMN technical_skills_reason JSON NULL, | ||
| MODIFY COLUMN technical_skills_improvements JSON NULL, | ||
| MODIFY COLUMN project_experience_reason JSON NULL, | ||
| MODIFY COLUMN project_experience_improvements JSON NULL, | ||
| MODIFY COLUMN problem_solving_reason JSON NULL, | ||
| MODIFY COLUMN problem_solving_improvements JSON NULL, | ||
| MODIFY COLUMN career_growth_reason JSON NULL, | ||
| MODIFY COLUMN career_growth_improvements JSON NULL, | ||
| MODIFY COLUMN documentation_reason JSON NULL, | ||
| MODIFY COLUMN documentation_improvements JSON NULL; |
| 0.30 * technicalSkills.score() | ||
| + 0.25 * projectExperience.score() | ||
| + 0.20 * problemSolving.score() | ||
| + 0.15 * careerGrowth.score() | ||
| + 0.10 * documentation.score() |
| return List.of(); | ||
| } | ||
| try { |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java (1)
70-87: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
buildEnd()function-call arguments에reasoning을 포함하세요.Line 70-87의 end arguments에는
overall_summary는 있지만reasoning이 없습니다.GptRequest.createEndParams()의 required 계약(reasoning,rank,feedback,overall_summary)과 불일치라 테스트 신뢰도가 떨어집니다.As per coding guidelines, "Create fixtures using global/fixture/{domain}/XxxFixtureBuilder pattern with static builder() method and fluent setters with sensible defaults".🔧 Proposed fix
String arguments = """ { + "reasoning": "%s", "rank": "%s", "feedback": "%s", "overall_summary": { "strengths": "%s", "improvements": "%s", "learning_direction": "%s" } } """.formatted( + "마지막 답변 평가 근거와 전체 면접 종합 평가 근거입니다.", answerRank != null ? answerRank.name() : "A", feedback != null ? feedback : "좋은 답변입니다.", strengths != null ? strengths : "전체적으로 답변이 명확합니다.", improvements != null ? improvements : "구체 사례를 더 보강하면 좋겠습니다.", learningDirection != null ? learningDirection : "기초 개념 심화 학습을 권장합니다." );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java` around lines 70 - 87, The buildEnd() return payload in GptResponseFixtureBuilder is missing the required "reasoning" field; update the multi-line arguments string passed to create(...) (the block that currently includes "rank","feedback","overall_summary") to also include a "reasoning" key and populate it from the fixture's reasoning field (use the same null-coalescing pattern as for answerRank/feedback/strengths/etc.), so the produced object matches GptRequest.createEndParams()’s contract ("reasoning", "rank", "feedback", "overall_summary") and tests remain consistent.src/main/java/com/samhap/kokomen/resume/domain/ResumeEvaluation.java (1)
134-163:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
complete()에 점수/리스트 무결성 검증을 추가해 주세요.Line 134-163에서 입력값 검증 없이 저장되어, 잘못된 점수 범위나
null리스트가 그대로 영속화될 수 있습니다. 최소한 점수(0~100)와 리스트 null 방어는 엔티티 경계에서 보장하는 것이 안전합니다.변경 제안
public void complete(int technicalSkillsScore, List<String> technicalSkillsReason, @@ int totalScore, String totalFeedback) { + validateScore(technicalSkillsScore, "technicalSkillsScore"); + validateScore(projectExperienceScore, "projectExperienceScore"); + validateScore(problemSolvingScore, "problemSolvingScore"); + validateScore(careerGrowthScore, "careerGrowthScore"); + validateScore(documentationScore, "documentationScore"); + validateScore(totalScore, "totalScore"); + this.state = ResumeEvaluationState.COMPLETED; this.technicalSkillsScore = technicalSkillsScore; - this.technicalSkillsReason = technicalSkillsReason; - this.technicalSkillsImprovements = technicalSkillsImprovements; + this.technicalSkillsReason = technicalSkillsReason == null ? List.of() : List.copyOf(technicalSkillsReason); + this.technicalSkillsImprovements = technicalSkillsImprovements == null ? List.of() : List.copyOf(technicalSkillsImprovements); this.projectExperienceScore = projectExperienceScore; - this.projectExperienceReason = projectExperienceReason; - this.projectExperienceImprovements = projectExperienceImprovements; + this.projectExperienceReason = projectExperienceReason == null ? List.of() : List.copyOf(projectExperienceReason); + this.projectExperienceImprovements = projectExperienceImprovements == null ? List.of() : List.copyOf(projectExperienceImprovements); this.problemSolvingScore = problemSolvingScore; - this.problemSolvingReason = problemSolvingReason; - this.problemSolvingImprovements = problemSolvingImprovements; + this.problemSolvingReason = problemSolvingReason == null ? List.of() : List.copyOf(problemSolvingReason); + this.problemSolvingImprovements = problemSolvingImprovements == null ? List.of() : List.copyOf(problemSolvingImprovements); this.careerGrowthScore = careerGrowthScore; - this.careerGrowthReason = careerGrowthReason; - this.careerGrowthImprovements = careerGrowthImprovements; + this.careerGrowthReason = careerGrowthReason == null ? List.of() : List.copyOf(careerGrowthReason); + this.careerGrowthImprovements = careerGrowthImprovements == null ? List.of() : List.copyOf(careerGrowthImprovements); this.documentationScore = documentationScore; - this.documentationReason = documentationReason; - this.documentationImprovements = documentationImprovements; + this.documentationReason = documentationReason == null ? List.of() : List.copyOf(documentationReason); + this.documentationImprovements = documentationImprovements == null ? List.of() : List.copyOf(documentationImprovements); this.totalScore = totalScore; this.totalFeedback = totalFeedback; } + + private void validateScore(int score, String fieldName) { + if (score < 0 || score > 100) { + throw new IllegalArgumentException(fieldName + " must be between 0 and 100"); + } + }As per coding guidelines "Use
@Validannotation in DTOs for validation, entity-level validation in constructors, business validation in service layer".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/samhap/kokomen/resume/domain/ResumeEvaluation.java` around lines 134 - 163, In ResumeEvaluation.complete, add entity-level input validation: verify each score parameter (technicalSkillsScore, projectExperienceScore, problemSolvingScore, careerGrowthScore, documentationScore, totalScore) is within 0-100 and throw an IllegalArgumentException with a clear message if out of range; for each list parameter (technicalSkillsReason, technicalSkillsImprovements, projectExperienceReason, projectExperienceImprovements, problemSolvingReason, problemSolvingImprovements, careerGrowthReason, careerGrowthImprovements, documentationReason, documentationImprovements) defend against null by replacing with empty immutable/defensive copies (e.g., Collections.emptyList() or new ArrayList<>(list)) and store defensive copies on the entity to avoid external mutation; keep the state set to ResumeEvaluationState.COMPLETED only after validation passes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseProperties.java`:
- Around line 13-15: The three nullable Float properties evaluationTemperature,
generationTemperature and feedbackTemperature in BedrockConverseProperties can
be null and cause an NPE when passed to BedrockConverseClient.converse(...,
float temperature); fix by making them primitive floats with sensible defaults
and enabling validation on the properties class: change the field types
evaluationTemperature/generationTemperature/feedbackTemperature to primitive
float with explicit default initializers (or set via the constructor), annotate
the properties class with `@Validated/`@ConfigurationProperties as appropriate,
and update any constructors/accessors so callers (including
BedrockConverseClient.converse calls) use the non-null primitive values
directly.
In `@src/main/java/com/samhap/kokomen/global/external/gpt/GptProperties.java`:
- Around line 5-10: Add validation to GptProperties to fail fast when config
keys are missing: annotate the class/bean with `@Validated` and annotate the
record components apiKey with `@NotBlank` and evaluationTemperature,
generationTemperature, feedbackTemperature with `@NotNull` so they cannot be null
and avoid unboxing NPEs when callers pass primitive double; update imports to
javax/ jakarta.validation constraints as used in the project and ensure Spring
will validate `@ConfigurationProperties-bound` records at startup.
In
`@src/main/java/com/samhap/kokomen/interview/external/dto/response/ResumeBasedQuestionGptResponseMessage.java`:
- Around line 5-10: The ResumeBasedQuestionGptResponseMessage record's toolCalls
component may not be populated because OpenAI returns tool_calls (snake_case);
update the record by annotating the toolCalls component with
`@JsonProperty`("tool_calls") and add the necessary import for
com.fasterxml.jackson.annotation.JsonProperty so Jackson maps the snake_case
field to the toolCalls component during deserialization.
In
`@src/main/java/com/samhap/kokomen/interview/service/core/InterviewProceedService.java`:
- Line 101: Before calling interview.evaluate(...) in InterviewProceedService,
check the string returned by totalFeedbackResponse.composeTotalFeedback(); if
it's null/empty or only whitespace, throw an exception to fail-fast (e.g.,
IllegalStateException) instead of passing a blank value to interview.evaluate;
this validation should occur right before the call to
interview.evaluate(totalFeedbackResponse.composeTotalFeedback(), totalScore) so
blank LLM responses are rejected early.
In `@src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptRequest.java`:
- Around line 77-83: The chained .replace calls in ResumeGptRequest.create cause
double-replacement when user inputs contain other placeholder tokens
(USER_PROMPT_TEMPLATE), so change to a single-pass replacement: build a map of
placeholders (e.g.,
"{{resume_text}}","{{portfolio_text}}","{{job_position}}","{{job_description}}","{{job_career}}")
to their nullToEmpty(request...()) values and perform one regex-based replace on
USER_PROMPT_TEMPLATE that looks up each matched token in the map and substitutes
once; update the create method to use this single-pass replacer to preserve
original content and avoid cascading substitutions.
In
`@src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptResponseMessage.java`:
- Around line 6-11: The record ResumeGptResponseMessage is missing the Jackson
mapping for the snake_case field name; add `@JsonProperty`("tool_calls") to the
toolCalls component in ResumeGptResponseMessage so Jackson deserializes the
OpenAI "tool_calls" array into the List<ToolCall> toolCalls field (do the same
fix in ResumeBasedQuestionGptResponseMessage if present).
In
`@src/main/java/com/samhap/kokomen/resume/service/dto/ResumeEvaluationResponse.java`:
- Around line 20-27: withCalculatedTotalScore() can NPE when any category
(technicalSkills, projectExperience, problemSolving, careerGrowth,
documentation) is null; change the method to treat missing categories as score 0
by null-checking each category before calling score() (e.g., use ternary checks
or Optional.ofNullable(...).map(...).orElse(0.0)) and then compute the weighted
sum, round to int, set the total score and return the object as before; update
references to technicalSkills.score(), projectExperience.score(),
problemSolving.score(), careerGrowth.score(), and documentation.score()
accordingly so no direct call occurs on a null object.
In `@src/main/resources/db/migration/V44__resume_evaluation_reasons_to_json.sql`:
- Around line 1-11: Before altering columns to JSON, normalize existing string
values in table resume_evaluation for each *_reason and *_improvements column
(technical_skills_reason, technical_skills_improvements,
project_experience_reason, project_experience_improvements,
problem_solving_reason, problem_solving_improvements, career_growth_reason,
career_growth_improvements, documentation_reason, documentation_improvements) by
backfilling only rows where the column is non-null and not valid JSON: transform
the plain string into a JSON value (e.g., wrap into a stable JSON object or
JSON-quote the string) and write it back; perform this for each column in a safe
transactional migration (or with idempotent conditional UPDATEs) before running
the ALTER TABLE that changes the column types to JSON, and include checks (using
JSON_VALID or equivalent) so rerunning the migration is safe.
In
`@src/test/java/com/samhap/kokomen/global/fixture/interview/BedrockResponseFixtureBuilder.java`:
- Around line 69-73: The end-payload fixture in BedrockResponseFixtureBuilder
(the buildEnd() path that constructs the LinkedHashMap `input` and returns a new
BedrockConverseResponse via Document.fromMap) is missing the required
"reasoning" field; update that method to put a "reasoning" entry into `input`
(use Document.fromString with a sensible default like a short explanation or
empty string) so the fixture matches
InterviewBedrockRequestFactory.createEndToolConfig()'s required keys
("reasoning", "rank", "feedback", "overall_summary") while keeping existing use
of Document.fromString/Document.fromMap.
---
Outside diff comments:
In `@src/main/java/com/samhap/kokomen/resume/domain/ResumeEvaluation.java`:
- Around line 134-163: In ResumeEvaluation.complete, add entity-level input
validation: verify each score parameter (technicalSkillsScore,
projectExperienceScore, problemSolvingScore, careerGrowthScore,
documentationScore, totalScore) is within 0-100 and throw an
IllegalArgumentException with a clear message if out of range; for each list
parameter (technicalSkillsReason, technicalSkillsImprovements,
projectExperienceReason, projectExperienceImprovements, problemSolvingReason,
problemSolvingImprovements, careerGrowthReason, careerGrowthImprovements,
documentationReason, documentationImprovements) defend against null by replacing
with empty immutable/defensive copies (e.g., Collections.emptyList() or new
ArrayList<>(list)) and store defensive copies on the entity to avoid external
mutation; keep the state set to ResumeEvaluationState.COMPLETED only after
validation passes.
In
`@src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java`:
- Around line 70-87: The buildEnd() return payload in GptResponseFixtureBuilder
is missing the required "reasoning" field; update the multi-line arguments
string passed to create(...) (the block that currently includes
"rank","feedback","overall_summary") to also include a "reasoning" key and
populate it from the fixture's reasoning field (use the same null-coalescing
pattern as for answerRank/feedback/strengths/etc.), so the produced object
matches GptRequest.createEndParams()’s contract ("reasoning", "rank",
"feedback", "overall_summary") and tests remain consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 54d58ecd-ba3e-4dec-9ccf-aca1aae8cdf2
📒 Files selected for processing (43)
src/main/java/com/samhap/kokomen/global/external/BaseGptClient.javasrc/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseClient.javasrc/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseProperties.javasrc/main/java/com/samhap/kokomen/global/external/gpt/GptConfig.javasrc/main/java/com/samhap/kokomen/global/external/gpt/GptProperties.javasrc/main/java/com/samhap/kokomen/global/persistence/StringListJsonConverter.javasrc/main/java/com/samhap/kokomen/interview/external/AnswerFeedbackBedrockClient.javasrc/main/java/com/samhap/kokomen/interview/external/InterviewProceedBedrockClient.javasrc/main/java/com/samhap/kokomen/interview/external/InterviewProceedGptClient.javasrc/main/java/com/samhap/kokomen/interview/external/ResumeBasedQuestionBedrockService.javasrc/main/java/com/samhap/kokomen/interview/external/ResumeBasedQuestionGptClient.javasrc/main/java/com/samhap/kokomen/interview/external/dto/request/GptFunctionParameters.javasrc/main/java/com/samhap/kokomen/interview/external/dto/request/GptRequest.javasrc/main/java/com/samhap/kokomen/interview/external/dto/request/InterviewBedrockRequestFactory.javasrc/main/java/com/samhap/kokomen/interview/external/dto/request/ResumeBasedQuestionGptRequest.javasrc/main/java/com/samhap/kokomen/interview/external/dto/response/ResumeBasedQuestionGptResponseMessage.javasrc/main/java/com/samhap/kokomen/interview/external/dto/response/TotalFeedbackResponse.javasrc/main/java/com/samhap/kokomen/interview/service/core/InterviewProceedService.javasrc/main/java/com/samhap/kokomen/interview/tool/GptSystemMessageConstant.javasrc/main/java/com/samhap/kokomen/interview/tool/InterviewBedrockSystemMessageConstant.javasrc/main/java/com/samhap/kokomen/interview/tool/InterviewPromptFragments.javasrc/main/java/com/samhap/kokomen/resume/domain/ResumeEvaluation.javasrc/main/java/com/samhap/kokomen/resume/external/ResumeBasedQuestionBedrockClient.javasrc/main/java/com/samhap/kokomen/resume/external/ResumeEvaluationBedrockClient.javasrc/main/java/com/samhap/kokomen/resume/external/ResumeEvaluationGptClient.javasrc/main/java/com/samhap/kokomen/resume/external/dto/ResumeBedrockRequestFactory.javasrc/main/java/com/samhap/kokomen/resume/external/dto/ResumeBedrockSystemMessageConstant.javasrc/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptRequest.javasrc/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptResponseMessage.javasrc/main/java/com/samhap/kokomen/resume/service/ResumeEvaluationAsyncService.javasrc/main/java/com/samhap/kokomen/resume/service/dto/ResumeEvaluationResponse.javasrc/main/java/com/samhap/kokomen/resume/service/dto/evaluation/CareerGrowthResponse.javasrc/main/java/com/samhap/kokomen/resume/service/dto/evaluation/DocumentationResponse.javasrc/main/java/com/samhap/kokomen/resume/service/dto/evaluation/ProblemSolvingResponse.javasrc/main/java/com/samhap/kokomen/resume/service/dto/evaluation/ProjectExperienceResponse.javasrc/main/java/com/samhap/kokomen/resume/service/dto/evaluation/TechnicalSkillsResponse.javasrc/main/java/com/samhap/kokomen/resume/tool/ResumePromptFragments.javasrc/main/resources/application.ymlsrc/main/resources/db/migration/V44__resume_evaluation_reasons_to_json.sqlsrc/test/java/com/samhap/kokomen/global/fixture/interview/BedrockResponseFixtureBuilder.javasrc/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.javasrc/test/java/com/samhap/kokomen/global/fixture/resume/ResumeEvaluationFixtureBuilder.javasrc/test/resources/application.yml
| public record ResumeBasedQuestionGptResponseMessage( | ||
| String role, | ||
| String content | ||
| String content, | ||
| List<ToolCall> toolCalls | ||
| ) { | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# ObjectMapper에 전역 snake_case 설정이 있는지 확인
rg -n "SNAKE_CASE|PropertyNamingStrategy" --type=javaRepository: samhap-soft/kokomen-backend
Length of output: 493
ResumeBasedQuestionGptResponseMessage의 toolCalls 역직렬화 매핑 보강 필요
OpenAI 응답은 tool_calls(snake_case)인데, 현재 확인된 범위에서는 SNAKE_CASE 전역 PropertyNamingStrategy 설정은 보이지 않고(예: TossPaymentsClientBuilder는 LOWER_CAMEL_CASE로 설정), Jackson 기본 매핑만으로는 toolCalls에 값이 안 들어와 null이 될 위험이 있습니다. 따라서 toolCalls 컴포넌트에 @JsonProperty("tool_calls")를 명시하세요.
🐛 수정 제안
package com.samhap.kokomen.interview.external.dto.response;
+import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public record ResumeBasedQuestionGptResponseMessage(
String role,
String content,
+ `@JsonProperty`("tool_calls")
List<ToolCall> toolCalls
) {
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/samhap/kokomen/interview/external/dto/response/ResumeBasedQuestionGptResponseMessage.java`
around lines 5 - 10, The ResumeBasedQuestionGptResponseMessage record's
toolCalls component may not be populated because OpenAI returns tool_calls
(snake_case); update the record by annotating the toolCalls component with
`@JsonProperty`("tool_calls") and add the necessary import for
com.fasterxml.jackson.annotation.JsonProperty so Jackson maps the snake_case
field to the toolCalls component during deserialization.
| public record ResumeGptResponseMessage( | ||
| String role, | ||
| String content | ||
| String content, | ||
| List<ToolCall> toolCalls | ||
| ) { | ||
| } |
There was a problem hiding this comment.
toolCalls 필드에 @JsonProperty("tool_calls") 어노테이션이 누락되었습니다.
ResumeBasedQuestionGptResponseMessage와 동일한 이슈입니다. OpenAI API 응답의 snake_case 필드명과 매핑되지 않아 역직렬화 시 null이 반환될 수 있습니다.
🐛 수정 제안
package com.samhap.kokomen.resume.external.dto;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.samhap.kokomen.interview.external.dto.response.ToolCall;
import java.util.List;
public record ResumeGptResponseMessage(
String role,
String content,
+ `@JsonProperty`("tool_calls")
List<ToolCall> toolCalls
) {
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptResponseMessage.java`
around lines 6 - 11, The record ResumeGptResponseMessage is missing the Jackson
mapping for the snake_case field name; add `@JsonProperty`("tool_calls") to the
toolCalls component in ResumeGptResponseMessage so Jackson deserializes the
OpenAI "tool_calls" array into the List<ToolCall> toolCalls field (do the same
fix in ResumeBasedQuestionGptResponseMessage if present).
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java (1)
61-97:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winJSON 문자열 직접 포맷팅으로 fixture 인자가 깨질 수 있습니다.
buildProceed()/buildEnd()에서%s로 JSON을 직접 만들면 입력값에 따옴표·개행·역슬래시가 포함될 때 invalid JSON이 됩니다. 맵을 JSON 직렬화해서 arguments를 생성하는 방식으로 바꾸는 게 안전합니다.수정 예시
+import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; +import java.util.Map; @@ public class GptResponseFixtureBuilder { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ public GptResponse buildProceed() { - String arguments = """ - { - "reasoning": "%s", - "rank": "%s", - "feedback": "%s", - "next_question": "%s" - } - """.formatted( - reasoning != null ? reasoning : "답변 평가 근거와 다음 질문 설계 근거입니다.", - answerRank != null ? answerRank.name() : "A", - feedback != null ? feedback : "좋은 답변입니다.", - nextQuestion != null ? nextQuestion : "스레드 안전하다는 것은 무엇인가요?" - ); - return create(arguments, "generate_feedback"); + Map<String, Object> arguments = Map.of( + "reasoning", reasoning != null ? reasoning : "답변 평가 근거와 다음 질문 설계 근거입니다.", + "rank", answerRank != null ? answerRank.name() : "A", + "feedback", feedback != null ? feedback : "좋은 답변입니다.", + "next_question", nextQuestion != null ? nextQuestion : "스레드 안전하다는 것은 무엇인가요?" + ); + return create(toJson(arguments), "generate_feedback"); } @@ public GptResponse buildEnd() { - String arguments = """ - { - "reasoning": "%s", - "rank": "%s", - "feedback": "%s", - "overall_summary": { - "strengths": "%s", - "improvements": "%s", - "learning_direction": "%s" - } - } - """.formatted( - reasoning != null ? reasoning : "마지막 답변 평가 근거와 전체 면접 종합 평가 근거입니다.", - answerRank != null ? answerRank.name() : "A", - feedback != null ? feedback : "좋은 답변입니다.", - strengths != null ? strengths : "전체적으로 답변이 명확합니다.", - improvements != null ? improvements : "구체 사례를 더 보강하면 좋겠습니다.", - learningDirection != null ? learningDirection : "기초 개념 심화 학습을 권장합니다." - ); - return create(arguments, "generate_total_feedback"); + Map<String, Object> arguments = Map.of( + "reasoning", reasoning != null ? reasoning : "마지막 답변 평가 근거와 전체 면접 종합 평가 근거입니다.", + "rank", answerRank != null ? answerRank.name() : "A", + "feedback", feedback != null ? feedback : "좋은 답변입니다.", + "overall_summary", Map.of( + "strengths", strengths != null ? strengths : "전체적으로 답변이 명확합니다.", + "improvements", improvements != null ? improvements : "구체 사례를 더 보강하면 좋겠습니다.", + "learning_direction", learningDirection != null ? learningDirection : "기초 개념 심화 학습을 권장합니다." + ) + ); + return create(toJson(arguments), "generate_total_feedback"); } + + private String toJson(Map<String, Object> arguments) { + try { + return OBJECT_MAPPER.writeValueAsString(arguments); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Fixture JSON 생성 실패", e); + } + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java` around lines 61 - 97, The code in GptResponseFixtureBuilder methods buildProceed() and buildEnd() constructs JSON by String.formatted("%s", ...) which breaks when fields contain quotes, newlines or backslashes; replace the manual string formatting with building a Map/POJO of the intended JSON structure (including nested map for overall_summary in buildEnd()) and serialize it to a JSON string using your project's JSON serializer (e.g., ObjectMapper.writeValueAsString) before calling create(arguments, ...); ensure you reference the same keys ("reasoning","rank","feedback","next_question" and "overall_summary" with "strengths","improvements","learning_direction") so create(...) receives valid escaped JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In
`@src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java`:
- Around line 61-97: The code in GptResponseFixtureBuilder methods
buildProceed() and buildEnd() constructs JSON by String.formatted("%s", ...)
which breaks when fields contain quotes, newlines or backslashes; replace the
manual string formatting with building a Map/POJO of the intended JSON structure
(including nested map for overall_summary in buildEnd()) and serialize it to a
JSON string using your project's JSON serializer (e.g.,
ObjectMapper.writeValueAsString) before calling create(arguments, ...); ensure
you reference the same keys ("reasoning","rank","feedback","next_question" and
"overall_summary" with "strengths","improvements","learning_direction") so
create(...) receives valid escaped JSON.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 763866a1-3799-494e-8157-9d9dc921ed63
📒 Files selected for processing (9)
src/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseProperties.javasrc/main/java/com/samhap/kokomen/global/external/gpt/GptProperties.javasrc/main/java/com/samhap/kokomen/interview/service/core/InterviewProceedService.javasrc/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptRequest.javasrc/main/java/com/samhap/kokomen/resume/service/dto/ResumeEvaluationResponse.javasrc/main/resources/db/migration/V44__resume_evaluation_reasons_to_json.sqlsrc/test/java/com/samhap/kokomen/global/fixture/interview/BedrockResponseFixtureBuilder.javasrc/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.javasrc/test/resources/application.yml
closed #
작업 내용
스크린샷
참고 사항