-
프런트엔드 AX 설계기 4편 - LLM-as-judge는 왜 "다른 에이전트"가 채점해야 하는가AI 엔지니어링 2026. 7. 3. 19:47728x90반응형
1. 도입부 (Why This Matters)
AI가 spec을 쓰고, 같은 세션에 "이 spec 괜찮아?"라고 물으면 거의 항상 "좋다"는 답이 돌아온다. 점수를 매기게 해도 마찬가지다 — 늘 0.85쯤에서 통과한다. 자동 검증을 붙였다는 안도감은 크지만, 실제로 돌아간 건 검증 연극(theater)에 가깝다.
이 글은 그 자기 채점이 왜 항상 통과하는지(self-preference bias), 그걸 체크를 더 넣는 대신 구조로 어떻게 풀었는지(writer/evaluator 분리 + 절대 rubric + 실데이터 임계값 보정), 그리고 모델을 상위 티어로 올린 뒤에도 이 분리가 여전히 필요한지 A/B로 재측정한 결과까지 담는다. 읽는 데 8~10분. 다 읽고 나면 지금 돌리는 자동 검증이 theater인지 아닌지 바로 판별할 수 있다.
2. 핵심 개념 (What & Why)
self-preference bias(자기 선호 편향): LLM 평가자가, 사람이 보기엔 동급인 출력인데도 자기가 생성한 쪽을 더 높게 매기는 편향. 한 줄 비유로는 "자기 답안을 자기가 채점하면 후해진다"이다.
이 편향은 self-evaluation(reward modeling, self-refine 등)이 퍼지면서 본격적으로 문제가 됐다. 평가자(evaluator)와 피평가자(evaluatee)가 같은 모델일 때 새로운 편향이 끼어든다. Panickssery 등(2024)은 모델의 자기 인식 능력(자기 출력을 알아보는 정도)과 self-preference의 강도가 선형으로 비례한다는 걸 보였다. 즉 "방금 내가 쓴 거"라는 인식 자체가 점수를 끌어올린다.
여기서 설계의 분기점이 하나 있다. 이 편향은 "둘 중 뭐가 더 나아?"라는 비교(pairwise) 채점에서 특히 세게 나타나고, rubric 절대(pointwise) 점수에서는 양상이 다르다. 절대 점수는 자기 선호로 인한 조작에는 상대적으로 덜 취약한 대신, 런(run)마다 점수가 흔들리는(drift) 약점이 있다. 그래서 고른 조합이 "절대 rubric + 별도 콘텍스트 호출 + 실데이터로 임계값 보정"이다. 비교 채점의 자기 선호를 피하고, 절대 점수의 drift는 데이터로 메운다.
writer/evaluator 분리란 글을 쓴 세션(writer)과 채점하는 에이전트(evaluator)를 서로 다른 콘텍스트에 두는 것이다. "방금 내가 쓴 거"라는 자기 인식 채널을 끊으면 self-preference가 약해진다 — 위 문헌이 가리키는 바로 그 채널이다.
이 설계는 사실 "점수를 매기는 것 자체가 정직하지 않다"는 자기 반박에서 출발했다. 같은 LLM이 자기 spec을 채점하면 늘 0.85가 나오고, 임계값 0.2도 결국 임의값이며 checklist가 같은 효과를 더 정직하게 낸다는 반론이다. 이 반론을 인정하고 나서야 점수를 버리는 대신 "누가 채점하는가"를 바꾸는 쪽으로 방향이 잡혔다. 점수는 도구일 뿐이고, 정직성을 만드는 건 채점자의 위치다.
3. 동작 원리 (How It Works)

분리 없는 경로(그림 왼쪽)는 단순하다. writer 세션이 spec을 쓰고 같은 콘텍스트가 그대로 채점한다. 결과는 거의 항상 ~0.85 PASS — 검증 theater다.
분리된 경로(오른쪽)는 네 가지 장치로 구성된다.
- fresh context 호출. writer는 산출물(spec 텍스트·diff·규칙 참조)만 evaluator에게 넘긴다.
@evaluator는 writer의 세션 기억 없이, 넘겨받은 근거만 보고 채점한다. self-recognition 채널이 끊긴다. - 절대 rubric + 차원별 floor. ISO/IEC/IEEE 29148-2018 품질 특성을 토대로 5개 차원을 가중(25/20/20/20/15)해 절대 점수를 낸다. 각 차원에는 독립적인 floor가 있어, 한 차원이 바닥이면 전체 평균이 높아도 통과하지 못한다(per-component floor). 모든 점수에는 근거(justification) 텍스트가 붙는다.
- Score Gate 루프. 전체 임계값 미달이거나 어느 차원이라도 floor를 깨면 FAIL. 이때 가장 낮은 차원 하나만 콕 집어 질문하고, writer가 그 부분만 고쳐 재채점 한다.
- 좁은 트리거. 모든 plan에 게이트를 걸지 않는다. 변경 파일이 3개 이상이고
--update모드가 아닐 때만 발동한다. 작은 변경은 표본에서 빠지므로 뒤의 보정 수학도 깨끗하게 유지된다.
5개 차원은 완전성·일관성·명확성·검증가능성·실현가능성처럼 요구사항 품질을 나누는 축이다. 더불어 LLM-as-judge 문헌이 제시하는 편향 완화 기법 약 9가지 중 5가지를 적용했다 — 별도 채점자, 차원별 floor, 근거를 강제하는 분석적 rubric, 의도된 보류(Non-goals) 인식, 그리고 confidence floor. 앙상블이나 교차 모델 심판 같은 나머지는 비용 대비 효과가 표본에서 정당화될 때까지 미뤄두었다. "전부 적용"이 아니라 "지금 표본에서 값을 하는 것만"이 기준이다.
토큰 관점에서 분리는 공짜가 아니다. evaluator는 추가 에이전트 호출이고, 그만큼 비용·지연이 붙는다. 좁은 트리거는 그 비용을 정당화하는 장치다 — "병목을 만들 거면 그 값을 해야 한다".
마지막 퍼즐이 임계값이다. 절대 점수는 drift 하므로 임계값을 감으로 박으면 arbitrary 해진다. 그래서 archive에 쌓인 실제 spec 표본(N=8)을 근거로 전체 임계값을 0.2에서 0.3으로 올렸다. 이론(절대 점수는 흔들린다)과 운영(그래서 데이터로 맞춘다)이 여기서 만난다.
4. 실무 적용 (Practical Examples)
✅ 권장 패턴 (Good Practice)
별도 콘텍스트 evaluator를, 절대 rubric JSON(점수 + 근거 + 차원별 floor)으로, 좁은 hard-signal 트리거에서만 호출한다.
# 게이트 트리거: hard signal 하나로만 발동 (휴리스틱 금지) if affected_files >= 3 and mode != "--update": # writer 세션이 아니라 별도 컨텍스트에서 채점 r = call_agent("@evaluator", mode="score", payload=spec_text) breached = [c for c in r.components if c.score < c.floor] if r.overall < THRESHOLD or breached: # 전체가 아니라 '가장 낮은 차원' 하나만 질문 ask_user(question_for(r.lowest_component)) revise_then_rescore() # 고친 부분만 다시 채점evaluator의 출력은 사람이 아니라 기계가 소비하므로 스키마를 고정한다. 점수마다 근거를 강제하는 게 핵심이다.
{ "overall": 0.78, "verdict": "PASS", "components": { "completeness": { "score": 0.80, "floor": 0.75, "justification": "수용 기준 3개가 명시됨" }, "consistency": { "score": 0.72, "floor": 0.70, "justification": "용어 'task'가 2가지로 혼용" } } }❌ 안티패턴 (Anti-Pattern)
# 같은 세션에서 자기 spec을 자기가 채점 > "방금 쓴 이 spec, 0~1로 점수 매겨줘" < 0.85 (← 거의 항상 이 근처에서 통과)같은 콘텍스트의 자기 채점은 self-preference를 그대로 통과시킨다. 비교 채점으로 "내 출력 vs 대안"을 시키면 편향이 가장 세게 발동한다. 임계값을 감으로 0.2에 박아두고 안 건드리는 것도, 체크 항목 수만 늘려 게이트를 부풀리는 것도 같은 함정이다 — 정직성은 개수가 아니라 분리에서 온다.
🔍 실행 결과
상위 티어 모델로 업그레이드한 뒤, "이제 모델이 좋아졌으니 분리를 빼도 되지 않을까"를 A/B로 재측정했다. 이 저장소 한 환경의 소표본이라 일반화는 조심해야 하지만 방향은 일관됐다. 분리를 끈 조건에서 self-preference가 약 14점(점수 스케일 기준) 더 높았고, 결함을 잡아내는 recall은 약 절반(누락 2배), 수렴까지의 iteration도 4-0으로 분리 쪽이 우세했다. 모델을 올려도 writer/judge 분리는 load-bearing이었다. 깎아도 되는 건 분리가 아니라 capability-fluency scaffold(모델이 이미 잘하는 걸 거드는 보조 장치) 쪽이었다.
5. 장단점 및 고려사항
장점 단점 ✓ 자기인식 채널을 끊어 정직한 채점 ✗ 추가 에이전트 호출 = 토큰·지연 비용 ✓ 절대 rubric + floor로 약점 차원을 못 가림 ✗ 절대 점수는 run마다 drift → 임계값 보정 필요 ✓ 좁은 트리거로 비용을 정당화 ✗ 트리거·표본 관리라는 운영 부담 ✓ 실데이터 보정으로 arbitrary 탈출 ✗ 표본이 쌓이기 전엔 floor가 표준값(미보정) 6. 핵심 3줄 요약 (Key Takeaways)
- 같은 모델이 자기 출력을 채점하면 통과는 보장되지만 검증은 theater다 — 정직성은 writer/evaluator 분리에서 나온다.
- self-preference는 비교 채점에서 가장 세다. 절대 rubric + 별도 콘텍스트로 약화시키고, 절대 점수의 drift는 실데이터 임계값 보정으로 메운다.
- 모델을 업그레이드해도 분리는 load-bearing이다 — 깎을 것은 fluency scaffold이지 judge 분리가 아니다.
지금 자동 검증이 같은 세션에서 자기 채점 중이라면, 채점만 별도 콘텍스트 에이전트로 떼어내고 트리거를 "변경 파일 ≥ 3" 같은 hard signal 하나로 좁혀라. 그 두 가지만으로 theater의 절반은 사라진다.
결국 evaluator를 위한 출력 스키마·rubric·트리거를 설계하는 일은, "에이전트라는 사용자"를 위한 인터페이스를 설계하는 일이다. 명확한 입력 계약, 가릴 수 없는 피드백, 측정 가능한 수용 기준 — 사람을 위한 UI에서 늘 하던 craft를 평가자 에이전트라는 새 표면에 옮긴 것뿐이다. 인터페이스를 잘 만드는 craft는, 그 끝이 사람이든 에이전트든 하나다.
2026.06.26 - [AI 엔지니어링] - 프런트엔드 AX 설계기 3편 — `done` 경계를 측정으로 설계한 이야기
참고 자료 (검증 출처)
- Arjun Panickssery, Samuel R. Bowman, Shi Feng, "LLM Evaluators Recognize and Favor Their Own Generations" (2024) — 자기 인식 능력과 self-preference bias의 선형 상관. arxiv.org/abs/2404.13076
- "Self-Preference Bias in LLM-as-a-Judge" (2024) — LLM 심판의 자기 선호 편향 정량화. arxiv.org/abs/2410.21819
- "Pairwise or Pointwise? Evaluating Feedback Protocols for Bias in LLM-Based Evaluation" (2025) — 비교(pairwise) vs 절대(pointwise) 프로토콜의 편향 차이. arxiv.org/abs/2504.14716
- ISO/IEC/IEEE 29148-2018 — 요구사항 품질 특성(rubric 차원의 baseline).
본문의 자체 수치(+14점·recall 2배·iteration 4-0)는 이 저장소 한 환경의 소표본 측정값으로, 환경에 따라 다르다. 외부 문헌의 편향 크기도 각 연구 설정 기준이다.
반응형'AI 엔지니어링' 카테고리의 다른 글
프런트엔드 AX 설계기 3편 — `done` 경계를 측정으로 설계한 이야기 (0) 2026.07.01 프런트엔드 AX 설계기 2편 — 에이전트를 '자율 실행'에서 '명시적 동의'로 (0) 2026.06.26 프런트엔드 AX 설계기 1편 — AI 에이전트 설정을 4개 레이어로 쪼갠 이유 (0) 2026.06.24 프런트엔드 AX 설계기 0편 — 레거시 3개와 차세대 FE를 위한 워크플로우 설계기 (0) 2026.06.19 안 만든 것이 규율이다 — 절제로 끌고 간 토이 프로젝트 회고 (Scrooge 5편) (0) 2026.06.16 - fresh context 호출. writer는 산출물(spec 텍스트·diff·규칙 참조)만 evaluator에게 넘긴다.