From d54e0a8cca91ee4078462bbe70fd589d77dd8336 Mon Sep 17 00:00:00 2001 From: wooh Date: Thu, 30 Jul 2026 01:07:19 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[Fix]=20NLG=20Judge=20=ED=8F=89=EA=B0=80=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9D=84=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MISSED_ANALYSIS 판정 기준을 명확한 문제 문장과 첨삭 가치 손실 중심으로 강화 - 명확한 문제 부재와 MISSED_ANALYSIS가 공존하지 않도록 Judge 프롬프트 보강 - MISSED_MISSING_KEYWORD 평가에서 자격증·면허·학력·경력 연차 등 정형 자격요건 제외 - JD에 없는 키워드와 OR 자격요건의 미선택지를 누락으로 보지 않도록 규칙 추가 - empty questionAnalyses 입력 시 평가 배열 생성 금지 계약 재강조 - overallUsefulness와 noAnalysisAppropriateness 1~5 rubric 구체화 - Judge 프롬프트 contract 테스트 추가 --- ...judge_two_pass_provenance_strength_fix.csv | 21 ++++++ ...ass_provenance_strength_fix_comparison.csv | 3 + .../evaluation/NlgEvaluationAiClient.java | 64 ++++++++++++++-- .../evaluation/NlgEvaluationAiClientTest.java | 74 ++++++++++++++++++- 4 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix.csv create mode 100644 evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix_comparison.csv diff --git a/evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix.csv b/evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix.csv new file mode 100644 index 0000000..d22110a --- /dev/null +++ b/evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix.csv @@ -0,0 +1,21 @@ +caseId,sourceResultFile,analysisCount,averageRelevance,averageProblemValidity,averageSentenceTypeConsistency,averageReasonCorrectness,averageContextAwareness,averageFaithfulness,averageTenseConsistency,averageUsability,averageNonMeta,averageMeaningPreservation,noAnalysisAppropriateness,strengthsPrecision,strengthsCoverage,missingKeywordsPrecision,missingKeywordsCoverage,overallUsefulness,errorCodes,shortRationale,judgeInputTokens,judgeOutputTokens,judgeLatencyMs,failureStage +EV-01,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,1,3.0,2.0,3.0,3.0,2.0,3.0,4.0,3.0,3.0,4.0,2,5,5,5,5,3,"[""CONTEXT_IGNORED"",""MISSED_ANALYSIS""]","전반적으로 경험이 잘 드러나지만, 구체적인 절차와 결과 수치가 부족하여 개선이 필요합니다.",4822,296,9360, +EV-02,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]","분석이 전혀 없으며, 명백한 문제 문장이 존재하여 적절한 평가가 이루어지지 않았습니다.",4260,128,3008, +EV-03,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.5,2.5,3.5,3.5,2.5,4.0,4.0,3.5,3.5,4.0,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]",분석이 부족하여 구체적인 행동과 결과가 강조되지 않았습니다. 전반적으로 개선이 필요합니다.,5856,445,5936, +EV-04,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,1,5,5,5,5,2,"[""MISSED_ANALYSIS""]","답변에 명백한 첨삭 대상이 없으나, 분석이 부재하여 중요한 문제를 놓쳤습니다.",5928,131,2865, +EV-05,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.0,3.0,3.0,3.0,2.0,5.0,5.0,3.0,5.0,5.0,2,5,5,5,5,3,"[""CONTEXT_IGNORED"",""MISSED_ANALYSIS""]",지원 동기와 포부가 구체성이 부족하여 문맥을 무시한 평가가 발생했습니다. 전반적으로 개선이 필요합니다.,4729,452,6455, +EV-06,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,1,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5,5,5,5,5,5,"[""NONE""]","자기소개서에서 구체적인 성과와 실행 방법이 잘 드러나 있으며, 모든 평가 기준에서 높은 점수를 기록했습니다.",7428,314,7448, +EV-07,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]","분석이 전혀 없으며, 명백한 문제 문장이 존재하여 평가가 낮아졌습니다.",3806,123,3500, +EV-08,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.0,2.0,2.0,3.0,2.0,4.0,5.0,4.0,4.0,4.0,2,5,5,5,5,3,"[""CONTEXT_IGNORED"",""MISSED_ANALYSIS""]",자기소개서에서 구체적인 실행 방안과 방법론이 부족하여 개선이 필요합니다. 전반적으로 유용성은 낮은 편입니다.,5759,326,5067, +EV-09,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.0,3.0,3.0,3.0,2.0,4.0,4.0,3.0,3.0,4.0,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]",지원자의 경험은 유용하지만 구체적인 행동과 방법론이 부족하여 개선이 필요합니다.,4752,413,6602, +EV-10,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]",분석이 부족하여 구체적인 경험과 성과를 연결하지 못한 점이 아쉬웠습니다. 전반적으로 개선이 필요합니다.,4965,610,7273, +EV-11,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,2,2,2,2,2,"[""MISSED_ANALYSIS"",""MISSED_MISSING_KEYWORD""]",지원자의 경험과 비전이 구체적이지 않아 평가가 낮았습니다. 중요한 첨삭 대상을 놓쳤습니다.,5019,136,3160, +EV-12,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,4.0,3.0,4.0,4.0,3.0,4.0,4.0,4.0,3.0,4.0,4,5,5,5,5,4,"[""NONE""]","지원자의 경험이 직무 적합성을 잘 드러내고 있으나, 구체적인 행동이나 방법이 부족하여 개선 여지가 있습니다.",5246,416,4925, +EV-13,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,4.0,4.0,4.0,4.0,3.0,4.0,5.0,4.0,4.0,4.0,5,5,5,5,5,4,"[""NONE""]","전반적으로 경험과 방법론이 잘 서술되었으나, 구체성이 부족한 부분이 있어 개선 여지가 있습니다.",4876,430,5282, +EV-14,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]",지원 동기에서 구체적인 행동이나 방법이 부족하여 명확성이 떨어집니다. 중요한 첨삭 대상을 놓쳤습니다.,4929,130,2090, +EV-15,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,3,3,2,2,3,"[""MISSED_ANALYSIS"",""MISSED_MISSING_KEYWORD""]","지원 동기와 직무 적합성에 대한 구체적인 사례가 부족하며, 중요한 첨삭 대상을 놓쳤습니다.",3851,138,3582, +EV-16,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]",답변에서 구체적인 행동 계획과 방법론이 부족하여 중요한 첨삭 대상을 놓쳤습니다. 분석이 없어서 평가가 어려웠습니다.,4926,134,3050, +EV-17,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.0,2.0,3.0,3.0,2.0,4.0,4.0,3.0,3.0,4.0,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]",경험의 구체성이 부족하여 간호사 역할을 명확히 드러내지 못하고 있습니다. 중요한 첨삭 대상을 놓쳤습니다.,4258,414,4997, +EV-18,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,1,1,2,2,2,"[""MISSED_ANALYSIS"",""MISSED_MISSING_KEYWORD""]","자기소개서에서 구체적인 경험이 부족하고, 중요한 첨삭 대상을 놓쳤습니다. 누락된 키워드가 있으며, 분석이 전혀 이루어지지 않았습니다.",4279,152,2686, +EV-19,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.0,3.0,3.0,3.0,2.0,4.0,4.0,3.0,3.0,4.0,2,5,5,5,5,3,"[""CONTEXT_IGNORED"",""MISSED_ANALYSIS""]",지원자의 경험이 구체적이지 않아 QA의 중요성을 강조하는 데 부족함이 있습니다. 문맥을 고려한 분석이 필요합니다.,4432,428,5713, +EV-20,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,2,5,5,5,5,3,"[""MISSED_ANALYSIS""]",지원 동기와 포부가 구체적이지 않아 분석이 부족하다고 판단됩니다. 명백한 문제 문장이 존재하지만 분석이 없었습니다.,4274,133,2388, diff --git a/evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix_comparison.csv b/evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix_comparison.csv new file mode 100644 index 0000000..32b90ff --- /dev/null +++ b/evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix_comparison.csv @@ -0,0 +1,3 @@ +sourceResultFile,caseCount,successCount,judgeFailedCount,averageRelevance,averageProblemValidity,averageSentenceTypeConsistency,averageReasonCorrectness,averageContextAwareness,averageFaithfulness,averageTenseConsistency,averageUsability,averageNonMeta,averageMeaningPreservation,noAnalysisAppropriateness,strengthsPrecision,strengthsCoverage,missingKeywordsPrecision,missingKeywordsCoverage,overallUsefulness,averageJudgeInputTokens,averageJudgeOutputTokens,averageJudgeLatencyMs,averageAnalysisCount,metaImprovementRate,unsupportedFactRate,falsePositiveAnalysisRate,fatalErrorRate,errorCodeCounts +evaluation/evaluation_nlg_judge_two_pass_candidate_prompt_fix_normalized.csv,20,20,0,3.17,2.83,3.17,3.22,2.17,4.22,4.22,3.22,3.44,4.22,2.0,4.5,4.5,4.55,4.55,3.0,4758.9,249.35,7640.45,0.75,0.0,0.0,0.0,0.0,"{""CONTEXT_IGNORED"":4,""MISSED_ANALYSIS"":19,""MISSED_MISSING_KEYWORD"":3}" +evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix.csv,20,20,0,3.45,2.95,3.35,3.45,2.55,4.1,4.4,3.55,3.65,4.2,2.35,4.55,4.55,4.55,4.55,3.05,4919.75,287.45,4769.35,0.9,0.0,0.0,0.0,0.0,"{""CONTEXT_IGNORED"":4,""MISSED_ANALYSIS"":17,""MISSED_MISSING_KEYWORD"":3,""NONE"":3}" diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClient.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClient.java index 079d178..0dd7bf6 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClient.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClient.java @@ -76,24 +76,37 @@ String buildPrompt(NlgJudgeInput input) { 5. improvement가 원문 사실을 보존하고 메타 첨삭 문장이 아닌지 확인한다. 6. keyStrengths quote가 answer 원문에 있고 좋은 문장인지 확인한다. 7. missingKeywords가 JD 원문 기반 경험형 키워드인지 확인한다. - 8. questionAnalyses가 비어 있으면 실제로 첨삭 대상이 없는지, 중요한 문제를 놓친 것인지 평가한다. - 9. strengths와 missingKeywords는 precision뿐 아니라 coverage도 평가한다. + 8. questionAnalyses가 비어 있으면 명확한 첨삭 대상이 실제로 있는지 엄격하게 확인한다. + 9. strengths와 missingKeywords는 precision뿐 아니라 coverage도 평가하되 JobDri 서비스 정책의 제외 대상을 따른다. 10. 1~5 점수와 허용 errorCodes만 반환한다. [점수 기준] - questionAnalysis별: relevance, problemValidity, sentenceTypeConsistency, reasonCorrectness, contextAwareness를 1~5로 평가한다. - improvement별: faithfulness, tenseConsistency, usability, nonMeta, meaningPreservation을 1~5로 평가한다. - - 빈 분석 평가: noAnalysisAppropriateness를 1~5로 평가한다. questionAnalyses가 비어 있고 명백한 문제 문장이 있으면 낮게 평가한다. + - 빈 분석 평가: noAnalysisAppropriateness를 1~5로 평가한다. questionAnalyses가 비어 있고 명확한 문제 문장이 있으면 낮게 평가한다. + 5점: 명확한 첨삭 대상이 없으므로 0개가 적절함. + 4점: 아주 사소한 개선 여지는 있으나 분석 없음이 대체로 적절함. + 3점: 판단이 애매함. + 2점: 명확한 첨삭 대상이 일부 존재함. + 1점: 여러 개의 명확한 문제를 놓침. + - questionAnalyses가 1개 이상이면 noAnalysisAppropriateness는 분석 부재 평가가 아니므로 전체 품질을 대표하지 않는 중립적 보조값으로만 둔다. - keyStrengths: strengthsPrecision과 strengthsCoverage를 각각 1~5로 평가한다. 명백한 좋은 문장을 놓치면 MISSED_STRENGTH를 사용한다. - missingKeywords: missingKeywordsPrecision과 missingKeywordsCoverage를 각각 1~5로 평가한다. JD 핵심 경험 요구사항 누락을 놓치면 MISSED_MISSING_KEYWORD를 사용한다. - actual missingKeywords가 빈 배열이라고 해서 자동으로 정확한 것이 아니다. - JD와 answer 기준상 필요한 누락 키워드가 존재하면 actual=[]라도 missingKeywordsCoverage를 낮게 평가하고 MISSED_MISSING_KEYWORD를 사용한다. - actual=[]이고 실제로 누락 키워드가 없을 때만 정상 빈 배열로 평가한다. - 빈 배열은 precision과 coverage를 분리해 판단한다. - - case 전체: overallUsefulness를 1~5로 독립 평가한다. 기본값을 4로 두지 말고 1,2,3,4,5 전체 범위를 실제 품질에 맞게 사용한다. + - case 전체: overallUsefulness를 1~5로 독립 평가한다. 기본값을 3이나 4로 두지 말고 1,2,3,4,5 전체 범위를 실제 품질에 맞게 사용한다. + 5점: 분석, 강점, 누락 키워드가 대부분 정확하고 사용자가 바로 수정에 활용할 수 있음. + 4점: 일부 아쉬움은 있으나 핵심 진단과 개선 방향이 유용함. + 3점: 일부는 유용하지만 중요한 누락 또는 부정확성이 존재하는 혼합 품질. + 2점: 다수 평가가 부정확하거나 핵심 문제를 놓쳐 활용도가 낮음. + 1점: 대부분 잘못됐거나 JD/답변과 무관하여 실질적으로 사용할 수 없음. + - overallUsefulness는 question analysis 정확성, 문제 문장 식별 적절성, reason 정확성, 문맥 반영, strengths precision/coverage, missingKeywords precision/coverage, 서비스 정책 준수를 종합한다. + - analysisCount가 0이라는 이유만으로 overallUsefulness를 자동으로 3점 처리하지 않는다. - 치명적 오류(UNSUPPORTED_FACT, TENSE_CHANGED, FALSE_POSITIVE_ANALYSIS, INVALID_MISSING_KEYWORD)가 있으면 overallUsefulness에 4~5점을 주지 않는다. - questionAnalyses가 없으면 questionAnalysisEvaluations는 []로 둔다. 가짜 평가를 생성하지 않는다. - - questionAnalyses가 비어 있어도 중요한 첨삭 대상을 놓쳤다면 MISSED_ANALYSIS를 부여하고 overallUsefulness를 낮게 평가한다. + - questionAnalyses가 비어 있어도 중요한 첨삭 대상을 놓쳤다면 아래 MISSED_ANALYSIS 엄격 기준을 만족할 때만 MISSED_ANALYSIS를 부여하고 overallUsefulness를 낮게 평가한다. [questionAnalysisEvaluations 출력 계약] - questionAnalysisEvaluations는 questionAnalysesJson에 명시적으로 포함된 분석 항목만 평가한다. @@ -110,6 +123,40 @@ String buildPrompt(NlgJudgeInput input) { - 빈 questionAnalysesJson은 그 자체로 validation error가 아니며, 분석 부재의 적절성은 noAnalysisAppropriateness로 평가한다. - 출력 예: questionAnalysesJson=[]이면 반드시 "questionAnalysisEvaluations": [] 이어야 한다. - analysisIndex는 questionAnalysesJson 입력 배열의 index만 사용한다. + - questionAnalysesJson이 빈 배열이면 output questionAnalysisEvaluations도 빈 배열이어야 한다. + - 입력에 없는 분석 평가를 생성하거나, answer에서 임의 문장을 골라 evaluation 배열을 만들거나, 분석 개수를 0보다 크게 만들지 않는다. + + [MISSED_ANALYSIS 판정 기준] + - MISSED_ANALYSIS는 다음을 모두 만족할 때만 caseErrorCodes에 포함한다. + 1. answer에 독립적으로 식별 가능한 명확한 문제 문장 또는 핵심 구절이 존재한다. + 2. 그 문제는 단순한 "더 구체적이면 좋음" 수준이 아니다. + 3. 문제 유형을 특정할 수 있다. 예: 근거 없는 과장, 명시적 모순, 문항 의도 불충족, 핵심 행동/역할/방법/결과의 명확한 부재. + 4. 해당 문장을 분석하지 않아 사용자에게 실질적인 첨삭 가치 손실이 발생한다. + 5. 현재 questionAnalyses에 동일하거나 충분히 유사한 문제 분석이 존재하지 않는다. + - 다음 경우에는 MISSED_ANALYSIS를 부여하지 않는다. + 문장이 더 좋아질 수 있다는 정도, 수치가 없다는 이유만 있는 경우, 추상적인 포부지만 문항 목적상 자연스러운 경우, + 이미 다른 분석이 동일한 핵심 문제를 다루는 경우, 명확한 문제 문장을 특정할 수 없는 경우, + 답변 전반에 대한 일반적 아쉬움만 있는 경우, 단순히 분석 개수가 적다는 이유, + "구체성 부족"만 있고 어느 문장이 왜 문제인지 특정할 수 없는 경우. + - MISSED_ANALYSIS를 부여할 때 shortRationale에는 놓친 원문 문장 또는 핵심 구절, 문제 유형, 기존 분석으로 커버되지 않는 이유를 간결하게 포함한다. + - 이 근거를 제시할 수 없으면 MISSED_ANALYSIS를 부여하지 않는다. + - 명확한 문제 문장이 없다고 판단했다면 MISSED_ANALYSIS를 errorCodes에 포함하지 않는다. + - "명확한 문제 문장이 없다"와 "MISSED_ANALYSIS"를 동시에 주장하지 않는다. + - "첨삭 대상이 명확하지 않다"와 "중요한 첨삭 대상을 놓쳤다"를 동시에 주장하지 않는다. + + [MISSED_MISSING_KEYWORD 판정 기준] + - MISSED_MISSING_KEYWORD는 다음을 모두 만족할 때만 caseErrorCodes에 포함한다. + 1. 실제 JD 원문의 mainTasks 또는 qualifications에 요구사항이 존재한다. + 2. JobDri 서비스 정책상 missing keyword 제외 대상이 아니다. + 3. answer에 동일하거나 의미상 충족되는 내용이 없다. + 4. 현재 missingKeywords에 동일하거나 충분히 유사한 키워드가 없다. + - missing keyword 평가 대상에서 제외한다: 자격증, 면허, 학력, 경력 연차, 나이, 법적/정형 보유 조건, 선택형 자격요건의 다른 선택지. + - 제외 예시: 사회복지사, 청소년상담사, 직업상담사, 운전면허, 대졸 이상, 경력 3년 이상. + - 제외 대상이 아닌 예시: Spring Boot 실무 경험, 포토샵 활용 능력, 엑셀 고급 활용, 더존 사용 능력, 4대보험 신고 경험. + - source=QUALIFICATION이라는 이유만으로 모든 항목을 제외하지 않는다. + - JD에 없는 키워드, 다른 직군의 일반 요구사항, 답변에 이미 존재하는 키워드, OR 조건에서 하나를 충족했을 때 나머지 선택지를 누락으로 판단하지 않는다. + - MISSED_MISSING_KEYWORD를 부여할 때 shortRationale에는 JD의 실제 요구사항, 답변에서 충족되지 않은 이유, 기존 missingKeywords로 커버되지 않는 이유를 간결하게 포함한다. + - 이 근거를 제시할 수 없으면 MISSED_MISSING_KEYWORD를 부여하지 않는다. [errorCode와 점수 정합성] - problemValidity <= 2이면 FALSE_POSITIVE_ANALYSIS 후보로 검토한다. @@ -132,9 +179,12 @@ String buildPrompt(NlgJudgeInput input) { - 좋은 문장 오탐: 이미 수치/방법/결과가 충분한 문장을 MENTIONED로 잡으면 problemValidity=1~2, errorCodes=[FALSE_POSITIVE_ANALYSIS]. - 문맥 무시: 앞뒤 문장에 근거가 있는데 대상 문장만 보고 부족하다고 하면 contextAwareness=1~2, errorCodes=[CONTEXT_IGNORED]. - 메타 improvement: "구체적으로 작성했습니다"처럼 첨삭 행위를 설명하면 nonMeta=1~2, errorCodes=[META_IMPROVEMENT]. - - 빈 결과의 false negative: questionAnalyses=[]이지만 답변에 명백한 첨삭 대상이 있으면 noAnalysisAppropriateness=1~2, errorCodes=[MISSED_ANALYSIS]. + - MISSED_ANALYSIS를 부여하지 않는 빈 결과: 구체적인 경험과 직무 연결이 충분하고 일부 문장이 더 자세해질 수는 있지만 명백한 오류나 첨삭 대상이 없으면 questionAnalyses=[], noAnalysisAppropriateness=4~5, MISSED_ANALYSIS 없음. + - MISSED_ANALYSIS를 부여하는 빈 결과: answer에 "저는 모든 업무를 완벽하게 해낼 수 있습니다."처럼 근거 없는 과장 표현이라는 명확한 문제 문장이 있고 questionAnalyses=[]이면 noAnalysisAppropriateness=1~2, errorCodes=[MISSED_ANALYSIS]. shortRationale에 원문 구절과 문제 유형을 명시한다. - 적절한 빈 결과: 답변에 명백한 문제 문장이 없고 강점/누락 키워드도 적절하면 noAnalysisAppropriateness=4~5, errorCodes=[NONE]. - - 누락 키워드 실패: JD 핵심 경험 요구사항 후보가 있는데 missingKeywordsJson=[]이면 missingKeywordsCoverage=1~2, errorCodes=[MISSED_MISSING_KEYWORD]. + - 정형 자격증 누락 금지: JD가 "사회복지사, 청소년상담사 중 1개 이상"이고 answer에 "청소년상담사 3급 보유"가 있으면 missingKeywordsJson=[]이어도 MISSED_MISSING_KEYWORD 없음. 사회복지사를 누락으로 보지 않는다. + - JD 밖 키워드 금지: JD가 "엑셀 고급 활용, 4대보험 신고, 더존 사용"인데 채용 파이프라인 소싱, 온보딩 프로그램 기획을 기대하면 안 된다. 실제 JD에 없으므로 MISSED_MISSING_KEYWORD 금지. + - 실제 누락 키워드 실패: JD에 "Spring Boot 기반 REST API 개발"이 있고 answer에 Java 학습 경험만 있으며 Spring Boot 사용 언급이 없고 missingKeywordsJson=[]이면 missingKeywordsCoverage=1~2, errorCodes=[MISSED_MISSING_KEYWORD] 가능. - 누락 키워드 정상 빈 배열: JD 핵심 경험 요구사항이 답변에 충분히 반영되어 missingKeywordsJson=[]이면 MISSED_MISSING_KEYWORD를 사용하지 않는다. - 점수 예시는 anchoring용이 아니다. 1,2,3,4,5를 실제 오류 심각도에 따라 분산해서 사용한다. diff --git a/src/test/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClientTest.java b/src/test/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClientTest.java index 6fa9f40..5c6e40f 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClientTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClientTest.java @@ -62,7 +62,7 @@ void buildPromptContainsJudgeRulesWithoutChainOfThoughtOutput() { assertThat(prompt).contains("actual missingKeywords가 빈 배열이라고 해서 자동으로 정확한 것이 아니다"); assertThat(prompt).contains("actualMissingKeywordCount"); assertThat(prompt).contains("validatedMissingKeywordCandidateCount"); - assertThat(prompt).contains("기본값을 4로 두지 말고"); + assertThat(prompt).contains("기본값을 3이나 4로 두지 말고"); assertThat(prompt).contains("1,2,3,4,5를 실제 오류 심각도에 따라 분산"); } @@ -131,6 +131,78 @@ void buildPromptKeepsExistingJudgeCriteria() { .contains("MISSED_MISSING_KEYWORD"); } + @Test + @DisplayName("MISSED_ANALYSIS는 명확한 문제 문장과 근거가 있을 때만 허용하도록 지시한다") + void buildPromptRestrictsMissedAnalysisCriteria() { + String prompt = buildPrompt("[]", "{}", "{}"); + + assertThat(prompt) + .contains("[MISSED_ANALYSIS 판정 기준]") + .contains("독립적으로 식별 가능한 명확한 문제 문장 또는 핵심 구절") + .contains("단순한 \"더 구체적이면 좋음\" 수준이 아니다") + .contains("문제 유형을 특정할 수 있다") + .contains("실질적인 첨삭 가치 손실") + .contains("현재 questionAnalyses에 동일하거나 충분히 유사한 문제 분석이 존재하지 않는다") + .contains("문장이 더 좋아질 수 있다는 정도") + .contains("수치가 없다는 이유만 있는 경우") + .contains("명확한 문제 문장을 특정할 수 없는 경우") + .contains("\"구체성 부족\"만 있고 어느 문장이 왜 문제인지 특정할 수 없는 경우") + .contains("놓친 원문 문장 또는 핵심 구절, 문제 유형, 기존 분석으로 커버되지 않는 이유") + .contains("명확한 문제 문장이 없다고 판단했다면 MISSED_ANALYSIS를 errorCodes에 포함하지 않는다") + .contains("\"명확한 문제 문장이 없다\"와 \"MISSED_ANALYSIS\"를 동시에 주장하지 않는다") + .contains("noAnalysisAppropriateness=4~5, MISSED_ANALYSIS 없음") + .contains("저는 모든 업무를 완벽하게 해낼 수 있습니다."); + } + + @Test + @DisplayName("MISSED_MISSING_KEYWORD는 서비스의 정형 자격요건 제외 정책과 JD 근거를 따른다") + void buildPromptAlignsMissedMissingKeywordWithServicePolicy() { + String prompt = buildPrompt("[]", "{}", "{}"); + + assertThat(prompt) + .contains("[MISSED_MISSING_KEYWORD 판정 기준]") + .contains("실제 JD 원문의 mainTasks 또는 qualifications에 요구사항이 존재한다") + .contains("JobDri 서비스 정책상 missing keyword 제외 대상이 아니다") + .contains("answer에 동일하거나 의미상 충족되는 내용이 없다") + .contains("현재 missingKeywords에 동일하거나 충분히 유사한 키워드가 없다") + .contains("자격증, 면허, 학력, 경력 연차, 나이") + .contains("선택형 자격요건의 다른 선택지") + .contains("사회복지사") + .contains("청소년상담사") + .contains("운전면허") + .contains("대졸 이상") + .contains("경력 3년 이상") + .contains("Spring Boot 실무 경험") + .contains("포토샵 활용 능력") + .contains("엑셀 고급 활용") + .contains("더존 사용 능력") + .contains("4대보험 신고 경험") + .contains("JD에 없는 키워드") + .contains("OR 조건에서 하나를 충족했을 때 나머지 선택지를 누락으로 판단하지 않는다") + .contains("사회복지사, 청소년상담사 중 1개 이상") + .contains("채용 파이프라인 소싱, 온보딩 프로그램 기획") + .contains("Spring Boot 기반 REST API 개발"); + } + + @Test + @DisplayName("overallUsefulness와 noAnalysisAppropriateness는 1~5 전체 범위를 쓰도록 rubric을 제공한다") + void buildPromptDefinesUsefulnessAndNoAnalysisRubrics() { + String prompt = buildPrompt("[]", "{}", "{}"); + + assertThat(prompt) + .contains("5점: 명확한 첨삭 대상이 없으므로 0개가 적절함.") + .contains("4점: 아주 사소한 개선 여지는 있으나 분석 없음이 대체로 적절함.") + .contains("3점: 판단이 애매함.") + .contains("2점: 명확한 첨삭 대상이 일부 존재함.") + .contains("1점: 여러 개의 명확한 문제를 놓침.") + .contains("5점: 분석, 강점, 누락 키워드가 대부분 정확하고 사용자가 바로 수정에 활용할 수 있음.") + .contains("4점: 일부 아쉬움은 있으나 핵심 진단과 개선 방향이 유용함.") + .contains("3점: 일부는 유용하지만 중요한 누락 또는 부정확성이 존재하는 혼합 품질.") + .contains("2점: 다수 평가가 부정확하거나 핵심 문제를 놓쳐 활용도가 낮음.") + .contains("1점: 대부분 잘못됐거나 JD/답변과 무관하여 실질적으로 사용할 수 없음.") + .contains("analysisCount가 0이라는 이유만으로 overallUsefulness를 자동으로 3점 처리하지 않는다"); + } + @Test @DisplayName("NLG judge evaluate는 limiter 예외를 감싸지 않고 그대로 전파한다") void evaluateRethrowsLimiterRuntimeException() { From 7a269e4cc3811abff0d24549502a505efdf141bb Mon Sep 17 00:00:00 2001 From: wooh Date: Thu, 30 Jul 2026 01:22:35 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[Fix]=20NLG=20Judge=20=ED=94=84=EB=A1=AC?= =?UTF-8?q?=ED=94=84=ED=8A=B8=20=EC=A0=95=EC=B1=85=20=EB=AC=B8=EA=B5=AC=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MISSED_ANALYSIS 판정 기준을 별도 상수로 분리 - MISSED_MISSING_KEYWORD 판정 기준을 별도 상수로 분리 - actual=[] missingKeywords 평가에서 정형 자격요건 제외 정책 우선순위 명시 - NLG Judge 프롬프트 contract 테스트 보강 --- .../evaluation_nlg_judge_policy_alignment.csv | 21 ++++++ ..._nlg_judge_policy_alignment_comparison.csv | 3 + .../evaluation/NlgEvaluationAiClient.java | 72 +++++++++++-------- .../evaluation/NlgEvaluationAiClientTest.java | 2 + 4 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 evaluation/evaluation_nlg_judge_policy_alignment.csv create mode 100644 evaluation/evaluation_nlg_judge_policy_alignment_comparison.csv diff --git a/evaluation/evaluation_nlg_judge_policy_alignment.csv b/evaluation/evaluation_nlg_judge_policy_alignment.csv new file mode 100644 index 0000000..3a2489a --- /dev/null +++ b/evaluation/evaluation_nlg_judge_policy_alignment.csv @@ -0,0 +1,21 @@ +caseId,sourceResultFile,analysisCount,averageRelevance,averageProblemValidity,averageSentenceTypeConsistency,averageReasonCorrectness,averageContextAwareness,averageFaithfulness,averageTenseConsistency,averageUsability,averageNonMeta,averageMeaningPreservation,noAnalysisAppropriateness,strengthsPrecision,strengthsCoverage,missingKeywordsPrecision,missingKeywordsCoverage,overallUsefulness,errorCodes,shortRationale,judgeInputTokens,judgeOutputTokens,judgeLatencyMs,failureStage +EV-01,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,1,4.0,3.0,4.0,4.0,3.0,4.0,5.0,4.0,4.0,4.0,4,5,5,5,5,4,"[""NONE""]","전반적으로 경험이 잘 드러나지만, 구체적인 절차와 결과 수치가 부족합니다.",6315,270,15590, +EV-02,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,5,5,5,5,5,4,"[""NONE""]","전반적으로 경험과 강점이 잘 드러나 있으며, 분석이 없지만 주요 문제는 발견되지 않았습니다.",5753,117,9338, +EV-03,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.5,3.5,3.5,4.0,3.5,4.0,4.0,4.0,4.0,4.0,4,5,5,5,5,4,"[""NONE""]","전반적으로 분석 역량과 경험이 잘 드러나 있으나, 구체적인 행동과 결과를 더 강조할 필요가 있습니다.",7349,441,19185, +EV-04,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,1,5,5,5,5,2,"[""MISSED_ANALYSIS""]",명확한 문제 문장이 존재하나 분석이 없었습니다. 구체적인 행동 계획과 방법이 부족한 점이 주요 문제입니다.,7421,130,13282, +EV-05,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,4.0,3.0,3.0,4.0,3.0,4.0,4.0,4.0,4.0,4.0,4,5,5,5,5,4,"[""NONE""]","지원 동기와 포부가 구체적이지 않아 개선 여지가 있으며, 전반적으로 유용한 분석이 제공되었습니다.",6222,420,11695, +EV-06,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,1,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5,5,5,5,5,5,"[""NONE""]","전반적으로 구체적인 성과와 실행 방법이 잘 드러나 있으며, 분석이 적절하게 이루어졌습니다.",8921,312,9913, +EV-07,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,5,5,5,5,5,4,"[""NONE""]","전반적으로 강점과 누락 키워드가 적절하게 반영되어 있으며, 분석이 없지만 명확한 문제 문장이 없습니다.",5299,123,5722, +EV-08,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.0,3.0,3.0,4.0,3.0,4.0,4.0,3.0,4.0,4.0,4,5,5,5,5,4,"[""NONE""]","전반적으로 자기소개서의 강점이 잘 드러나지만, 구체적인 실행 방안과 방법론이 부족하여 개선 여지가 있습니다.",7252,421,7677, +EV-09,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,4.0,3.0,4.0,4.0,3.0,4.0,5.0,4.0,4.0,4.0,4,5,5,5,5,4,"[""NONE""]","전반적으로 구체적인 행동과 방법론이 부족하지만, 경험을 잘 설명하고 있어 유용한 분석입니다.",6245,408,7479, +EV-10,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,4,5,5,5,5,3,"[""NONE""]",전반적으로 구체적인 경험과 성과가 부족하여 활용도가 낮습니다.,6458,109,2962, +EV-11,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,1,3,3,2,2,2,"[""MISSED_ANALYSIS"",""MISSED_MISSING_KEYWORD""]","명확한 문제 문장이 존재하나 분석이 없으며, JD의 핵심 경험 요구사항이 누락되었습니다.",6512,135,4096, +EV-12,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,4.0,4.0,4.0,4.0,4.0,4.0,5.0,4.0,4.0,4.0,4,5,5,5,5,4,"[""NONE""]","전반적으로 직무 적합성을 잘 드러내고 있으나, 구체적인 행동이나 방법을 추가하면 더욱 명확해질 것입니다.",6739,417,8190, +EV-13,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,4.0,3.0,4.0,4.0,3.0,4.0,5.0,4.0,4.0,4.0,4,5,5,5,5,4,"[""NONE""]","전반적으로 경험과 방법론이 잘 서술되었으나, 구체성이 부족한 부분이 있어 개선 여지가 있습니다.",6369,430,7414, +EV-14,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,4,5,5,5,5,4,"[""NONE""]","지원 동기와 포부에서 구체적인 행동과 방법이 부족하지만, 전반적으로 강점이 잘 드러나고 있습니다.",6422,121,5742, +EV-15,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,1,3,3,2,2,2,"[""MISSED_ANALYSIS"",""MISSED_MISSING_KEYWORD""]","지원 동기와 직무 적합성에서 구체적인 사례가 부족하며, 채용 관련 경험이 누락되었습니다.",5344,136,3224, +EV-16,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,1,5,5,5,5,2,"[""MISSED_ANALYSIS""]",명확한 문제 문장이 존재하지만 분석이 없었습니다. 구체적인 행동 계획과 방법론이 부족한 점이 주요 문제입니다.,6419,130,5137, +EV-17,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,4.0,3.0,4.0,4.0,3.0,4.0,5.0,4.0,4.0,4.0,4,5,5,5,5,4,"[""NONE""]","전반적으로 경험의 구체성이 부족하지만, 분석이 유용하며 개선 방향이 명확합니다.",5751,394,7867, +EV-18,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,4,3,3,2,2,3,"[""MISSED_MISSING_KEYWORD""]",답변에서 구체적인 경험이 부족하여 JD의 '사내 일반 행정 지원' 요구사항을 충족하지 못하고 있습니다.,5772,132,3890, +EV-19,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,2,3.0,3.0,4.0,4.0,3.0,4.0,4.0,4.0,4.0,4.0,3,5,5,5,5,4,"[""NONE""]",지원 동기와 성격의 장단점에서 구체적인 경험이 부족하여 개선 여지가 있습니다. 그러나 전반적으로 유용한 분석이 이루어졌습니다.,5925,403,8503, +EV-20,evaluation/evaluation_ai_results_two_pass_provenance_strength_fix.csv,0,,,,,,,,,,,1,5,5,5,5,2,"[""MISSED_ANALYSIS""]",지원 동기와 포부에서 구체적인 행동이나 계획이 결여된 명확한 문제 문장이 존재하지만 분석이 없었습니다.,5767,130,2413, diff --git a/evaluation/evaluation_nlg_judge_policy_alignment_comparison.csv b/evaluation/evaluation_nlg_judge_policy_alignment_comparison.csv new file mode 100644 index 0000000..dc01f30 --- /dev/null +++ b/evaluation/evaluation_nlg_judge_policy_alignment_comparison.csv @@ -0,0 +1,3 @@ +sourceResultFile,caseCount,successCount,judgeFailedCount,averageRelevance,averageProblemValidity,averageSentenceTypeConsistency,averageReasonCorrectness,averageContextAwareness,averageFaithfulness,averageTenseConsistency,averageUsability,averageNonMeta,averageMeaningPreservation,noAnalysisAppropriateness,strengthsPrecision,strengthsCoverage,missingKeywordsPrecision,missingKeywordsCoverage,overallUsefulness,averageJudgeInputTokens,averageJudgeOutputTokens,averageJudgeLatencyMs,averageAnalysisCount,metaImprovementRate,unsupportedFactRate,falsePositiveAnalysisRate,fatalErrorRate,errorCodeCounts +evaluation/evaluation_nlg_judge_two_pass_provenance_strength_fix.csv,20,20,0,3.45,2.95,3.35,3.45,2.55,4.1,4.4,3.55,3.65,4.2,2.35,4.55,4.55,4.55,4.55,3.05,4919.75,287.45,4769.35,0.9,0.0,0.0,0.0,0.0,"{""CONTEXT_IGNORED"":4,""MISSED_ANALYSIS"":17,""MISSED_MISSING_KEYWORD"":3,""NONE"":3}" +evaluation/evaluation_nlg_judge_policy_alignment.csv,20,20,0,3.85,3.35,3.85,4.1,3.35,4.1,4.6,4.0,4.1,4.1,3.35,4.7,4.7,4.55,4.55,3.45,6412.75,258.95,7965.95,0.9,0.0,0.0,0.0,0.0,"{""MISSED_ANALYSIS"":5,""MISSED_MISSING_KEYWORD"":3,""NONE"":14}" diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClient.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClient.java index 0dd7bf6..117d39f 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClient.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClient.java @@ -18,6 +18,42 @@ @RequiredArgsConstructor @Slf4j class NlgEvaluationAiClient { + private static final String MISSED_ANALYSIS_RULES = """ + [MISSED_ANALYSIS 판정 기준] + - MISSED_ANALYSIS는 다음을 모두 만족할 때만 caseErrorCodes에 포함한다. + 1. answer에 독립적으로 식별 가능한 명확한 문제 문장 또는 핵심 구절이 존재한다. + 2. 그 문제는 단순한 "더 구체적이면 좋음" 수준이 아니다. + 3. 문제 유형을 특정할 수 있다. 예: 근거 없는 과장, 명시적 모순, 문항 의도 불충족, 핵심 행동/역할/방법/결과의 명확한 부재. + 4. 해당 문장을 분석하지 않아 사용자에게 실질적인 첨삭 가치 손실이 발생한다. + 5. 현재 questionAnalyses에 동일하거나 충분히 유사한 문제 분석이 존재하지 않는다. + - 다음 경우에는 MISSED_ANALYSIS를 부여하지 않는다. + 문장이 더 좋아질 수 있다는 정도, 수치가 없다는 이유만 있는 경우, 추상적인 포부지만 문항 목적상 자연스러운 경우, + 이미 다른 분석이 동일한 핵심 문제를 다루는 경우, 명확한 문제 문장을 특정할 수 없는 경우, + 답변 전반에 대한 일반적 아쉬움만 있는 경우, 단순히 분석 개수가 적다는 이유, + "구체성 부족"만 있고 어느 문장이 왜 문제인지 특정할 수 없는 경우. + - MISSED_ANALYSIS를 부여할 때 shortRationale에는 놓친 원문 문장 또는 핵심 구절, 문제 유형, 기존 분석으로 커버되지 않는 이유를 간결하게 포함한다. + - 이 근거를 제시할 수 없으면 MISSED_ANALYSIS를 부여하지 않는다. + - 명확한 문제 문장이 없다고 판단했다면 MISSED_ANALYSIS를 errorCodes에 포함하지 않는다. + - "명확한 문제 문장이 없다"와 "MISSED_ANALYSIS"를 동시에 주장하지 않는다. + - "첨삭 대상이 명확하지 않다"와 "중요한 첨삭 대상을 놓쳤다"를 동시에 주장하지 않는다. + """; + + private static final String MISSED_MISSING_KEYWORD_RULES = """ + [MISSED_MISSING_KEYWORD 판정 기준] + - MISSED_MISSING_KEYWORD는 다음을 모두 만족할 때만 caseErrorCodes에 포함한다. + 1. 실제 JD 원문의 mainTasks 또는 qualifications에 요구사항이 존재한다. + 2. JobDri 서비스 정책상 missing keyword 제외 대상이 아니다. + 3. answer에 동일하거나 의미상 충족되는 내용이 없다. + 4. 현재 missingKeywords에 동일하거나 충분히 유사한 키워드가 없다. + - missing keyword 평가 대상에서 제외한다: 자격증, 면허, 학력, 경력 연차, 나이, 법적/정형 보유 조건, 선택형 자격요건의 다른 선택지. + - 제외 예시: 사회복지사, 청소년상담사, 직업상담사, 운전면허, 대졸 이상, 경력 3년 이상. + - 제외 대상이 아닌 예시: Spring Boot 실무 경험, 포토샵 활용 능력, 엑셀 고급 활용, 더존 사용 능력, 4대보험 신고 경험. + - source=QUALIFICATION이라는 이유만으로 모든 항목을 제외하지 않는다. + - JD에 없는 키워드, 다른 직군의 일반 요구사항, 답변에 이미 존재하는 키워드, OR 조건에서 하나를 충족했을 때 나머지 선택지를 누락으로 판단하지 않는다. + - MISSED_MISSING_KEYWORD를 부여할 때 shortRationale에는 JD의 실제 요구사항, 답변에서 충족되지 않은 이유, 기존 missingKeywords로 커버되지 않는 이유를 간결하게 포함한다. + - 이 근거를 제시할 수 없으면 MISSED_MISSING_KEYWORD를 부여하지 않는다. + """; + private final OpenAIClient openAIClient; private final LlmConcurrencyLimiter llmConcurrencyLimiter; @@ -93,7 +129,7 @@ String buildPrompt(NlgJudgeInput input) { - keyStrengths: strengthsPrecision과 strengthsCoverage를 각각 1~5로 평가한다. 명백한 좋은 문장을 놓치면 MISSED_STRENGTH를 사용한다. - missingKeywords: missingKeywordsPrecision과 missingKeywordsCoverage를 각각 1~5로 평가한다. JD 핵심 경험 요구사항 누락을 놓치면 MISSED_MISSING_KEYWORD를 사용한다. - actual missingKeywords가 빈 배열이라고 해서 자동으로 정확한 것이 아니다. - - JD와 answer 기준상 필요한 누락 키워드가 존재하면 actual=[]라도 missingKeywordsCoverage를 낮게 평가하고 MISSED_MISSING_KEYWORD를 사용한다. + - JD와 answer 기준상 필요한 비정형 업무·역량 누락 키워드가 존재하면 actual=[]라도 missingKeywordsCoverage를 낮게 평가하고 MISSED_MISSING_KEYWORD를 사용한다. 단, 자격증, 면허, 학력, 경력 연차, 나이 등 정형 자격요건은 이후 [MISSED_MISSING_KEYWORD 판정 기준]의 제외 정책이 우선하며 MISSED_MISSING_KEYWORD 대상이 아니다. - actual=[]이고 실제로 누락 키워드가 없을 때만 정상 빈 배열로 평가한다. - 빈 배열은 precision과 coverage를 분리해 판단한다. - case 전체: overallUsefulness를 1~5로 독립 평가한다. 기본값을 3이나 4로 두지 말고 1,2,3,4,5 전체 범위를 실제 품질에 맞게 사용한다. @@ -126,37 +162,9 @@ String buildPrompt(NlgJudgeInput input) { - questionAnalysesJson이 빈 배열이면 output questionAnalysisEvaluations도 빈 배열이어야 한다. - 입력에 없는 분석 평가를 생성하거나, answer에서 임의 문장을 골라 evaluation 배열을 만들거나, 분석 개수를 0보다 크게 만들지 않는다. - [MISSED_ANALYSIS 판정 기준] - - MISSED_ANALYSIS는 다음을 모두 만족할 때만 caseErrorCodes에 포함한다. - 1. answer에 독립적으로 식별 가능한 명확한 문제 문장 또는 핵심 구절이 존재한다. - 2. 그 문제는 단순한 "더 구체적이면 좋음" 수준이 아니다. - 3. 문제 유형을 특정할 수 있다. 예: 근거 없는 과장, 명시적 모순, 문항 의도 불충족, 핵심 행동/역할/방법/결과의 명확한 부재. - 4. 해당 문장을 분석하지 않아 사용자에게 실질적인 첨삭 가치 손실이 발생한다. - 5. 현재 questionAnalyses에 동일하거나 충분히 유사한 문제 분석이 존재하지 않는다. - - 다음 경우에는 MISSED_ANALYSIS를 부여하지 않는다. - 문장이 더 좋아질 수 있다는 정도, 수치가 없다는 이유만 있는 경우, 추상적인 포부지만 문항 목적상 자연스러운 경우, - 이미 다른 분석이 동일한 핵심 문제를 다루는 경우, 명확한 문제 문장을 특정할 수 없는 경우, - 답변 전반에 대한 일반적 아쉬움만 있는 경우, 단순히 분석 개수가 적다는 이유, - "구체성 부족"만 있고 어느 문장이 왜 문제인지 특정할 수 없는 경우. - - MISSED_ANALYSIS를 부여할 때 shortRationale에는 놓친 원문 문장 또는 핵심 구절, 문제 유형, 기존 분석으로 커버되지 않는 이유를 간결하게 포함한다. - - 이 근거를 제시할 수 없으면 MISSED_ANALYSIS를 부여하지 않는다. - - 명확한 문제 문장이 없다고 판단했다면 MISSED_ANALYSIS를 errorCodes에 포함하지 않는다. - - "명확한 문제 문장이 없다"와 "MISSED_ANALYSIS"를 동시에 주장하지 않는다. - - "첨삭 대상이 명확하지 않다"와 "중요한 첨삭 대상을 놓쳤다"를 동시에 주장하지 않는다. + %s - [MISSED_MISSING_KEYWORD 판정 기준] - - MISSED_MISSING_KEYWORD는 다음을 모두 만족할 때만 caseErrorCodes에 포함한다. - 1. 실제 JD 원문의 mainTasks 또는 qualifications에 요구사항이 존재한다. - 2. JobDri 서비스 정책상 missing keyword 제외 대상이 아니다. - 3. answer에 동일하거나 의미상 충족되는 내용이 없다. - 4. 현재 missingKeywords에 동일하거나 충분히 유사한 키워드가 없다. - - missing keyword 평가 대상에서 제외한다: 자격증, 면허, 학력, 경력 연차, 나이, 법적/정형 보유 조건, 선택형 자격요건의 다른 선택지. - - 제외 예시: 사회복지사, 청소년상담사, 직업상담사, 운전면허, 대졸 이상, 경력 3년 이상. - - 제외 대상이 아닌 예시: Spring Boot 실무 경험, 포토샵 활용 능력, 엑셀 고급 활용, 더존 사용 능력, 4대보험 신고 경험. - - source=QUALIFICATION이라는 이유만으로 모든 항목을 제외하지 않는다. - - JD에 없는 키워드, 다른 직군의 일반 요구사항, 답변에 이미 존재하는 키워드, OR 조건에서 하나를 충족했을 때 나머지 선택지를 누락으로 판단하지 않는다. - - MISSED_MISSING_KEYWORD를 부여할 때 shortRationale에는 JD의 실제 요구사항, 답변에서 충족되지 않은 이유, 기존 missingKeywords로 커버되지 않는 이유를 간결하게 포함한다. - - 이 근거를 제시할 수 없으면 MISSED_MISSING_KEYWORD를 부여하지 않는다. + %s [errorCode와 점수 정합성] - problemValidity <= 2이면 FALSE_POSITIVE_ANALYSIS 후보로 검토한다. @@ -205,6 +213,8 @@ String buildPrompt(NlgJudgeInput input) { actualMissingKeywordCount: %s validatedMissingKeywordCandidateCount: %s """.formatted( + MISSED_ANALYSIS_RULES, + MISSED_MISSING_KEYWORD_RULES, input.caseId(), input.sourceResultFile(), input.mainTasks(), diff --git a/src/test/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClientTest.java b/src/test/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClientTest.java index 5c6e40f..7b79fdc 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClientTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/analysis/evaluation/NlgEvaluationAiClientTest.java @@ -161,6 +161,8 @@ void buildPromptAlignsMissedMissingKeywordWithServicePolicy() { assertThat(prompt) .contains("[MISSED_MISSING_KEYWORD 판정 기준]") + .contains("필요한 비정형 업무·역량 누락 키워드가 존재하면 actual=[]라도") + .contains("정형 자격요건은 이후 [MISSED_MISSING_KEYWORD 판정 기준]의 제외 정책이 우선") .contains("실제 JD 원문의 mainTasks 또는 qualifications에 요구사항이 존재한다") .contains("JobDri 서비스 정책상 missing keyword 제외 대상이 아니다") .contains("answer에 동일하거나 의미상 충족되는 내용이 없다")