AI 평가 두 실행을 비교하기 전: 같은 작업 목록인지 먼저 확인하기

성공률을 비교하기 전에 두 실행이 같은 작업을 평가했는지부터 확인해야 합니다. 양쪽 모두 세 건이라고 해서 같은 세 건은 아닙니다. 한쪽에서 빠진 작업을 새 작업으로 바꾸면 성공률 차이에 모델 변화와 작업 구성 변화가 함께 섞입니다.

Python의 목록·사전·함수를 읽을 수 있는 독자를 위한 실습입니다. AI 재시도 로그 검산기와 Python 실습에서 만든 작업 단위 결과를 두 실행 사이에서 대조합니다. 아래의 가상 자료로 빠진 작업과 추가된 작업을 찾고, 같은 작업 목록에 대한 비교를 시작할 수 있는지 확인합니다.

2/3에서 3/3으로 올랐는데 비교를 멈추는 이유

원래 평가하기로 정한 목록은 A·B·C입니다. 이전 실행은 A 통과, B 실패, C 통과로 2/3입니다. 새 실행에는 A 통과, B 통과, D 통과가 있어 3/3입니다. 하지만 C는 없어지고 D가 추가됐습니다. 새 실행의 세 행만 보고 “같은 과제에서 성공률이 올랐다”고 말할 수 없습니다.

작업이전 실행새 실행대조 결과
Apasspass양쪽 존재
Bfailpass양쪽 존재
Cpass없음새 실행 누락
D원래 목록 밖pass새 실행 추가

교집합 A·B만 비교하면 이전 1/2, 새 실행 2/2입니다. 이 계산은 “두 파일에 공통으로 남은 두 작업”의 결과입니다. 원래 맡긴 세 작업의 비교가 아닙니다. C가 빠진 이유를 모른 채 제외하면 비교 대상이 결과에 따라 선택됐을 가능성도 남습니다. 그래서 아래 코드는 교집합 ID만 진단 정보로 보여 주고, 전체 비교 결과는 보류합니다.

이전 작업 A·B·C는 2/3 통과, 새 작업 A·B·D는 3/3 통과. C가 빠지고 D가 추가되어 전체 작업의 직접 비교를 보류한다. 공통 작업 A·B만의 비교는 별도 범위다.
합성 예시입니다. 두 실행의 건수가 같아도 평가 대상이 바뀌면 같은 작업 목록에서 얻은 개선으로 해석할 수 없습니다.

원래 목록과 비교 조건을 따로 보존합니다

EXPECTED는 결과 파일에서 추출하지 않고 실행 전에 확정한 작업 목록입니다. 두 결과 파일에서 C가 동시에 빠져도 원래 목록과 대조하면 누락을 찾을 수 있습니다. 반대로 EXPECTED 자체가 불완전하다면 이 코드가 그것을 알아내지는 못합니다. 목록의 출처와 버전을 함께 남겨야 합니다.

이 실습의 비교 조건은 rubric_version, snapshot_cutoff, dataset_version 세 문자열입니다. 같은 판정 기준·관측 마감·자료 버전을 사용했는지 먼저 봅니다. snapshot_cutoff는 같은 형식으로 정규화해 넣었다고 가정하며 이 코드는 시각을 해석하거나 오프셋을 변환하지 않습니다. 같은 순간이라도 표기가 다르면 불일치로 보류하므로 입력 단계에서 기준을 맞추세요.

모델이나 프롬프트 버전은 의도적으로 비교하는 대상일 수 있어 이 세 조건에 넣지 않았습니다. 실제 보고서에는 양쪽 버전을 따로 기록해야 합니다. 버전 이름이 같다는 것만으로 파일 내용과 판정 코드가 같다는 보장은 없습니다. 입력 사본과 기준 문서도 대조해야 합니다.

복사해서 실행하는 비교 전 점검

아래 코드를 cohort_audit.py라는 UTF-8 파일로 저장하고 python3 cohort_audit.py로 실행하세요. 환경에 따라 python 명령을 사용합니다. 외부 라이브러리는 필요 없습니다. 한 행은 재시도 한 번이 아니라 한 작업의 최종 판정입니다. pass·fail·unknown만 받고, unknown이 있으면 이 실습의 완전한 이진 비교는 보류합니다. unknown을 실패로 바꾸거나 분모에서 빼지 않습니다.

REQUIRED = ('rubric_version', 'snapshot_cutoff', 'dataset_version')
META = {'rubric_version': 'rubric-v1', 'snapshot_cutoff': '2026-11-04T09:00:00+00:00',
        'dataset_version': 'tasks-v1'}
EXPECTED = ['A', 'B', 'C']
OLD = [('A', 'pass'), ('B', 'fail'), ('C', 'pass')]
NEW = [('A', 'pass'), ('B', 'pass'), ('D', 'pass')]

def index(rows):
    out = {}
    for row in rows:
        if not isinstance(row, (tuple, list)) or len(row) != 2:
            raise ValueError('행은 작업 ID와 판정 두 필드여야 합니다')
        task, verdict = row
        if type(task) is not str or not task or task.strip() != task:
            raise ValueError('작업 ID 오류')
        if task in out:
            raise ValueError('중복 작업 ID')
        if type(verdict) is not str or verdict not in {'pass', 'fail', 'unknown'}:
            raise ValueError('판정 오류')
        out[task] = verdict
    return out

def compare(expected, old_rows, new_rows, old_meta, new_meta):
    if (not expected or any(type(t) is not str or not t or t.strip() != t for t in expected)
            or len(set(expected)) != len(expected)):
        raise ValueError('원래 작업 목록 오류')
    for meta in (old_meta, new_meta):
        if type(meta) is not dict or any(
            type(meta.get(k)) is not str or not meta[k].strip() for k in REQUIRED):
            raise ValueError('비교 조건 누락 또는 형식 오류')
    old, new = index(old_rows), index(new_rows)
    target = set(expected)
    issues = {
        'old_missing': sorted(target - old.keys()),
        'old_extra': sorted(old.keys() - target),
        'new_missing': sorted(target - new.keys()),
        'new_extra': sorted(new.keys() - target),
        'condition_mismatch': [k for k in REQUIRED if old_meta[k] != new_meta[k]],
        'unknown_old': sorted(t for t, v in old.items() if v == 'unknown'),
        'unknown_new': sorted(t for t, v in new.items() if v == 'unknown'),
    }
    ready = not any(issues.values())
    return {
        'ready': ready, 'issues': issues,
        'common_ids': sorted(old.keys() & new.keys()),
        'full_comparison': {
            'tasks': len(expected),
            'old_pass': sum(v == 'pass' for v in old.values()),
            'new_pass': sum(v == 'pass' for v in new.values()),
            'improved_ids': [t for t in expected if old[t] == 'fail' and new[t] == 'pass'],
            'regressed_ids': [t for t in expected if old[t] == 'pass' and new[t] == 'fail'],
        } if ready else None,
    }

if __name__ == '__main__':
    print(compare(EXPECTED, OLD, NEW, META, META))
    repaired = [('A', 'pass'), ('B', 'pass'), ('C', 'pass')]
    print(compare(EXPECTED, OLD, repaired, META, META))

첫 출력과 두 번째 출력은 다른 입력을 설명합니다

첫 출력은 ready가 False, full_comparison이 None입니다. new_missing에는 C, new_extra에는 D가 표시됩니다. common_ids는 A·B이지만 이 값만으로 전체 성공률을 계산하지 않습니다. 필요한 조치는 C의 기록을 찾고 D가 들어온 이유를 확인하는 것입니다. D의 이름만 C로 바꾸거나 누락 판정을 만들어 넣어서는 안 됩니다.

두 번째 출력은 설명을 위한 별도의 가상 입력입니다. 실제로 C의 통과 기록을 확인했다고 가정한 repaired 목록을 사용합니다. ready가 True이고 전체 세 작업에서 이전 통과 2건, 새 통과 3건, fail에서 pass로 바뀐 작업은 B로 표시됩니다. 실제 분석에서는 확인한 원본 기록으로만 목록을 보완해야 합니다.

ready는 입력 대조를 통과했다는 뜻이지 “새 모델이 우수하다”는 결론이 아닙니다. 세 작업은 합성 예제이며 일반적인 성공률 추정치가 아닙니다. 반복 실행의 의존성, 표본 수, 평가 환경과 통계적 불확실성은 별도 문제입니다.

세 가지 변형으로 보류 기준을 확인합니다

새 결과에 같은 A 행을 한 번 더 넣으면 중복 작업 ID 오류가 납니다. 사전으로 곧바로 변환해 중복을 덮어쓰지 않고, 넣기 전에 검사하기 때문입니다. 새 결과의 B를 unknown으로 바꾸면 형식 오류는 아니지만 비교는 보류됩니다. 판정을 아직 확정하지 못한 상태와 실패 판정은 구분합니다.

새 실행의 비교 조건에서 rubric_version만 바꾸면 condition_mismatch에 해당 이름이 나타납니다. 이 예제는 조건이 다른 두 결과를 자동으로 섞지 않습니다. 기준 변경 자체를 평가하려는 실험이라면 별도의 질문과 설계를 세워야 합니다. 행 순서만 바꾼 경우는 작업 ID로 대조하므로 결과가 같습니다.

이 코드가 확인하지 않는 것

ID가 같아도 질문 내용이나 정답표가 바뀌었다면 같은 작업이 아닐 수 있습니다. 같은 dataset_version 문자열을 반복 사용하면 대조를 통과하므로 자료 버전의 의미를 지키는 운영이 필요합니다. 한 작업에 여러 최종 판정이 정당하게 존재하는 설계라면 실행 ID·판정 개정 등으로 단위를 다시 정하세요. 원본 행을 임의로 버려 이 코드에 맞추지 않습니다.

이 실습은 한 번의 같은 작업 집합 비교를 위한 보수적인 입력 점검입니다. 관측 마감이 달라도 비교할 수 있는 연구 설계나 서로 다른 집단의 비교가 모두 잘못됐다는 뜻은 아닙니다. 다만 그런 질문을 이 코드의 ready로 승인하지 않습니다. 비교 범위와 허용 조건을 바꾸면 보고서에도 그 차이를 드러내야 합니다.

보고서에 남길 비교 범위

원래 작업 목록의 출처와 버전:
이전 / 새 실행 ID와 모델·프롬프트 버전:
판정 기준 / 자료 버전 / 관측 마감:
양쪽 누락·추가 작업 / 중복·unknown:
전체 비교 가능 여부와 보류 이유:
비교한 작업 수 / 개선·악화 작업:
표본·환경·판정 근거의 남은 한계:

검증한 범위와 출처

2026년 11월 7일 Python 3.12.14에서 본문 코드와 합성 자료를 실행했습니다. 누락·추가·중복 작업, 조건 불일치, unknown, 행 순서 변경과 잘못된 입력을 포함한 19개 점검을 통과했습니다. 실제 모델·고객 자료·통계 검정·성능 개선의 인과성은 시험하지 않았습니다.

공식 문서 확인일은 2026년 11월 7일입니다. Python 3.12 집합 문서의 차집합·교집합과 dict 문서의 키 조회를 참고했습니다. 원래 작업 목록과 비교 조건, unknown 보류 규칙은 이 글의 학습용 설계입니다. 숫자가 좋아졌는지 보기 전에, 그 숫자가 같은 대상을 세고 있는지 확인하는 데 사용하세요.


RUDA DIRECTOR · 루다(이루다)

작성자·공개 작업·편집 기준 · 문의·정정 요청

다른 글 보기 · 주제 탐색