Skip to content

[REFACTOR] 프롬프트 수정#356

Merged
unifolio0 merged 2 commits into
developfrom
feature/prompt
May 24, 2026
Merged

[REFACTOR] 프롬프트 수정#356
unifolio0 merged 2 commits into
developfrom
feature/prompt

Conversation

@unifolio0

Copy link
Copy Markdown
Contributor

closed #

작업 내용

스크린샷

참고 사항

@unifolio0 unifolio0 self-assigned this May 24, 2026
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 단계별 평가/생성/피드백 온도 설정 추가로 AI 응답 조절 가능
    • 피드백의 종합 출력이 강점/개선점/학습 방향 구조로 제공
  • 개선사항

    • 이력서 평가의 사유·개선사항을 리스트 형태로 반환하여 가독성 향상
    • 면접·이력서 프롬프트·출력 형식 재구성으로 응답 일관성 강화
    • 평가 총점 계산 로직이 클라이언트 흐름에 반영되어 결과 일관성 개선

Walkthrough

이 PR은 GPT/Bedrock 클라이언트의 온도 설정을 3개 필드로 분산하고, 응답 처리를 함수 호출 기반으로 전환하며, 평가 피드백 구조를 문자열에서 구조화된 객체로 재설계합니다.

Changes

설정 및 기본 클라이언트 구조

Layer / File(s) Summary
설정 프로퍼티 정의 및 바인딩
src/main/java/com/samhap/kokomen/global/external/gpt/GptProperties.java, src/main/java/com/samhap/kokomen/global/external/gpt/GptConfig.java, src/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseProperties.java, src/main/resources/application.yml, src/test/resources/application.yml
GptPropertiesGptConfig를 신규 추가하여 open-ai 프리픽스의 apiKey와 3개 온도(evaluation/generation/feedback)를 바인딩하고, BedrockConverseProperties 온도 필드도 3종으로 분리합니다.
BaseGptClient 의존성 주입 변경
src/main/java/com/samhap/kokomen/global/external/BaseGptClient.java
생성자가 String gptApiKey 대신 GptProperties gptProperties를 받도록 변경되고, executeRequest에서 인증 헤더가 gptProperties.apiKey()를 사용합니다.

Bedrock Converse 클라이언트 강화

Layer / File(s) Summary
온도 및 캐시 포인트 관리
src/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseClient.java
converse 메서드가 float temperature 파라미터를 받아 InferenceConfiguration에 적용하고, systemMessages에 CachePointType.DEFAULT 캐시 포인트를 append합니다.

GPT 클라이언트 생성자 및 온도 통합

Layer / File(s) Summary
InterviewProceedGptClient 온도 전달
src/main/java/com/samhap/kokomen/interview/external/InterviewProceedGptClient.java
생성자가 GptProperties를 주입받고, proceed/end 요청 생성 시 evaluationTemperature()GptRequest.create*GptRequest에 전달합니다.
ResumeBasedQuestionGptClient 온도 및 도구 호출
src/main/java/com/samhap/kokomen/interview/external/ResumeBasedQuestionGptClient.java
생성자가 GptProperties를 받고, 응답에서 메시지 콘텐츠 대신 첫 번째 toolCall의 function arguments를 반환하며, generationTemperature()를 요청에 포함합니다.
ResumeEvaluationGptClient 변경
src/main/java/com/samhap/kokomen/resume/external/ResumeEvaluationGptClient.java
생성자가 GptProperties를 받고, requestResumeEvaluation이 evaluationTemperature를 사용해 요청을 생성하고 응답에서 toolCalls.arguments를 추출하며 validateResponse 검증을 강화합니다.

함수 호출 기반 요청/응답 구조

Layer / File(s) Summary
GptRequest 함수 호출 및 온도 확장
src/main/java/com/samhap/kokomen/interview/external/dto/request/GptRequest.java
레코드에 Double temperature 컴포넌트를 추가하고, createProceedGptRequest/createEndGptRequest가 온도를 받아 함수 파라미터 스키마(reasoning/rank/feedback/overall_summary)와 함께 구성합니다.
ResumeBasedQuestionGptRequest 함수 호출 기반 변환
src/main/java/com/samhap/kokomen/interview/external/dto/request/ResumeBasedQuestionGptRequest.java
요청 레코드가 tools/tool_choice/temperature를 포함하고, create 메서드가 시스템/유저 메시지를 모두 구성한 뒤 submit_resume_questions 함수 호출 스키마를 설정하며, questions 배열의 JSON 스키마를 정의합니다.
응답 메시지 toolCalls 필드 추가
src/main/java/com/samhap/kokomen/interview/external/dto/response/ResumeBasedQuestionGptResponseMessage.java, src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptResponseMessage.java
ResumeBasedQuestionGptResponseMessage와 ResumeGptResponseMessage 레코드가 List<ToolCall> toolCalls 필드를 추가하여 도구 호출 응답을 처리합니다.
GptFunctionParameters 타입 유연화
src/main/java/com/samhap/kokomen/interview/external/dto/request/GptFunctionParameters.java
properties 필드가 Map<String, FunctionParamProperty>에서 Map<String, Object>로 변경되어 중첩 스키마를 지원합니다.

피드백 구조 재설계 및 응답 처리

Layer / File(s) Summary
TotalFeedbackResponse 중첩 구조 도입
src/main/java/com/samhap/kokomen/interview/external/dto/response/TotalFeedbackResponse.java
TotalFeedbackResponse 레코드가 단일 totalFeedback 문자열에서 OverallSummary 중첩 레코드(strengths/improvements/learningDirection)로 변경되고, composeTotalFeedback() 메서드가 이들을 문단으로 조합합니다.
Bedrock 피드백 요청 구조 변경
src/main/java/com/samhap/kokomen/interview/external/dto/request/InterviewBedrockRequestFactory.java
createAnswerFeedbackSystemAnswerRank를 받아 시스템 프롬프트에 포함하고, createAnswerFeedbackMessages가 rank 파라미터를 제거하여 인터뷰 히스토리만 생성하며, createEndToolConfig가 overall_summary 중첩 스키마를 추가합니다.
ResumeGptRequest 평가 스키마 및 함수 호출
src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptRequest.java
레코드가 tools/tool_choice/temperature를 포함하고, create 메서드가 ResumePromptFragments 기반 시스템 프롬프트와 submit_resume_evaluation 함수 스키마를 구성하며, 카테고리별 reason/improvements를 배열 구조로 정의합니다.
Bedrock 평가 스키마 검증 강화
src/main/java/com/samhap/kokomen/resume/external/dto/ResumeBedrockRequestFactory.java
questions 배열에 minItems/maxItems 제약을 추가하고, 평가 카테고리에 reasoning 필드를 추가하며, total_feedback 필드를 새로 정의하고 bulletArraySchema 헬퍼를 도입합니다.

프롬프트 템플릿 분해 및 재구성

Layer / File(s) Summary
InterviewPromptFragments 상수 정의
src/main/java/com/samhap/kokomen/interview/tool/InterviewPromptFragments.java
인터뷰 프롬프트용 페르소나/보안규칙/평가기준/랭크별 피드백 톤/단일 질문 제약/후속 질문 알고리즘을 public static final String으로 정의합니다.
ResumePromptFragments 상수 정의
src/main/java/com/samhap/kokomen/resume/tool/ResumePromptFragments.java
이력서 평가 프롬프트용 면접관/채용담당자 페르소나, 질문 생성 가이드, 평가 기준, 독립성 원칙, 점수 앵커, 보안 규칙을 public static final String으로 정의합니다.
GptSystemMessageConstant 템플릿 기반 재구성
src/main/java/com/samhap/kokomen/interview/tool/GptSystemMessageConstant.java
PROCEED_SYSTEM_MESSAGE와 END_SYSTEM_MESSAGE가 InterviewPromptFragments 조각들을 formatted()로 주입하는 템플릿으로 변경되고, 함수 호출(generate_feedback/generate_total_feedback) 기반 출력 요구사항으로 갱신됩니다.
InterviewBedrockSystemMessageConstant 확장
src/main/java/com/samhap/kokomen/interview/tool/InterviewBedrockSystemMessageConstant.java
IN_PROGRESS_RANK_AND_NEXT_QUESTION_PROMPT가 InterviewPromptFragments 기반 템플릿으로 변경되고, END_PROMPT와 ANSWER_FEEDBACK_PROMPT 공개 상수가 추가되어 도구 호출 필드를 지정합니다.
ResumeBedrockSystemMessageConstant 템플릿 기반 재구성
src/main/java/com/samhap/kokomen/resume/external/dto/ResumeBedrockSystemMessageConstant.java
QUESTION_GENERATION_PROMPT와 EVALUATION_PROMPT가 ResumePromptFragments 조각들을 조합하는 템플릿으로 교체되고, 출력 스키마가 카테고리별 reason/score/improvements 및 total_feedback 중심으로 재정의됩니다.

Bedrock 클라이언트 온도 통합 및 응답 처리

Layer / File(s) Summary
InterviewProceedBedrockClient 및 ResumeBasedQuestionBedrockClient 온도 전달
src/main/java/com/samhap/kokomen/interview/external/InterviewProceedBedrockClient.java, src/main/java/com/samhap/kokomen/resume/external/ResumeBasedQuestionBedrockClient.java, src/main/java/com/samhap/kokomen/resume/external/ResumeEvaluationBedrockClient.java
requestProceed/requestEnd와 generateQuestions/evaluate에서 converseClient.converse 호출 시 evaluationTemperature/generationTemperature를 추가 인자로 전달합니다.
ResumeBasedQuestionBedrockService 응답 정제 제거
src/main/java/com/samhap/kokomen/interview/external/ResumeBasedQuestionBedrockService.java
parseQuestionResponse에서 cleanJsonContent 정제 로직을 제거하여 응답 본문을 직접 파싱하도록 변경됩니다.

Resume 평가 도메인 및 서비스 계층 변경

Layer / File(s) Summary
ResumeEvaluation 필드 타입 변환 및 StringListJsonConverter
src/main/java/com/samhap/kokomen/resume/domain/ResumeEvaluation.java, src/main/java/com/samhap/kokomen/global/persistence/StringListJsonConverter.java
ResumeEvaluation의 10개 reason/improvements 필드가 String에서 List<String>으로 변경되고, StringListJsonConverter JPA 컨버터가 List을 JSON 문자열로 양방향 변환하며, complete 메서드 시그니처도 동일하게 변경됩니다.
평가 응답 DTO 배열 필드 변환
src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/TechnicalSkillsResponse.java, src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/ProjectExperienceResponse.java, src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/ProblemSolvingResponse.java, src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/CareerGrowthResponse.java, src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/DocumentationResponse.java
5개 평가 응답 DTO의 reason과 improvements 필드를 String에서 List<String>으로 변경합니다.
ResumeEvaluationResponse 총점 계산 및 null 안전 처리
src/main/java/com/samhap/kokomen/resume/service/dto/ResumeEvaluationResponse.java
withCalculatedTotalScore() 메서드를 추가하여 5개 카테고리를 가중치(0.30/0.25/0.20/0.15/0.10)로 합산한 총점을 계산하고, from 메서드가 nullToEmpty 헬퍼로 reason/improvements를 안전하게 변환합니다.
ResumeEvaluationGptClient 도구 호출 기반 변환
src/main/java/com/samhap/kokomen/resume/external/ResumeEvaluationGptClient.java
생성자가 GptProperties를 받고, requestResumeEvaluation이 evaluationTemperature를 포함한 요청을 생성한 뒤 toolCalls의 function arguments를 반환하며, validateResponse가 message/toolCalls 존재 여부를 추가 검증합니다.
ResumeEvaluationAsyncService 총점 계산 통합
src/main/java/com/samhap/kokomen/resume/service/ResumeEvaluationAsyncService.java
evaluateMemberAsync/fallbackToGptForMember/evaluateNonMemberAsync/fallbackToGptForNonMember가 모두 withCalculatedTotalScore()를 호출하여 Bedrock/GPT 응답 후 총점을 계산합니다.
InterviewProceedService 피드백 조합 변경
src/main/java/com/samhap/kokomen/interview/service/core/InterviewProceedService.java
evaluateInterview에서 interview.evaluate에 전달되는 피드백이 TotalFeedbackResponse.composeTotalFeedback()으로 변경되어 OverallSummary를 문단으로 조합합니다.

AnswerFeedback 요청 구조 및 테스트 픽스처

Layer / File(s) Summary
AnswerFeedbackBedrockClient 및 응답 픽스처
src/main/java/com/samhap/kokomen/interview/external/AnswerFeedbackBedrockClient.java, src/test/java/com/samhap/kokomen/global/fixture/interview/BedrockResponseFixtureBuilder.java, src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java
requestAnswerFeedback이 createAnswerFeedbackSystem에만 rank를 전달하고, 응답 픽스처(BedrockResponseFixtureBuilder/GptResponseFixtureBuilder)가 totalFeedback 필드를 제거하고 strengths/improvements/learningDirection 필드를 추가하며 buildEnd가 overall_summary 구조로 응답을 조합합니다.
ResumeEvaluationFixtureBuilder 리스트 변환
src/test/java/com/samhap/kokomen/global/fixture/resume/ResumeEvaluationFixtureBuilder.java
reason/improvements 필드를 List으로 변경하고, build 메서드에서 complete 호출 시 리스트 값을 전달합니다.
DB 마이그레이션 및 설정 파일 업데이트
src/main/resources/db/migration/V44__resume_evaluation_reasons_to_json.sql, src/main/resources/application.yml, src/test/resources/application.yml
resume_evaluation 테이블의 reason/improvements 컬럼을 JSON NULL로 수정하고, application.yml의 open-ai와 aws.bedrock 설정에 evaluation/generation/feedback-temperature 필드를 추가합니다.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

  • samhap-soft/kokomen-backend#355: BedrockConverseClient 및 BedrockConverseProperties의 온도 처리 방식 변경과 관련된 수정 사항이 겹칩니다.

Suggested Reviewers

  • nak-honest
  • kargowild

🐰 온도별로 갈라진 설정 새로 담아
함수 호출로 피드백 구조화하고
프롬프트 조각 모아 재사용하니
평가는 더욱 정교해졌네요! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목이 실제 변경사항의 주요 내용과 불일치합니다. 제목은 '프롬프트 수정'이지만, 실제로는 GPT 설정 구조 리팩토링, 온도 파라미터 분리, 함수 호출 기반 아키텍처 전환 등 광범위한 변경이 포함되어 있습니다. PR 제목을 '[REFACTOR] GPT/Bedrock 설정 구조 및 온도 파라미터 분리' 또는 유사하게 실제 변경의 핵심을 반영하도록 수정하세요.
Description check ⚠️ Warning PR 설명이 템플릿 형태만 남아있고 실제 작업 내용, 변경 사항, 스크린샷, 참고 사항이 모두 비어있어 정보가 없습니다. PR 설명에 주요 변경사항(설정 구조 변경, 온도 파라미터 분리, 함수 호출 기반 전환, DB 마이그레이션 등)을 구체적으로 작성하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/prompt

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 24, 2026

Copy link
Copy Markdown

Test Results

 51 files   51 suites   1m 24s ⏱️
291 tests 290 ✅ 1 💤 0 ❌
293 runs  292 ✅ 1 💤 0 ❌

Results for commit 76b49e0.

♻️ This comment has been updated with latest results.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, 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

  • 프롬프트 구조화 및 리팩토링: 프롬프트 관리를 위한 Fragment 클래스들을 도입하고, 시스템 메시지 및 도구 호출 스키마를 모듈화하여 유지보수성을 향상했습니다.
  • LLM 설정 세분화: GptProperties를 도입하여 평가, 생성, 피드백 단계별로 적절한 temperature 값을 설정할 수 있도록 구성했습니다.
  • 데이터 저장 방식 변경: 이력서 평가 항목의 이유와 개선 사항을 JSON 형태로 저장하기 위해 StringListJsonConverter를 추가하고 엔티티를 수정했습니다.
  • 응답 파싱 및 로직 개선: LLM 응답에서 tool_calls를 사용하도록 구조를 변경하고, 평가 점수 계산 로직을 응답 객체 내부로 캡슐화했습니다.
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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1 to +11
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

기존 technical_skills_reason 등의 컬럼이 TEXT 타입이고 이미 데이터가 존재한다면, 단순히 JSON 타입으로 MODIFY 할 때 유효하지 않은 JSON 형식 오류로 인해 마이그레이션이 실패할 수 있습니다. 기존 데이터를 ["기존 텍스트"]와 같은 JSON 배열 형식으로 변환하는 처리를 포함하거나, 데이터가 없는 경우에만 안전하게 실행될 수 있는지 확인이 필요합니다.

Comment on lines +22 to +26
0.30 * technicalSkills.score()
+ 0.25 * projectExperience.score()
+ 0.20 * problemSolving.score()
+ 0.15 * careerGrowth.score()
+ 0.10 * documentation.score()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

점수 계산에 사용되는 가중치(0.30, 0.25 등)가 ResumePromptFragments 클래스의 프롬프트 문자열 내에도 동일하게 정의되어 있습니다. 가중치 변경 시 두 곳을 모두 수정해야 하므로 관리 포인트가 중복되고 정합성이 깨질 위험이 있습니다. 가중치를 별도의 상수로 정의하고, 프롬프트 생성 시에도 해당 상수를 참조하도록 구성하는 것을 권장합니다.

Comment on lines +32 to +34
return List.of();
}
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

List.of()는 불변 리스트를 반환합니다. 엔티티의 필드를 통해 리스트에 요소를 추가하거나 수정하려는 시도가 있을 경우 UnsupportedOperationException이 발생할 수 있습니다. 가변 리스트인 new java.util.ArrayList<>()를 반환하는 것이 더 안전합니다.

        if (dbData == null || dbData.isBlank()) {
            return new java.util.ArrayList<>();
        }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)과 불일치라 테스트 신뢰도가 떨어집니다.

🔧 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 : "기초 개념 심화 학습을 권장합니다."
         );
As per coding guidelines, "Create fixtures using global/fixture/{domain}/XxxFixtureBuilder pattern with static builder() method and fluent setters with sensible defaults".
🤖 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 @Valid annotation 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

📥 Commits

Reviewing files that changed from the base of the PR and between 013adee and f1fe763.

📒 Files selected for processing (43)
  • src/main/java/com/samhap/kokomen/global/external/BaseGptClient.java
  • src/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseClient.java
  • src/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseProperties.java
  • src/main/java/com/samhap/kokomen/global/external/gpt/GptConfig.java
  • src/main/java/com/samhap/kokomen/global/external/gpt/GptProperties.java
  • src/main/java/com/samhap/kokomen/global/persistence/StringListJsonConverter.java
  • src/main/java/com/samhap/kokomen/interview/external/AnswerFeedbackBedrockClient.java
  • src/main/java/com/samhap/kokomen/interview/external/InterviewProceedBedrockClient.java
  • src/main/java/com/samhap/kokomen/interview/external/InterviewProceedGptClient.java
  • src/main/java/com/samhap/kokomen/interview/external/ResumeBasedQuestionBedrockService.java
  • src/main/java/com/samhap/kokomen/interview/external/ResumeBasedQuestionGptClient.java
  • src/main/java/com/samhap/kokomen/interview/external/dto/request/GptFunctionParameters.java
  • src/main/java/com/samhap/kokomen/interview/external/dto/request/GptRequest.java
  • src/main/java/com/samhap/kokomen/interview/external/dto/request/InterviewBedrockRequestFactory.java
  • src/main/java/com/samhap/kokomen/interview/external/dto/request/ResumeBasedQuestionGptRequest.java
  • src/main/java/com/samhap/kokomen/interview/external/dto/response/ResumeBasedQuestionGptResponseMessage.java
  • src/main/java/com/samhap/kokomen/interview/external/dto/response/TotalFeedbackResponse.java
  • src/main/java/com/samhap/kokomen/interview/service/core/InterviewProceedService.java
  • src/main/java/com/samhap/kokomen/interview/tool/GptSystemMessageConstant.java
  • src/main/java/com/samhap/kokomen/interview/tool/InterviewBedrockSystemMessageConstant.java
  • src/main/java/com/samhap/kokomen/interview/tool/InterviewPromptFragments.java
  • src/main/java/com/samhap/kokomen/resume/domain/ResumeEvaluation.java
  • src/main/java/com/samhap/kokomen/resume/external/ResumeBasedQuestionBedrockClient.java
  • src/main/java/com/samhap/kokomen/resume/external/ResumeEvaluationBedrockClient.java
  • src/main/java/com/samhap/kokomen/resume/external/ResumeEvaluationGptClient.java
  • src/main/java/com/samhap/kokomen/resume/external/dto/ResumeBedrockRequestFactory.java
  • src/main/java/com/samhap/kokomen/resume/external/dto/ResumeBedrockSystemMessageConstant.java
  • src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptRequest.java
  • src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptResponseMessage.java
  • src/main/java/com/samhap/kokomen/resume/service/ResumeEvaluationAsyncService.java
  • src/main/java/com/samhap/kokomen/resume/service/dto/ResumeEvaluationResponse.java
  • src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/CareerGrowthResponse.java
  • src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/DocumentationResponse.java
  • src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/ProblemSolvingResponse.java
  • src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/ProjectExperienceResponse.java
  • src/main/java/com/samhap/kokomen/resume/service/dto/evaluation/TechnicalSkillsResponse.java
  • src/main/java/com/samhap/kokomen/resume/tool/ResumePromptFragments.java
  • src/main/resources/application.yml
  • src/main/resources/db/migration/V44__resume_evaluation_reasons_to_json.sql
  • src/test/java/com/samhap/kokomen/global/fixture/interview/BedrockResponseFixtureBuilder.java
  • src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java
  • src/test/java/com/samhap/kokomen/global/fixture/resume/ResumeEvaluationFixtureBuilder.java
  • src/test/resources/application.yml

Comment thread src/main/java/com/samhap/kokomen/global/external/gpt/GptProperties.java Outdated
Comment on lines 5 to 10
public record ResumeBasedQuestionGptResponseMessage(
String role,
String content
String content,
List<ToolCall> toolCalls
) {
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# ObjectMapper에 전역 snake_case 설정이 있는지 확인
rg -n "SNAKE_CASE|PropertyNamingStrategy" --type=java

Repository: samhap-soft/kokomen-backend

Length of output: 493


ResumeBasedQuestionGptResponseMessagetoolCalls 역직렬화 매핑 보강 필요

OpenAI 응답은 tool_calls(snake_case)인데, 현재 확인된 범위에서는 SNAKE_CASE 전역 PropertyNamingStrategy 설정은 보이지 않고(예: TossPaymentsClientBuilderLOWER_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.

Comment thread src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptRequest.java Outdated
Comment on lines 6 to 11
public record ResumeGptResponseMessage(
String role,
String content
String content,
List<ToolCall> toolCalls
) {
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

JSON 문자열 직접 포맷팅으로 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

📥 Commits

Reviewing files that changed from the base of the PR and between f1fe763 and 76b49e0.

📒 Files selected for processing (9)
  • src/main/java/com/samhap/kokomen/global/external/bedrock/BedrockConverseProperties.java
  • src/main/java/com/samhap/kokomen/global/external/gpt/GptProperties.java
  • src/main/java/com/samhap/kokomen/interview/service/core/InterviewProceedService.java
  • src/main/java/com/samhap/kokomen/resume/external/dto/ResumeGptRequest.java
  • src/main/java/com/samhap/kokomen/resume/service/dto/ResumeEvaluationResponse.java
  • src/main/resources/db/migration/V44__resume_evaluation_reasons_to_json.sql
  • src/test/java/com/samhap/kokomen/global/fixture/interview/BedrockResponseFixtureBuilder.java
  • src/test/java/com/samhap/kokomen/global/fixture/interview/GptResponseFixtureBuilder.java
  • src/test/resources/application.yml

@unifolio0 unifolio0 merged commit a740c9a into develop May 24, 2026
4 checks passed
@unifolio0 unifolio0 deleted the feature/prompt branch May 24, 2026 10:50
unifolio0 added a commit that referenced this pull request Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant