RUDA DIRECTOR · AI 깊이 이해하기 — 자율 시스템 설계까지 · CHAPTER 10
정책 문서를 읽고 변경 사항을 보고하는 AI를 만들었다고 하자. 틀린 답을 볼 때마다 Prompt를 고쳤다. 날짜를 빼먹으면 날짜를 적으라고 쓰고, 적용 대상을 넓히면 대상 범위를 확인하라고 덧붙였다. 같은 문제를 다시 풀게 하자 모두 맞았다. 이제 새 문서에서도 잘할까?
30초 답: 이미 본 실패를 고치는 일은 필요하다. 다만 그 과정에서 선택에 사용한 문제의 점수와, 선택 과정에서 분리해 둔 새 문제의 점수는 증거의 성격이 다르다. 개발에는 정답을 활용하고, 최종 평가는 그 개발 결정에 쓰이지 않은 held-out 사례로 해야 한다. Training을 하지 않고 Prompt만 수정해도 이 구분이 필요하다.
정답 예시를 만드는 것 자체가 문제는 아니다. 정답은 채점에 필요하다. 문제는 평가 대상의 답·결과를 본 뒤 시스템이나 채점 기준을 그 사례에 맞춰 바꾸고, 같은 점수를 처음 보는 업무의 성능처럼 제시하는 데 있다.
Training·Development·Test: 파일 이름보다 사용 이력이 중요하다
| 데이터 역할 | 허용하는 사용 | 그 점수가 말하는 범위 |
|---|---|---|
| Training set | Fine-tuning 등 Weight 업데이트 | 학습한 사례의 적합도 |
| Development set | Prompt·검색 설정·모델·채점 규칙 개선 | 개발 중 비교와 오류 진단 |
| Final held-out test | 선택을 끝낸 시스템의 최종 측정 | 해당 표본·조건에서의 새 사례 성능 |
Prompt에 넣는 few-shot 예시는 추론 입력이지 Weight를 바꾸는 Training은 아니다. 그러나 그 예시와 거의 같은 문제로 평가하면 일반화에 대한 증거가 약해진다. 파일을 test.jsonl이라고 이름 붙여도, 그 결과를 보며 후보를 계속 고르면 사실상 개발 세트로 사용한 셈이다.
Cawley와 Talbot의 연구는 유한한 평가 표본에서 설정을 반복 선택하면 선택 기준 자체에 과적합하고, 성능 평가에도 편향이 생길 수 있음을 다룬다. LLM에서는 Weight뿐 아니라 Prompt, 검색 필터, Tool 선택 규칙도 선택 대상이다. 이 글은 그 분리 원칙을 애플리케이션 평가에 적용한다.
누출은 정답 복사 한 가지로 생기지 않는다
- 평가용 정답의 직접 노출: 채점용 정답·해설을 Prompt, 검색 index, Memory 또는 이전 대화에 넣어 답을 생성하게 한다. 채점기는 정답을 볼 수 있지만 평가 대상 시스템이 보는 입력과는 분리해야 한다.
- 같은 원본의 중복: 한 문서에서 만든 질문을 표현만 바꿔 개발·test 양쪽에 넣는다. 문장은 새로워도 문서와 답의 구조는 이미 익숙하다.
- Test 결과에 맞춘 선택: 최종 세트 점수를 보고 Prompt를 여러 번 고르거나, 못 맞힌 문제를 삭제하거나, 통과하기 쉽게 채점 기준을 바꾼다.
- 시간 경계 위반: 과거 시점의 판단을 평가하면서 그때는 없던 개정 문서나 사후 정답을 입력에 섞는다.
scikit-learn의 Data leakage 설명은 예측 시점에 쓸 수 없는 정보가 모델 구축에 들어와 성능 추정을 낙관적으로 만드는 문제를 다룬다. 문서 Agent에서도 먼저 “그 시점에 무엇을 읽을 수 있었는가”를 정해야 누출 여부를 판단할 수 있다.
RAG가 정답을 담은 원문을 검색하는 것은 그 자체로 누출이 아니다. 실제 업무에서도 그 문서를 읽도록 허용했다면 정상적인 작업이다. 반면 평가용 질문·모범 답안 묶음을 검색 index에 넣거나, 아직 공개되지 않은 미래 revision을 과거 시점 입력에 넣으면 평가 계약이 달라진다. 원문 접근 능력과 모범 답안 재현 능력을 구분해야 한다.
사전학습 오염(pretraining contamination)은 또 다른 층위다. 공개 벤치마크나 유사 문장이 기반 모델의 학습 데이터에 포함되었을 가능성을 뜻한다. GPT-3 논문도 학습 데이터와 평가 데이터의 겹침을 별도로 분석했다. 우리 앱의 개발·test 파일을 나눴다고 기반 모델의 전체 학습 이력까지 깨끗해지는 것은 아니다. 공개되지 않은 새 사례는 위험을 줄일 수 있지만, 오염이 전혀 없다고 단정할 근거는 되지 않는다.
무엇을 처음 보는 상황으로 남길 것인가
공개 문서 변경 보고 Agent의 목표가 “처음 보는 기관의 문서를 처리하기”라면, 같은 기관의 모든 문서와 파생 질문을 하나의 그룹으로 묶어 개발·test 사이에서 분리해야 한다. “처음 보는 문서”가 목표라면 원본 문서와 그 문서에서 만든 질문을 함께 묶는다. “아는 문서의 다음 개정을 처리하기”라면, 과거 revision으로 개발하고 이후 revision으로 평가하는 시간 분리가 더 직접적이다.
각각 다른 질문이다. 문서 단위 분리만으로 새 기관이나 미래 변화 대응을 입증할 수 없고, 시간 분리만으로 낯선 문서 형식에 대한 일반화를 입증할 수도 없다. 분리 단위는 배포 뒤 만날 새로움에 맞춘다. Group split과 시간 순서 평가의 원리는 scikit-learn의 교차검증 문서에서 확인할 수 있다.
- 먼저 성공 조건·대상 업무·분리 단위를 적는다
- 원본 ID, 문서 계열, revision, 생성 경로로 중복·유사 사례를 묶는다
- 개발용과 최종용 사례를 나누고, 최종용 정답과 결과에 대한 접근을 제한한다
- 개발 세트에서 후보와 채점기를 수정한 뒤 설정을 동결한다
- 최종 세트를 실행하고, 결과를 본 뒤 바꾼 버전은 새로운 평가로 구분한다
최종 평가에서 실패를 발견했다면 고쳐야 한다. 다만 그 사례는 이제 알려진 회귀 테스트로 남긴다. 이후 일반화 주장을 하려면 새로 분리한 평가 사례나 미리 정한 독립 평가 절차가 필요하다. “한 번 실행한 test는 버린다”는 뜻은 아니다. 고친 뒤 같은 사례를 다시 통과한 결과를 독립 검증으로 부르지 않는 것이 핵심이다.
문서 변경 보고를 위한 여섯 가지 합성 사례
이제 평가 세트에 어떤 실패를 넣을지 구체화하자. 다음은 설명을 위해 만든 가상 문서와 출력 규칙이다. 실제 기관의 정책이나 서비스 데이터가 아니며, 아래 사례를 전부 공개하므로 이 글 자체의 비공개 held-out 벤치마크도 아니다.
작업 계약은 간단하다. 지정된 두 revision을 비교해 확인된 대상·시행일만 보고한다. 변경이 없으면 no_change, 근거가 부족하거나 충돌하면 abstain, 읽기 자체가 실패하면 error를 반환한다. 바깥 시스템에 게시하거나 메시지를 보내는 일은 포함하지 않는다.
| 사례 | 입력 조건 | 기대하는 판정 |
|---|---|---|
| C01 · 일반 변경 | 이전과 달리 현재 revision에 신규 계정·10월 15일 명시 | report · 신규 계정 · 10월 15일 |
| C02 · 범위 함정 | 이전과 달리 현재 revision에 기존 계정·11월 1일 명시 | report · 기존 계정 · 11월 1일 |
| C03 · 변경 없음 | 두 revision의 대상·시행일 동일 | no_change |
| C04 · 출처 충돌 | 동일 우선순위의 현재 문서가 서로 다른 날짜 제시 | abstain · 충돌 표시 |
| C05 · 근거 누락 | 현재 문서에 시행일이 없음 | abstain · 날짜 추측 금지 |
| C06 · 읽기 실패 | 지정 문서 조회가 실패 | error · 변경 없음으로 처리 금지 |
C01만 늘리면 잘 답하는 능력은 볼 수 있어도 멈춰야 할 때 멈추는지는 알 수 없다. C04와 C05만 늘리면 아무 답도 하지 않는 시스템이 좋아 보일 수 있다. 답변해야 하는 경우와 보류해야 하는 경우를 함께 넣어야 한다. 실제 트래픽을 추정할 표본과 위험을 집중 점검할 도전 사례는 목적을 표시하고, 둘의 평균을 설명 없이 섞지 않는다.
같은 출력도 채점기를 바꾸면 83%와 50%가 된다
다음 코드는 Python 표준 기능만 사용하는 합성 채점 예제다. 모델을 호출하지 않는다. C01부터 C06까지의 정답과, 일부러 오류를 넣어 손으로 작성한 출력을 비교한다. 상태 이름만 맞는지 보는 채점과 대상·시행일까지 보는 채점의 차이를 재현할 수 있다.
# 설명용 정답과 수동 작성 출력. LLM 측정 데이터가 아니다.
# 각 튜플: (상태, 적용 대상, 시행일)
expected = [
("report", "new", "10-15"),
("report", "existing", "11-01"),
("no_change", None, None),
("abstain", None, None),
("abstain", None, None),
("error", None, None),
]
candidate = [
("report", "new", "10-15"),
("report", "all", "11-01"), # 대상을 과장
("no_change", None, None),
("abstain", None, "12-01"), # 보류하면서 날짜를 추측
("report", "all", "12-01"), # 없는 근거로 답변
("error", None, None),
]
always_abstain = [("abstain", None, None)] * len(expected)
def score(outputs, status_only=False):
assert len(outputs) == len(expected)
hits = sum(
a[0] == b[0] if status_only else a == b
for a, b in zip(outputs, expected)
)
return f"{hits}/{len(expected)} ({hits/len(expected):.1%})"
print("status_only:", score(candidate, True))
print("strict_fields:", score(candidate))
print("always_abstain:", score(always_abstain))
status_only: 5/6 (83.3%)
strict_fields: 3/6 (50.0%)
always_abstain: 2/6 (33.3%)
상태만 보면 C02의 범위 과장과 C04의 근거 없는 날짜가 통과한다. 필드를 함께 보면 두 오류가 드러난다. 모두 보류하는 출력은 보류가 필요한 두 사례만 통과한다. 숫자 세 개는 모델 성능이 아니라, 이 여섯 수동 출력과 두 채점 규칙의 계산 결과다.
이 strict 채점기도 완성품은 아니다. 정규화된 세 필드만 비교하므로 인용의 진실성, 보류 사유, 누락된 변경, 출력 바깥의 추가 주장은 검사하지 않는다. 자유로운 요약문 전체를 문자열 일치로 채점하면 같은 뜻의 올바른 답도 실패할 수 있다. 실전에서는 날짜·ID처럼 엄격히 비교할 필드와 의미 판단이 필요한 문장을 나누고, 인용된 revision과 근거가 실제로 주장을 지지하는지 별도로 확인한다.
Baseline은 복잡도를 추가할 이유를 묻는다
새 모델이나 Agent 구조를 넣기 전, 같은 세트에서 비교할 기준선을 정한다. 문서가 이미 구조화돼 있다면 필드 차이를 계산하는 결정론적 비교기가 강한 기준선이 될 수 있다. 비정형 문서라면 현재 쓰는 Prompt와 모델을 고정한 단순 파이프라인이 현실적인 출발점이다. 항상 보류하는 방식은 과도한 보류를 드러내는 대조군으로 쓸 수 있지만, 유용한 업무 기준선을 대신하지 못한다.
비교에는 같은 입력·원문 snapshot·도구 접근권한·채점기·실행 예산을 쓴다. 첫 시도 한 번의 결과와 여러 번 재시도한 뒤 가장 좋은 결과를 비교해서는 안 된다. 도구나 읽기 범위가 달라져야 하는 비교라면 그 차이를 별도 조건으로 밝힌다. 정확한 변경 보고, 잘못된 단정, 불필요한 보류, 실패 처리와 함께 시간·호출 수·비용도 기록해야 더 복잡한 설계의 이익을 판단할 수 있다.
예를 들어 새 버전에서 C01이 좋아졌지만 C04의 충돌 보류가 깨졌다면, 전체 평균 하나로 회귀를 가릴 수 있다. 두 버전이 동일한 각 문제에서 성공하거나 실패했는지 나란히 보자. 평균이 조금 올랐다는 이유만으로 중요한 실패를 허용할 수 있는지는 별도의 제품 결정이다.
LLM으로 채점할 때도 정답과 채점기를 검증한다
코드 채점은 구조·숫자·필수 필드를 빠르게 확인한다. LLM 채점은 요약의 누락이나 의미 왜곡을 다루는 데 유용하지만 또 하나의 변동 가능한 판단이다. Anthropic의 Agent 평가 가이드는 코드·모델·사람 채점의 역할과 한계를 구분하고, 모델 채점기를 사람의 판단으로 보정할 것을 권한다.
- 채점 기준은 “좋은 답” 대신 대상 범위·날짜·근거·보류 조건처럼 관찰 가능한 항목으로 적는다
- 개발용 정상 답과 오류 답으로 채점기를 점검한다. 동의어를 쓴 정답, 그럴듯한 오답, 필드가 빠진 답을 함께 넣는다
- 정답도 원문에 대조한다. 두 검토자가 다르게 판정한 사례는 모호한 계약이나 라벨 오류인지 확인한다
- 최종 평가 전에 rubric과 judge 모델·Prompt를 고정한다. 결과를 본 뒤 채점 규칙을 바꿨다면 변경 이유와 재평가 범위를 공개한다
최종 test의 정답을 별도 평가 담당자가 확인하는 일은 필요하다. 그 정답이 시스템을 고르는 쪽으로 흘러들지 않도록 역할과 접근을 구분하면 된다. 실패 사례를 읽는 행위가 모두 금지되는 것이 아니라, 무엇을 보고 무엇을 바꿨는지가 평가의 독립성을 결정한다.
재현 가능한 평가 세트에는 문제보다 많은 것이 필요하다
URL만 저장하면 다음 실행에서 다른 revision을 읽을 수 있다. 공개 문서라도 평가 당시 본 원문과 조회 시점을 보관하고, 실행 대상 입력과 채점용 정답을 구분한다. 다음 항목은 특정 제품의 포맷이 아니라 이 문서 보고 작업에 제안하는 최소 기록이다.
| 기록 묶음 | 남길 내용 | 다시 확인할 질문 |
|---|---|---|
| 사례 | case ID, 원본·계열 ID, split, 질문, 원문 snapshot·hash | 같은 문제와 같은 근거인가? |
| 정답·채점 | 기대 필드·근거 위치, 보류 조건, rubric·grader 버전 | 같은 기준으로 점수를 냈는가? |
| 시스템 | 모델 식별자, Prompt·코드 버전, 검색 index, Tool 설정 | 무엇을 바꿔 비교했는가? |
| 실행 | 시각, trial ID, 사용 가능한 생성 설정, 예산, 출력·오류·시간·비용 | 재시도와 실패를 빠뜨리지 않았는가? |
원문 수집·저장에는 이용 조건과 개인정보 처리 범위를 지킨다. 개인정보가 없는 합성 fixture는 채점 코드와 계약을 설명하기에 좋지만, 실제 입력 분포를 대표한다고 자동으로 볼 수 없다. OpenAI의 Evaluation best practices도 작업별 목표, 대표적인 사례, 자동 채점과 사람 판단의 일치를 강조한다. 이 글의 예제는 특정 평가 API나 유료 서비스에 의존하지 않는다.
같은 설정을 기록해도 원격 모델·검색 결과·외부 문서가 바뀌면 완전히 같은 출력을 보장하기 어렵다. 목표는 재현 조건을 가능한 한 고정하고, 고정할 수 없었던 요소를 드러내는 것이다. 매 trial의 임시 상태와 대화 이력도 초기화해 앞선 문제의 답이 다음 입력에 남지 않도록 한다.
작은 표본에서 90%라는 숫자를 읽는 법
가령 20문제 중 18개를 맞혔다면 관찰 정답률은 90%다. 이것만으로 다음 업무의 성공 확률이 90%라고 확정할 수는 없다. 20문제가 어떤 분포에서 왔는지, 같은 문서에서 파생돼 서로 비슷한지, 실패 두 건이 얼마나 중요한지 함께 봐야 한다.
작은 표본에서는 한 문제가 점수를 크게 움직인다. 비율의 불확실성을 표시할 때는 표본 추출과 독립성 가정에 맞는 방법을 골라야 한다. NIST의 이항 비율 신뢰구간 설명도 표본이나 실패 수가 작을 때 단순 정규 근사 구간이 부정확할 수 있음을 짚는다. 편의대로 모은 도전 사례에 신뢰구간만 붙여 실제 운영 신뢰도로 바꾸지는 못한다.
같은 20문제를 열 번 실행한 200회도 새로운 200문제를 평가한 것과 다르다. 반복 실행은 같은 문제에서의 출력 변동을 보여주고, 새 문제를 늘리는 일은 업무 범위의 폭을 넓힌다. 따라서 문제 수와 trial 수, 사례별 성공 횟수를 나눠 적는다. 작은 세트는 뚜렷한 버그를 빨리 찾는 출발점으로 쓰고, 미세한 우열이나 높은 신뢰성 주장은 더 넓은 증거로 확인한다.
평가를 고칠 때 지켜야 할 마지막 경계
오늘 틀린 사례를 내일의 회귀 테스트에 넣는 것은 좋은 개발이다. 최종 평가 사례를 바탕으로 수정했다면 그 사실을 기록하고, 다음 버전의 새로운 능력은 다시 분리한 사례로 확인하면 된다. 데이터가 부족하다면 최종 성능을 주장하기보다 현재 평가를 탐색적 개발 결과로 제시하는 편이 정확하다.
문서 변경 보고 Agent의 첫 평가에서 확인해야 할 것은 화려한 종합 점수보다 구체적이다. 새 문서를 읽었는가. 적용 대상을 넓히지 않았는가. 없는 날짜를 만들지 않았는가. 읽기 실패를 “변경 없음”으로 덮지 않았는가. 그리고 그 문제의 답을 미리 이용해 시스템을 고른 것은 아닌가. 이 질문에 답할 수 있을 때 다음 장의 질문, “이 시스템에 어디까지 자율성을 허용할 것인가”로 넘어갈 근거가 생긴다.
참고 자료
- Cawley & Talbot — On Over-fitting in Model Selection and Subsequent Selection Bias in Performance Evaluation: 모델 선택과 성능 평가 편향
- scikit-learn — Cross-validation: 개발·test 분리, group split, 시간 순서 평가
- scikit-learn — Common pitfalls: Data leakage: 평가 정보 누출의 정의와 방지
- Brown et al. — Language Models are Few-Shot Learners: 사전학습 데이터와 벤치마크 오염 분석
- Anthropic — Demystifying evals for AI agents: task·trial·grader와 평가 설계
- OpenAI — Evaluation best practices: 작업별 평가와 채점 보정 원칙
- NIST — Confidence intervals: 작은 표본의 이항 비율 불확실성
자료 확인: 2026년 10월 2일. 문서·날짜·출력은 교육용 합성 예시다. 코드는 주어진 출력의 채점 계산만 수행하며, 실제 LLM 호출·Fine-tuning·운영 성능 실험은 하지 않았다.
시리즈 탐색
Previous: Chapter 09 — RAG와 Fine-tuning의 차이: 최신 지식과 일관된 행동을 어디서 고칠까
Next: Chapter 11 — 자율성을 어디까지 허용할 것인가
Related: Chapter 08 — AI Hallucination은 왜 생기는가: 그럴듯한 오답을 막는 검증 설계
Series start: Chapter 01 — LLM의 학습과 추론: 프롬프트를 고치면 모델도 배우는가