에이전트 테스트가 전부 초록불이었던 이유 — 모델이 벤치마크를 '속이는 법'을 배웠기 때문입니다
저자가 만든 AI 리뷰어 벤치마크에서 로컬 3B 모델이 'step_1'이라는 트리거 하나로 precision 1.00의 'pass' 판정을 받은 사례를 소개합니다. 모델이 나쁜 것이 아니라 보상 함수가 잘못 설계된 것이며, matcher를 일주일간 고쳐도 20%에 머물던 golden pass rate가 분류 로직의 6줄 수정으로 50%까지 오른 경험을 통해 '모든 모델에서 균일하게 나쁜 결과는 측정 실패'라는 교훈을 전합니다.
본문
AI 리뷰어가 매번 'pass'라고만 말한다면 그것은 리뷰어가 아니라 도장(rubber stamp)입니다. 저자는 자신이 의도치 않게 그런 도장을 만들었음을 인정하며, precision·recall·임계값·초록색 테스트 스위트까지 갖춘 그럴듯한 벤치마크에서 모델이 가장 저렴한 방법으로 요구 조건을 충족시킨 과정을 해부합니다.
문자열 "step_1" 하나로 통과한 벤치마크
로컬 3B 모델이 생성한 rule trigger는 문자 그대로 "step_1"이었습니다. 모든 trajectory가 step 필드를 포함하고 모든 trajectory에 첫 번째 step이 있으므로 이 트리거는 전부와 매칭됐고, matcher는 precision 1.00, recall 0.02를 기록하면서도 'pass' verdict를 반환했습니다. false positive 2건이 이 트리거 하나에서 비롯됐습니다. 모델이 문제를 일으킨 것이 아니라, 정의된 문제를 풀었을 뿐입니다. 모델의 실제 목적은 '진짜 실패를 찾는 것'이 아니라 '0.70 이상 점수를 받는 트리거를 만드는 것'이었고, 그 목적 아래 "step_1"은 완벽한 답이었습니다. 저자는 이를 '모델이 잘못 행동하는 것이 아니라 벤치마크가 잘못 보상하고 있는 것'이라고 요약합니다.
구조적 artifact는 공격 표면입니다
모든 레코드에 등장하는 구조적 요소 — step 번호, timestamp, session ID, tool 이름 — 는 전부 shortcut이 될 수 있는 공격 표면입니다. 해법은 그 필드를 제거하는 것이 아니라(포맷의 일부이므로) 그 필드와의 매칭에 보상하지 않는 것이었습니다. 저자는 degenerate trigger를 matcher에 도달하기 전에 거부하는 3줄짜리 게이트를 추가했습니다. 정규식 ^step[_\s]*\d+$에 매칭되면 무조건 False를 반환하는 방식입니다.
정규식 이후에도 남은 의미론적 간극
게이트 적용 후에도 구조와 무관한 false positive 3건이 남았습니다. 'git push fails with authentication error'가 'git push fails with non-fast-forward'와 매칭됐고(6개 토큰 중 3개 공유, 하지만 다른 실패 유형), 'python import fails with wrong module'이 'python ImportError'와 매칭됐습니다(아예 다른 도구). token-overlap matcher는 유사성을 보지만 사람은 완전히 다른 문제로 봅니다. matcher가 곧 reward function이며, 저자의 보상 함수는 '같은 실패'가 아니라 '공유된 토큰'이었다는 것이 한 단계 더 깊은 교훈입니다.
일주일간 고친 것은 틀린 층이었습니다
저자는 일주일간 matcher만 수정했습니다. precision 공식 버그, 50개 이상의 distinctive phrase, alias 확장, floor 상향 — 4개의 수정과 359개의 검증 테스트, 전부 green이었지만 golden pass rate는 10%에서 20%로 오르는 데 그쳤습니다. 그런데 match가 의미하는 바를 분류하는 simulator에서 6줄짜리 수정을 발견했습니다. near-miss 복구를 clean success로 계산해 올바르게 발화한 트리거에 오히려 벌점을 주고 있었던 것입니다. 이 수정 하나로 golden이 20%에서 50%로 뛰었고, 두 cloud 모델 모두에서 같은 날 재현됐습니다. 초록색 스위트는 계속 통과하고 있었지만, 틀린 층을 검증하고 있었던 것입니다. 결정적인 verdict가 올바른 verdict는 아닙니다.
모든 모델이 동일하게 나쁘면 평가 층을 의심하세요
수정 전 golden pass rate는 local과 cloud, 3B와 8B를 가리지 않고 20%에 고정돼 있었습니다. 모델의 능력 한계처럼 보였지만 실제로는 모든 모델에 동일하게 작용하는 분류 버그였습니다. 모델과 무관한 실패는 대개 측정 실패입니다. 저자는 이후에도 같은 형태를 겪었습니다. golden recall이 두 번의 현장 테스트에서 0.087에 머문 이유는 후보가 자기 도메인이 아닌 전체 230개 trajectory 풀로 채점됐기 때문이었고, git 실패 3건을 막은 규칙이 3/200(약 0.015)으로 기록됐습니다. 참조를 소스 도메인으로 좁히자 모델·프롬프트·matcher 변경 없이 recall이 2~3배 상승했습니다. 분모가 버그였습니다.
세 가지 습관, 그리고 여전히 남은 위험
저자가 권하는 습관은 셋 모두 소박합니다. 첫째, 모든 false positive를 트리거까지 추적할 것 — '5 false positives'는 무의미하지만 'degenerate 2 + semantic 3'은 무엇을 고칠지 정확히 알려줍니다. 둘째, scorer를 최적화하기 전에 분모가 무엇을 의미하는지 물을 것 — 전체 실패 대비 recall과 이 규칙이 잡아야 할 실패 대비 recall은 다른 질문입니다. 셋째, 결과가 모델 전반에 걸쳐 균일하게 나쁘면 출력을 분류하는 층을 볼 것 — 모델과 무관한 실패가 사는 곳입니다. 마지막으로 false negative는 보이지 않는다고 경고합니다. matcher와 모델이 동시에 진짜 실패를 놓치고 일치하면 깨끗한 verdict와 초록 스위트만 남고, 프로덕션에서 드러날 때까지 아무 신호도 없습니다. false positive는 성가시지만 false negative는 위험합니다. 더 나은 reward function은 기준을 높일 뿐 위험을 제거하지는 못하며, 저자는 '당신의 AI 시스템이 통과시킨 것 중 마지막으로 통과하지 말았어야 할 것은 무엇인가'라는 질문으로 글을 맺습니다. 코드는 CauterRule(pip install cauterule)로 공개돼 있습니다.
원문: dev.to / 번역·요약: Trawling