Skip to content

fix(ai): 정직한 "모르겠습니다" 가 오답처럼 감점되던 문제 - #271

Merged
i3months merged 1 commit into
devfrom
fix/ai-honest-dontknow-scoring
Sep 29, 2026
Merged

i3months merged 1 commit into
devfrom
fix/ai-honest-dontknow-scoring

Conversation

@i3months

Copy link
Copy Markdown
Member

면접 품질 백로그의 L6. 2개월 전 항목이라 먼저 현재 코드에 유효한지 확인했고, 유효했다 —
그리고 파고드니 세 곳이 어긋나 있었다.

핵심 전제: _build_score_basis 의 _mean 은 None 을 제외한다. 그래서 0 과 null 의 차이가
곧 점수 차이
다.

1. 스키마가 null 을 막고 있었다

specificity: float   # required
logic: float         # required
correctness: float | None

그런데 프롬프트는 DONT_KNOW·확인형 단답에 null 을 지시하고 있었다. 실제로 돌려 봤다:

프롬프트 지시대로 null → evaluation = None          ← 검증 실패를 파서가 삼킴
스키마대로 0          → specificity=0.0 logic=0.0   ← 평균을 끌어내림

null 을 쓰면 model_validate_json 이 터지고 parse_followup_result 가 예외를 삼켜
평가가 통째로 사라진다(structure 까지). 진짜 파싱 실패와 구분도 안 된다.
0 을 쓰면 모른다고 말한 것이 틀린 답을 길게 늘어놓은 것과 같은 취급이 된다.

어느 쪽도 맞지 않았다. 받는 쪽(MessageEvaluation)은 처음부터 전부 nullable 이었다 —
보내는 쪽만 막고 있었다.

2. 프롬프트가 모호했다

DONT_KNOW/CLARIFICATION 이면 채점은 보수적으로 (낮게/null) 둡니다.

모호하면 LLM 이 매번 다르게 고른다. 반드시 null 로 바꾸고 meta 스키마에도
<0~5 또는 null> 을 명시했다. null 은 '채점 대상 아님'이고 0 은 '형편없는 답변'이다.

3. structure 가 0 으로 새어 들어갔다 — 테스트가 잡았다

_STRUCTURE_SCORE = {"FULL_STAR": 5.0, "PARTIAL_STAR": 2.5, "NONE": 0.0}

1·2 를 고친 뒤 "전부 null 이면 근거 없음이어야 한다" 테스트가 실패했다. 채점 불가 답변의
structure="NONE" 이 0.0 으로 집계돼 전부 모름인 세션이 communication ≈ 0,
overall ≈ 0 으로 나왔다.

_is_scoreable(specificity·logic 이 둘 다 null이면 채점 불가)로 structure 도 제외한다.

structure="NONE" 자체는 정상 감점 사유다 — 길게 답했는데 구조가 없는 경우.
구분 기준은 structure 값이 아니라 채점 가능 여부다. 이것까지 빼면 과교정이라
따로 테스트로 막았다.

기준값 문자열에 채점 대상 아님 N건(… 감점 사유가 아님) 도 넣었다. 숫자만 주면 LLM 이
"답변 수가 적네" 로 읽고 깎을 수 있다.

테스트

테스트 기대
null 평가가 파싱에서 살아남는다 (structure 포함) 예전엔 통째로 사라짐
null 은 집계에서 빠지고 0 은 안 빠진다 logic 80 vs 40
전부 null 이면 0 이 아니라 '근거 없음'
채점 불가 답변이 structure 로 전달력을 안 끌어내린다 comm 90
길게 답했는데 구조 없는 답변은 여전히 감점 comm 30 (과교정 방지)
프롬프트가 "반드시 null" 요구 + meta 에 null 허용
기준값이 LLM 에게 '감점 사유 아님'을 알린다

Core 는 무변경

컬럼(DOUBLE PRECISION)·DTO(Double)·읽는 쪽(hasEval 체크)이 모두 이미 null 을 견딘다.
백로그가 "양측 동시" 를 경고했지만 확인해 보니 한쪽만 고치면 되는 상태였다.

AI 444건 통과.

면접 품질 백로그의 L6. 실제 코드에 아직 유효했고, 파고드니 세 곳이 어긋나
있었다.

`_build_score_basis` 의 `_mean` 은 None 을 제외한다. 그래서 **0 과 null 의 차이가
곧 점수 차이**다. 그런데:

## 1. 스키마가 null 을 막고 있었다

AnswerEvaluation.specificity/logic 이 required 였다. 프롬프트는 DONT_KNOW·
확인형 단답에 null 을 지시하는데, LLM 이 그대로 따르면 model_validate_json 이
터지고 parse_followup_result 가 그 예외를 삼켜 **평가가 통째로 사라졌다**
(structure 까지). 진짜 파싱 실패와 구분도 안 됐다.

  프롬프트 지시대로 null → evaluation = None
  스키마대로 0          → specificity=0.0 logic=0.0  (평균을 끌어내림)

어느 쪽도 맞지 않았다. 받는 쪽(MessageEvaluation)은 처음부터 전부 nullable
이었다 — 보내는 쪽만 막고 있었다.

## 2. 프롬프트가 모호했다

"보수적으로(낮게/null)" — 모호하면 LLM 이 매번 다르게 고른다.
"반드시 null" 로 바꾸고 meta 스키마에도 <0~5 또는 null> 을 명시했다.
null 은 '채점 대상 아님'이고 0 은 '형편없는 답변'이다.

## 3. structure 가 0 으로 새어 들어갔다 (테스트가 잡음)

_STRUCTURE_SCORE["NONE"] = 0.0 이라, 채점 불가 답변의 structure 가 그대로
집계됐다. 전부 모름인 세션이 communication ≈ 0, overall ≈ 0 으로 나왔다.

_is_scoreable(specificity·logic 둘 다 null 이면 채점 불가)로 structure 도
제외한다. **structure="NONE" 자체는 정상 감점 사유다** — 길게 답했는데 구조가
없는 경우. 구분 기준은 structure 값이 아니라 채점 가능 여부다.

기준값 문자열에 "채점 대상 아님 N건(감점 사유가 아님)" 도 넣었다. 숫자만 주면
LLM 이 "답변 수가 적네" 로 읽고 깎을 수 있다.

## 테스트

  null 평가가 파싱에서 살아남는다 (structure 포함)
  null 은 집계에서 빠지고 0 은 안 빠진다 — logic 80 vs 40
  전부 null 이면 0 이 아니라 '근거 없음'
  채점 불가 답변이 structure 로 전달력을 끌어내리지 않는다 — comm 90
  **길게 답했는데 구조 없는 답변은 여전히 감점된다 — comm 30**
  프롬프트가 "반드시 null" 을 요구하고 meta 에도 null 을 허용한다
  기준값이 LLM 에게 '감점 사유 아님'을 알린다

Core 는 무변경 — 컬럼(DOUBLE PRECISION)·DTO(Double)·읽는 쪽(hasEval 체크)이
모두 이미 null 을 견딘다.

AI 444건 통과.
@i3months
i3months merged commit 1a9155f into dev Sep 29, 2026
6 checks passed
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