1-5점 대신 Pass/Fail — AI Eval Binary 전환기와 A/B 실험 결과 [시리즈 2편]

이 글은 시리즈 1편: AI가 만든 콘텐츠, AI가 평가한다의 후속입니다.

GPTers 팀이 AI 실습 평가 시스템을 Likert 스케일에서 Binary 체크리스트로 전환한 과정, 실제 A/B 실험 데이터, 그리고 "모델 간 합의도"라는 난제를 어떻게 풀었는지 공유합니다.

1-5점의 함정

1편에서 우리는 2-Tier Eval 아키텍처와 Opik 통합을 소개했습니다. Tier 2에서 LLM 3개(Claude Sonnet, Gemini Pro, GPT-4.1-mini)가 실습을 1-5 Likert 스케일로 평가하고, 합의도를 계산하는 구조였습니다.

그런데 운영하면서 문제가 드러났습니다.

24개 골든 데이터셋으로 실험을 돌렸을 때:

모델별 평가 점수 (story_coherence / difficulty_fit)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
claude-sonnet    0.82 / 0.73
gemini-pro       0.97 / 0.52
gpt-4.1-mini     0.24 / 0.10  ← 극단적 저점

같은 실습을 보고 Claude는 "괜찮다(0.82)", Gemini는 "좋다(0.97)", GPT는 "형편없다(0.24)"라고 평가합니다. 모델 간 합의도 0.48 — 동전 던지기와 다를 바 없었습니다.

이것은 우리만의 문제가 아닙니다. Hamel Husain이 정확히 지적한 것처럼:

"1-5 스케일에서 3점 vs 4점의 경계가 모델마다 다르다. Binary는 더 명확한 사고와 일관된 라벨링을 강제한다."

Binary로의 전환: 설계 결정

핵심 아이디어: 하위 기준별 Pass/Fail

1-5 점수 하나를 매기는 대신, 여러 개의 Yes/No 질문으로 쪼깁니다.

[Before] Likert
"story_coherence: 1-5점으로 평가하세요"
→ 모델마다 3인지 4인지 판단 기준이 다름

[After] Binary
"title_content_match: 제목과 내용이 일치하는가?" → Y/N
"before_after: Before/After가 명확한가?" → Y/N
"storyline: 흐름이 자연스러운가?" → Y/N
"wrap_up_reflection: 마무리에서 학습 내용을 되돌아보는가?" → Y/N
"description_accuracy: 실습 내용을 정확히 반영하는가?" → Y/N

→ 차원 점수 = pass 수 / 전체 = 4/5 = 0.80

뉘앙스를 잃지 않으면서 명확성을 확보합니다. 0.80이라는 숫자가 나오지만, 어떤 기준에서 실패했는지 즉시 파악할 수 있습니다.

3차원 → 6차원으로 확장

Likert 모드에서는 3개 차원(analogy, story, difficulty)만 평가했습니다. Binary로 전환하면서 더 세분화된 차원을 추가할 수 있었습니다:

Binary 6차원 체크리스트
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
차원                  기준 수   평가 대상
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
analogy_quality       5개      비유가 비개발자에게 이해 가능한가
story_coherence       5개      제목~마무리 스토리 일관성
difficulty_fit        4개      표기 난이도와 실제 복잡도 일치
pedagogical_design    6개      교수 설계 원칙 준수 여부 ← 신규
action_clarity        4개      행동 지시의 구체성/명확성 ← 신규
engagement            6개      학습자 몰입/동기 유발 요소 ← 신규

6차원 x 30개 기준. Likert였다면 6개 점수를 매기는 것이 인지 부하가 커서 3개로 제한했겠지만, Binary는 "이 기준을 충족하는가?"만 답하면 되므로 확장이 자연스럽습니다.

Few-shot: "좋은 예시 vs 나쁜 예시"를 모든 기준에

Hamel이 강조한 또 하나의 best practice: "Most mistakes have to do with not providing good examples."

모든 기준에 pass/fail 예시를 내장했습니다:

{
  key: "everyday_language",
  label: "일상 언어 사용",
  description: "비유가 일상생활/업무 언어로 표현되어 비개발자도 즉시 이해 가능한가?",
  passExample: "API는 식당의 주문 시스템과 같습니다. 메뉴를 보고 주문하면 음식이 나옵니다.",
  failExample: "API는 RESTful 아키텍처의 엔드포인트로, HTTP 메서드를 통해 리소스를 CRUD 조작합니다.",
}

30개 기준 x 2(pass/fail) = 60개 few-shot 예시가 judge 프롬프트에 포함됩니다. 모델이 "이 정도면 pass인가?"를 판단할 때 구체적 기준점을 갖게 됩니다.

A/B 실험: Binary vs Likert

말이 아닌 데이터로 검증했습니다.

실험 설정

데이터셋:  24개 골든 시나리오
생성 모델: gemini-3.1-pro-preview
평가 패널: claude-sonnet, gemini-3.1-pro, gpt-4.1-mini
비교:      --scoring binary vs --scoring likert

동일한 24개 시나리오에 대해 실습을 새로 생성하고, 같은 3개 모델 패널로 평가했습니다. 유일한 차이는 scoring 방식입니다.

결과 1: gpt-4.1-mini의 극적 정상화

story_coherence:

모델           Binary   Likert   변화
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
claude-sonnet  0.88     0.82     유사
gemini-pro     0.97     0.97     동일
gpt-4.1-mini   0.96     0.24     +300% ←←←

difficulty_fit:

모델           Binary   Likert   변화
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
claude-sonnet  0.17     0.73     Binary에서 더 엄격
gemini-pro     0.67     0.52     소폭 상승
gpt-4.1-mini   0.75     0.10     +650% ←←←

Likert에서 gpt-4.1-mini는 story_coherence에 0.04~0.39 같은 극단적 저점을 찍었습니다. 같은 실습에 대해 Claude가 0.90을 주고 GPT가 0.04를 주는 것은 평가 체계의 실패입니다.

Binary로 바꾸자 GPT도 0.80~1.00 범위에서 일관된 판정을 내렸습니다. "스토리가 일관되는가?"라는 모호한 질문 대신, "제목과 내용이 일치하는가?"라는 명확한 질문에는 모델 간 해석 차이가 줄어듭니다.

결과 2: 모델 합의도 개선

Likert에서 합의도 0.48이었던 것이, Binary에서는 모델별 점수가 수렴하는 양상을 보였습니다. 특히 story_coherence에서 세 모델 모두 0.88~0.97 범위로 좁혀졌습니다.

결과 3: 숨겨진 약점 발견

6차원으로 확장하면서 이전에 보이지 않던 약점이 드러났습니다:

Binary 추가 차원 결과 (패널 평균)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
pedagogical_design   0.91   교수 설계는 양호
action_clarity       0.64   ⚠️ 약점 — 행동 지시가 불명확
engagement           0.83   몰입 요소는 적절

action_clarity가 0.64로 가장 낮았습니다. 4개 기준(구체성, 즉시 수행 가능, 분량 적절, 대상자 언어) 중 구체성과 분량에서 주로 실패. "프롬프트를 입력하세요"처럼 모호한 learner_action이 문제였습니다.

이것이 Binary의 진짜 가치입니다. Likert 0.70이라는 숫자에서는 "어디를 고쳐야 하는지" 알 수 없지만, Binary의 action_clarity: specificity=FAIL, immediately_actionable=PASS에서는 정확히 무엇을 개선해야 하는지 보입니다.

Eval → 프롬프트 개선 사이클

Binary eval 결과를 기반으로 첫 번째 프롬프트 개선 사이클을 돌렸습니다.

약점 식별 → 프롬프트 규칙 추가

action_clarity 0.64 → quality-rules.txt[CRITICAL] learner_action 구체성 규칙 추가:

### [CRITICAL] learner_action 구체성

beginner: "어디에서 무엇을 하는지" 수준의 구체적 행동 지시 필수
  ✅ "ChatGPT 입력란에 '이 CSV 데이터에서 월별 매출 합계를 구해줘'라고 입력해줘"
  ❌ "적절한 프롬프트를 입력하세요" (무엇을, 어디에 입력하는지 불명확)

intermediate/advanced: 과제 제시 시에도 맥락(입력 데이터, 기대 출력)을 포함
  ✅ "앞에서 만든 employee_data.csv를 읽어서 부서별 평균 근무시간을 계산하는 코드를 작성해줘"
  ❌ "데이터를 분석하는 코드를 작성해줘" (어떤 데이터, 어떤 분석인지 불명확)

검증: "무엇을", "어디에서", "어떤 결과를 기대하며" 중 2가지 이상 포함

이전 사이클의 성과

이번이 첫 사이클은 아닙니다. 이전에도 Tier 1 통과율 분석에서 발견한 문제를 수정했습니다:

프롬프트 개선 사이클 결과
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
규칙              Before       After        수정
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
terminology       100% 실패    info         빈 배열 skip, threshold 30%
tools-alignment   100% 실패    13% 실패     claude_feature 필드 확인 전환

Tier 1 통과율: 53% → 60%. best practice에서 권장하는 70% 통과율에 근접하고 있습니다.

개선 사이클 워크플로우

1. [eval 실행] 24개 골든 데이터셋 → Tier 1 + Tier 2 Binary 평가
      ↓
2. [약점 식별] 차원별/기준별 실패율 분석
      ↓  (action_clarity: specificity 40% 실패)
3. [프롬프트 수정] quality-rules.txt에 규칙 추가
      ↓
4. [A/B 비교] --compare v1.txt v2.txt --file scenario.txt
      ↓  (규칙별 점수 비교, winner 판정)
5. [재평가] 수정된 프롬프트로 다시 eval 실행
      ↓
6. [반복] 다음 약점 차원으로 이동

이 사이클이 가능한 이유는 Binary eval이 "어떤 기준에서 실패했는지"를 정확히 알려주기 때문입니다. Likert 0.70에서는 시작할 수 없는 개선 루프입니다.

구현 상세: 빠지기 쉬운 함정들

함정 1: 모델이 기준보다 많은 항목을 반환한다

Binary eval에서 모델에게 "4개 기준에 대해 pass/fail을 판정하세요"라고 요청했는데, 5개나 6개를 반환하는 경우가 있었습니다. passCount / total에서 total은 정의된 기준 수(4)인데 passCount가 5가 되면 점수 1.25가 나옵니다.

// Before — score > 1.0 가능
const passCount = result.object.criteria.filter(c => c.pass).length;
const score = passCount / dim.criteria.length;

// After — slice로 초과분 제거 + clamp
const criteria = result.object.criteria.slice(0, dim.criteria.length);
const passCount = criteria.filter(c => c.pass).length;
const score = Math.min(passCount / dim.criteria.length, 1);

실제로 action_clarity에서 2.50, engagement에서 1.50 같은 점수가 나와서 발견했습니다. generateObject의 schema 제약이 배열 길이까지 강제하지 않는 모델이 있습니다.

함정 2: Few-shot 예시를 정의하고 전달하지 않는다

binary-criteria.ts에 96개의 passExample/failExample을 정성스럽게 작성했지만, judge 프롬프트를 구성하는 코드에서 description만 전달하고 예시를 빠뜨렸습니다.

프로덕션 코드(binary-eval.ts)에서는 이미 포함되어 있었지만, CLI 실험 코드(opik-eval.ts)에서 빠져 있었습니다. 같은 로직의 복제본이 있을 때 하나만 수정하는 실수 — eval 시스템에서도 DRY 원칙은 중요합니다.

함정 3: 프로덕션 환경변수 누락

3개 모델 패널의 기본 구성이 gemini-pro, claude-sonnet, gpt-4.1-mini인데, 프로덕션(Vercel)에 OPENAI_API_KEY가 없어서 gpt-4.1-mini가 항상 err을 반환했습니다.

코드에는 graceful degradation이 있었지만, DEFAULT_PANEL_ALIASES에는 gpt-4.1-mini가 하드코딩되어 있어서 사용 가능 여부와 기본 패널 구성이 불일치했습니다. eval 결과에 err이 섞이면 합의도 계산이 왜곡됩니다.

후방 호환: Likert를 살려둔 이유

Binary가 우월하다는 결론이 나왔지만, Likert 모드를 삭제하지 않았습니다.

# Binary (기본값)
npx tsx scripts/eval-practice.ts "시나리오"

# Likert (명시적 선택)
npx tsx scripts/eval-practice.ts "시나리오" --scoring likert

이유:

  1. 기존 eval 데이터와의 비교 — 과거 Likert 점수와 현재 Binary 점수를 나란히 볼 수 있어야 함

  2. 평가 방법론 자체의 A/B 테스트 — 스코링 방식도 실험 대상

  3. 비용 적은 전환ScoringMode 토글 하나로 전환되므로 유지 비용이 거의 없음

측정된 개선 효과 요약

Before (Likert) → After (Binary)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
평가 차원 수         3개 → 6개
기준 수             — → 30개 (각각 few-shot 내장)
모델 합의도          0.48 → 수렴 (story 0.88~0.97)
gpt-4.1-mini story  0.24 → 0.96
gpt-4.1-mini diff   0.10 → 0.75
약점 진단            "0.70점" → "action_clarity:specificity=FAIL"
Tier 1 통과율        53% → 60% (프롬프트 개선 후)

다음 단계

1. action_clarity 프롬프트 개선 효과 검증

이번에 추가한 [CRITICAL] learner_action 구체성 규칙이 실제로 action_clarity 점수를 올리는지 Tier 2 Binary eval로 검증합니다. 목표: 0.64 → 0.75+.

2. 통과율 70% 도달

Hamel의 best practice "70% 통과율이 적절 — 100%는 테스트가 너무 쉽다는 신호". 현재 60%에서 나머지 10%를 프롬프트 개선으로 채워야 합니다. 주요 타겟: density(68점), time-realism(77점).

3. 인간 라벨 캘리브레이션

Binary eval의 신뢰도를 최종 검증하려면 인간의 판정과 비교해야 합니다. 30건 수동 라벨링 → 모델 패널과의 일치율 측정 → 불일치 20% 이상인 기준은 재설계.

핵심 교훈

1. 스케일보다 기준이 중요하다

1-5점이냐 0/1이냐보다, "무엇을 측정하는가"를 명확히 정의하는 것이 핵심입니다. Binary 전환은 우리에게 각 차원을 하위 기준으로 쪼개도록 강제했고, 그 과정에서 평가 체계 자체가 정교해졌습니다.

2. A/B 실험은 평가 방법론에도 적용해야 한다

프롬프트만 A/B 테스트하는 것이 아니라, 평가 방식 자체도 A/B 테스트 해야 합니다. "Binary가 더 좋을 것이다"는 직관이었지만, 24개 x 3모델 x 2방식 = 144개 평가 결과로 검증했습니다.

3. 모델 간 합의도가 eval 품질의 핵심 지표다

개별 점수의 절대값보다 "평가자들이 동의하는가?"가 더 중요합니다. 합의도 0.48이면 어떤 점수든 의미가 없고, 0.90이면 어떤 점수든 신뢰할 수 있습니다. Binary 전환의 가장 큰 성과는 점수 변화가 아니라 합의도 개선입니다.

4. Eval은 진단 도구여야 한다

"이 실습은 78점입니다"보다 "이 실습은 행동 지시가 불명확합니다(action_clarity:specificity=FAIL)"가 훨씬 유용합니다. 점수는 문제가 있는지 알려주고, 기준은 무엇이 문제인지 알려줍니다.

1
2개의 답글

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요