오프라인 LLM 평가셋이 제품 성능을 속이는 네 가지 방식
모델 교체 실험에서 정확도가 8% 올랐는데 배포 후 고객 불만은 줄지 않는 일이 흔하다. 평가 코드가 틀리지 않아도 평가셋이 제품의 현재 트래픽을 대표하지 않거나, 정답이 누출되거나, 한 사용자의 유사 질문이 train과 test에 갈라져 있으면 숫자는 쉽게 좋아진다. 특히 에이전트 제품은 최종 답변만 채점하면 도구 선택 실패와 실행 실패를 숨긴다.
평가셋은 한 번 만들고 보관하는 시험지가 아니다. 샘플의 출처, 생성 시점, 사용자·문서 군집, 정답 근거, 채점기 버전을 관리하는 운영 자산이다. 이 글은 데이터 누출, 중복, 분포 이동, 채점기 편향을 어떻게 발견하고 제품 지표와 연결할지 구체적으로 다룬다.
평가 단위부터 제품 실패 단위와 맞춰야 한다
고객지원 봇에서 실패 단위는 질문 한 문장만이 아니다. 검색 쿼리 생성, 문서 선택, 답변, 인용, 정책 준수까지 이어진 trace다. 최종 문장이 그럴듯해도 잘못된 문서를 근거로 만들었다면 다음 문서 업데이트 때 무너진다. 반대로 표현은 정답과 달라도 올바른 정책 문서를 인용하고 해결 절차를 제공하면 성공일 수 있다.
샘플 스키마에 input, conversation_context, available_tools, retrieved_doc_ids, expected_facts, forbidden_actions, reference_answer, segment, captured_at을 둔다. 하나의 scalar score보다 retrieval recall, citation entailment, tool selection, argument correctness, policy violation, task completion을 분리한다. 그래야 점수가 떨어졌을 때 프롬프트를 고칠지 검색기를 고칠지 판단할 수 있다.
누출 1: 정답 문구가 프롬프트나 검색 문서에 숨어 있다
평가용 reference answer를 만드는 과정에서 그 문장을 검색 인덱스에 함께 넣거나, 파일명에 정답 레이블을 넣는 실수가 있다. 모델은 추론을 잘한 것이 아니라 평가 제작자의 표식을 읽는다. 분류 문제에서 예시 ID가 레이블과 규칙적으로 연결되거나, 시스템 프롬프트에 평가 카테고리 순서가 노출되는 경우도 같다.
샘플을 사람이 읽는 것만으로 충분하지 않다. 모델 입력으로 실제 직렬화되는 전체 payload를 저장하고 정답 문자열, 레이블 이름, reference 문구의 n-gram이 들어 있는지 자동 검사한다. RAG 평가는 검색 인덱스 스냅샷 ID와 문서 ingest 시간을 고정한다. 평가 질문을 만든 원본 문서가 당연히 검색되어야 하는 과제와, 외부 지식을 추론해야 하는 과제를 구분해 누출의 정의도 명시한다.
누출 2: 거의 같은 사용자가 양쪽 split에 들어간다
무작위 row split은 대화 데이터에서 위험하다. 같은 고객이 며칠 간격으로 같은 주문을 묻거나, 한 티켓에서 파생된 질문들이 문장만 바뀌어 여러 행이 된다. 이 중 일부를 few-shot 예시나 튜닝 데이터에 넣고 나머지를 test에 두면 모델은 문제 유형을 일반화한 것이 아니라 특정 주문 문맥을 재현할 수 있다.
split 키를 row ID가 아니라 tenant_id, user_id, ticket_thread_id, source_document_id 같은 군집으로 잡는다. 민감한 식별자를 직접 쓰지 못하면 안정적인 해시를 사용한다. 문장 임베딩으로 near-duplicate를 찾되 유사도 임계값 하나를 맹신하지 말고 숫자·이름만 다른 템플릿 질문도 별도 정규화해 비교한다. 중복 제거 전후 점수 차이를 보고하면 기존 성과가 얼마나 암기에 의존했는지 드러난다.
분포 이동: 지난 분기의 성공이 오늘을 설명하지 못한다
제품 UI, 가격 정책, 문서 구조, 사용자 층이 바뀌면 질문 분포도 바뀐다. 6개월 전 데이터에서 배송 문의가 절반이었지만 지금은 구독 해지와 환불이 늘었을 수 있다. 전체 정확도는 오래된 대량 세그먼트가 지배해 새로운 고위험 실패를 가린다. 평가셋에 captured_at과 product version을 넣어야 하는 이유다.
고정 회귀셋과 최근 트래픽셋을 분리한다. 회귀셋은 절대 다시 깨지면 안 되는 핵심 동작을 보고, 최근셋은 현재 분포를 반영한다. 대시보드에는 둘을 합친 단일 점수 대신 세그먼트별 샘플 수와 신뢰구간을 표시한다. 신규 세그먼트는 샘플이 적어 변동이 크므로 사람이 검토할 최소 건수와 배포 차단 조건을 따로 둔다.
시나리오 1: FAQ 봇의 정확도는 올랐는데 상담 전환이 늘어난 경우
평가셋은 짧고 단일 의도인 FAQ 질문으로 구성됐지만 실제 사용자는 이전 답변을 가리키며 “그럼 가족 계정은?”처럼 묻는다. 새 모델은 독립 질문 정답률이 높지만 대화 상태를 잃는다. 평가 입력에 직전 턴이 빠졌기 때문에 실패가 측정되지 않았다. 실제 로그에서 상담 전환 직전 3턴을 샘플링해 multi-turn 세그먼트를 만든다.
채점 기준도 최종 사실만 보지 않고 필요한 확인 질문을 했는지 본다. 가족 계정의 국가나 요금제가 없으면 바로 단정하는 답보다 clarifying question이 정답이다. reference answer 하나와 문자열 유사도를 비교하면 올바른 확인 질문을 오답 처리할 수 있다. answerable_without_clarification 필드를 넣고 행동 유형을 먼저 채점한다.
시나리오 2: 코딩 에이전트가 테스트는 통과하지만 변경 범위를 망친다
코딩 평가가 숨겨진 unit test 통과만 보면 에이전트가 요청하지 않은 API를 삭제하거나 설정 파일을 대규모 포맷팅해도 성공이다. 실제 리뷰 비용과 회귀 위험은 커진다. 샘플에는 허용된 파일 범위, 금지된 public API 변경, 최대 diff 크기, 새 dependency 정책을 포함한다.
채점은 테스트 통과와 함께 changed files, AST-level API diff, lint 결과, 보안 스캔, 패치 크기를 기록한다. 단, 작은 diff를 무조건 좋은 것으로 두면 필요한 리팩터링을 억제한다. 과제별 expected change surface를 정의하고 범위를 벗어난 수정만 벌점화한다. 동일 repository snapshot과 lockfile을 고정하지 않으면 dependency 업데이트 때문에 모델 비교가 오염된다.
LLM 채점기는 일관된 판사가 아니라 버전이 있는 모델이다
자유형 답변에 LLM judge를 쓰면 비용은 줄지만 채점기 자체가 긴 답변, 자신감 있는 문체, 특정 표현을 선호할 수 있다. 후보 모델 이름이 노출되면 브랜드 편향도 생긴다. 답변 순서를 바꿔 pairwise 평가를 반복하고, 모델 식별자를 숨기며, 길이를 통제한 변형 샘플로 편향을 점검한다.
judge prompt, judge model, temperature, rubric version을 결과와 함께 저장한다. 채점기 변경 전후에는 사람이 라벨한 calibration set에서 일치율과 세그먼트별 오차를 비교한다. “LLM 점수 4.2”보다 오류 유형별 precision/recall이 유용하다. 안전 위반처럼 드문 사건은 평균 점수에 섞지 말고 규칙 검사와 사람 검토를 결합해 별도 gate로 둔다.
배포 결정을 위한 평가 운영 기준
평가셋의 각 샘플은 출처와 정답 근거를 추적할 수 있어야 하며, 사용자·문서 군집 단위로 split해야 한다. 새 모델 결과에는 전체 평균 외에 신규 사용자, 장문 입력, 다중 턴, 고위험 도구 호출 같은 세그먼트 차이를 붙인다. 표본이 적으면 승패를 단정하지 않고 confidence interval과 실패 사례 원문을 함께 본다.
배포 gate는 “총점 1점 상승”이 아니라 critical_policy_violation=0, 핵심 세그먼트 비열화 한도, latency·cost 한도를 조합한다. 온라인 canary에서는 오프라인 task ID를 로그에 연결해 실제 완료율과 비교한다. 매주 false positive와 false negative를 평가셋에 되먹임하되 test를 반복 튜닝해 사실상 train set으로 만들지 않는다. 자주 보는 개발셋과 잠가 둔 최종 검증셋을 분리해야 숫자가 다시 제품을 설명한다.
평가셋 자체의 품질을 측정하는 메타 지표
모델 점수만 추적하지 말고 평가셋 건강 상태를 별도 대시보드로 만든다. 샘플별 마지막 사람 검토 시각, 근거 문서가 아직 유효한 비율, duplicate cluster 크기, 세그먼트 분포, judge-human disagreement를 본다. 정답 근거 URL이 삭제되거나 정책 버전이 바뀐 샘플은 모델 오답이 아니라 시험지 오류를 만들 수 있다. 이런 샘플은 조용히 수정하지 말고 dataset version을 올려 이전 결과와 비교 가능하게 한다.
사람 라벨도 무오류가 아니다. 고위험 세그먼트는 두 명이 독립 라벨하고 불일치 사유를 taxonomy로 남긴다. ambiguous_policy, missing_context, multiple_valid_actions, annotator_error처럼 분류하면 모델 문제가 아닌 제품 정책의 모호함이 드러난다. 일치율이 낮은 샘플을 제거하기만 하면 쉬운 문제만 남으므로, 먼저 정책 담당자가 정답 조건을 명료화해야 한다.
매 릴리스의 실패 샘플을 평가셋에 모두 추가하면 특정 모델의 약점에 과적합된다. 실제 트래픽에서 확률적으로 추출한 샘플, 고위험 사건, 과거 회귀를 일정 비율로 섞는다. 샘플 채택 규칙과 제외 사유를 저장하고, 모델 개발자가 볼 수 있는 dev set과 접근을 제한한 holdout set을 나눈다. 평가 파이프라인이 좋은 모델을 고르는 도구로 남으려면 시험지 변경도 코드 변경처럼 리뷰와 버전 관리가 필요하다.
팀 회의에서 숫자로 답해야 할 질문
이 주제를 운영 회의에 올릴 때는 ‘좋아졌다’는 표현을 금지하고 judge-human disagreement, near-duplicate cluster, holdout 세그먼트의 이전 값, 변경 후 값, 표본 수를 한 화면에 놓는다. 특히 같은 사용자 질문이 양쪽 split에 포함된 상태를 재현한 부하 또는 회귀 샘플을 고정해야 다음 배포와 비교할 수 있다. 정상 요청만 모은 평균은 사고 직전의 위험을 설명하지 못한다. 실패·강등·재시도가 포함된 분모와 제외 규칙도 함께 기록한다.
담당자는 지표 변화에 대응하는 단 하나의 조치를 미리 연결한다. judge-human disagreement가 경계를 넘으면 어떤 배포를 되돌릴지, near-duplicate cluster가 늘면 어떤 큐나 샘플을 열어볼지, holdout 세그먼트의 이상을 누가 확인할지를 runbook에 적는다. 알람이 다섯 개 동시에 울리는데 모두 ‘조사 필요’로 끝나면 제어면이 없는 것과 같다. 변경자는 release ID와 예상 효과를 남기고, 관찰 창이 끝난 뒤 실제 효과와 부작용을 기록한다.
마지막 검토에서는 비용과 품질을 같은 요청 ID로 연결한다. 비용이 줄었지만 실패 재시도가 늘었거나, 정확도가 올랐지만 p95가 제품 예산을 넘으면 성공이 아니다. 이 글에서 제시한 메트릭은 보고용 숫자가 아니라 다음 행동을 고르는 분기 조건이다. 임계치, 관찰 시간, rollback 조건까지 적혀 있을 때 비로소 구현 지식이 운영 정책이 된다.