평가 데이터셋은 시스템이 실제로 만나는 입력 분포를 대표해야 하고, 채점은 한 가지 방법에만 의존하지 않아야 합니다. 이 수업에서는 데이터셋 구성 원리와 코드 채점, 심판 모델, 사람 평가를 섞어 쓰는 설계, 그리고 이를 반복 실행 가능한 프레임워크로 만드는 방법을 다룹니다.
데이터셋은 분포를 대표해야 한다
평가 데이터셋을 만들 때 가장 먼저 정할 것은 크기가 아니라 어떤 분포에서 뽑을 것인가입니다. 개발자가 머릿속으로 지어낸 입력만 모으면, 실제 사용자가 보내는 입력과 다른 분포를 평가하게 됩니다. 이 어긋남은 데이터셋을 아무리 키워도 해소되지 않습니다.
실무에서 쓰는 구성 원칙은 세 갈래입니다.
- 대표 표본 — 실제 트래픽 로그에서 무작위로 뽑는다. 분포를 그대로 반영하는 몫이며, 전체 점수의 기준이 된다.
- 엣지 케이스 — 드물지만 실패하면 비싼 입력을 의도적으로 모은다. 빈 입력, 아주 긴 입력, 여러 언어가 섞인 입력, 모순된 지시, 악의적 입력이 여기에 속한다.
- 회귀 사례 — 과거에 실제로 실패했던 입력을 영구 보관한다. 한 번 고친 버그가 다시 나타나는 것을 잡는 몫이다.
세 갈래를 한 점수로 합산하면 안 됩니다. 대표 표본에서는 잘하는데 엣지 케이스에서 무너지는 시스템과 그 반대인 시스템은 처방이 완전히 다른데, 합산 점수는 둘을 같게 보이게 만듭니다. 갈래별로 따로 보고하는 것이 원칙입니다.
Anthropic 문서는 평가 설계에서 "실제 배포 조건을 반영하라"는 점을 강조합니다. 평가할 때만 이상적인 입력을 주고 운영에서는 잡음이 섞인 입력이 들어온다면, 평가 점수는 운영 품질을 예측하지 못합니다.
혼합 방법론 — 세 가지 채점 수단
요강이 말하는 "혼합 방법론(mixed methodologies)"은 채점 수단을 여러 개 쓰라는 뜻입니다. 각 수단은 잡아내는 것과 놓치는 것이 다릅니다.
| 수단 | 강점 | 약점 | 적합한 곳 |
|---|---|---|---|
| 코드 기반 채점 | 저렴하고 완전히 재현 가능 | 표현의 다양성을 처리 못 함 | 분류, 추출, 형식 준수 |
| 심판 모델 | 자유 텍스트의 품질을 대량 채점 | 자체 편향, 비용, 비결정성 | 요약, 재작성, 근거 충실도 |
| 사람 평가 | 최종 기준, 미묘한 문제 포착 | 느리고 비싸며 확장 불가 | 표본 검증, 심판 보정 |
세 수단은 대체재가 아니라 계층입니다. 코드로 잡을 수 있는 것은 코드로 잡고, 코드가 못 잡는 것만 심판 모델에 넘기고, 심판 모델이 믿을 만한지는 사람 평가 표본으로 주기적으로 확인합니다. 이 순서를 뒤집어 모든 것을 심판 모델에 맡기면 비용은 비용대로 들면서 형식 오류 같은 값싼 실패를 비싸게 잡게 됩니다.
def evaluate(case, output):
# 1단계: 코드로 잡히는 실패는 여기서 끝낸다
if not matches_schema(output, case.schema):
return {"pass": False, "reason": "schema_violation", "judged_by": "code"}
if case.must_include and not all(k in output for k in case.must_include):
return {"pass": False, "reason": "missing_required", "judged_by": "code"}
# 2단계: 자유 텍스트 품질만 심판 모델에 넘긴다
verdict = judge(case.input, output, rubric=case.rubric)
return {"pass": verdict.score >= case.threshold,
"reason": verdict.rationale, "judged_by": "llm"}
judged_by 를 결과에 남겨 두면 나중에 "심판 모델이 내린 판정만 모아 사람이 검토"하는 흐름을 만들 수 있습니다.
심판 모델의 함정과 보정
심판 모델(LLM-as-judge)은 강력하지만 그 자체가 평가 대상이 필요한 시스템입니다. 프로 시험에서는 심판 모델의 한계를 아는지를 묻습니다.
알려진 편향은 다음과 같습니다.
- 길이 편향 — 긴 답변을 더 좋게 평가하는 경향이 있습니다.
- 위치 편향 — 두 후보를 비교시킬 때 먼저 제시된 쪽에 유리하게 판정하는 경향이 있습니다.
- 자기 선호 — 자신과 같은 계열 모델이 만든 답변에 후하게 매기는 경향이 보고됩니다.
- 채점 기준 표류 — 루브릭이 모호하면 같은 답변에도 실행마다 다른 점수가 나옵니다.
보정 수단도 정해져 있습니다. 위치 편향은 후보 순서를 바꿔 두 번 채점하고 평균을 내면 상당 부분 상쇄됩니다. 채점 기준 표류는 루브릭을 서술형이 아니라 체크리스트와 점수 구간으로 못 박아 줄입니다. 그리고 무엇보다, 심판 모델의 판정을 사람 판정 표본과 주기적으로 대조해 일치율을 지표로 관리해야 합니다.
# 위치 편향 상쇄: 순서를 바꿔 두 번 물어본다
score_ab = judge(prompt, first=a, second=b)
score_ba = judge(prompt, first=b, second=a)
final = (score_ab + invert(score_ba)) / 2
심판 모델이 사람과 얼마나 일치하는지 모르는 상태에서 나온 점수는, 정확해 보이지만 근거가 없습니다. 시험 함정: "심판 모델을 쓰면 사람 평가가 더 이상 필요 없다"는 선택지는 틀렸습니다. 사람 평가의 역할이 전수 채점에서 심판 보정으로 바뀌는 것이지 사라지는 것이 아닙니다.
반복 실행 가능한 프레임워크로 만들기
평가를 한 번 돌려 보는 것과 평가 체계를 갖추는 것은 다릅니다. 체계가 되려면 다음 성질을 만족해야 합니다.
- 버전 고정 — 데이터셋, 프롬프트, 모델, 채점 코드에 각각 버전이 붙어 있어 어떤 조합에서 나온 점수인지 재구성할 수 있어야 합니다.
- 자동 실행 — 프롬프트나 도구 정의가 바뀌면 자동으로 돌아야 합니다. 사람이 기억해서 돌리는 절차는 결국 건너뛰게 됩니다.
- 결과 보존 — 점수만이 아니라 실패한 사례의 입력과 출력을 함께 남겨야 원인 분석으로 이어집니다.
- 홀드아웃 분리 — 개선에 쓰는 개발 셋과 최종 확인용 홀드아웃을 나누어, 홀드아웃은 자주 들여다보지 않습니다.
마지막 항목이 프로 수준에서 가장 자주 어겨집니다. 같은 데이터셋을 보며 프롬프트를 계속 고치면 그 데이터셋에만 맞는 프롬프트가 만들어집니다. 점수는 오르는데 실제 사용자 지표는 그대로인 전형적인 증상이 여기서 나옵니다.
배치 처리는 평가를 정기 작업으로 돌릴 때 비용을 낮추는 수단입니다. 평가는 즉시 응답이 필요하지 않은 대표적인 작업이라, 실시간 호출로 전수 평가를 돌리는 것은 불필요하게 비쌉니다.
# CI에서 회귀 셋만 빠르게, 야간에 전체 셋을 배치로
if os.environ.get("CI"):
cases = load_cases("regression") # 수십 건, 몇 분
else:
cases = load_cases("full") # 수천 건, 배치로 야간 실행
평가 결과는 통과·실패 한 줄이 아니라 갈래별 점수와 실패 사례 목록으로 남겨야 다음 개선의 입력이 됩니다.
시험 함정
- 개발자가 지어낸 입력만으로 데이터셋을 구성하는 선택지 — 실제 트래픽 분포를 대표하지 못하면 크기를 키워도 소용없습니다.
- 대표 표본·엣지 케이스·회귀 사례를 한 점수로 합산해 보고하는 선택지 — 처방이 다른 상태를 같게 보이게 만듭니다.
- "심판 모델을 도입하면 사람 평가는 불필요하다"는 선택지 — 사람 평가의 역할이 전수 채점에서 심판 보정으로 바뀔 뿐입니다.
- 심판 모델의 위치 편향을 프롬프트 문구로만 막으려는 선택지 — 후보 순서를 바꿔 두 번 채점해 상쇄하는 것이 표준 대응입니다.
- 형식 위반까지 심판 모델에 맡기는 선택지 — 코드로 잡히는 실패를 비싸게 잡는 설계입니다.
- 개발 셋과 홀드아웃을 구분하지 않는 선택지 — 같은 데이터를 반복해 보며 고치면 그 데이터에만 맞는 시스템이 됩니다.
실습 시나리오
실제 시험과 같은 형식의 시나리오 문제입니다.
평가 데이터셋 200건으로 프롬프트를 여러 차례 개선해 점수를 크게 올렸는데, 배포 후 실제 사용자 지표는 거의 변하지 않았습니다. 가장 유력한 원인은 무엇입니까?
빌드 연습 · 3계층 평가 파이프라인 구축
약 50분1.세 갈래 데이터셋 구성
실제 로그 표본 30건, 엣지 케이스 10건, 회귀 사례 5건을 각각 파일로 나눠 만듭니다.
기대 결과 · 세 파일이 분리되어 있고 각 사례에 기대 결과가 붙어 있습니다.
2.코드 채점 계층 구현
스키마 위반과 필수 항목 누락을 잡는 함수를 작성합니다.
기대 결과 · 실패 시 사유 문자열이 함께 반환됩니다.
3.심판 모델 계층 추가
코드 채점을 통과한 사례만 심판 모델에 넘기고, 체크리스트형 루브릭을 사용합니다.
기대 결과 · 결과에 판정 주체가 코드인지 심판인지 기록됩니다.
4.위치 편향 보정
두 후보를 비교하는 평가에서 순서를 바꿔 두 번 채점하고 평균을 냅니다.
기대 결과 · 순서를 뒤집었을 때 판정이 바뀌는 사례 수가 로그에 남습니다.
5.갈래별 보고서 출력
세 갈래의 점수를 합산하지 않고 각각 출력하고, 실패 사례 입력과 출력을 함께 저장합니다.
기대 결과 · 보고서에 갈래별 점수와 실패 사례 목록이 분리되어 나타납니다.