우리의 AI 평가 하네스(Evaluation Harness)를 망가뜨린 세 가지 조용한 버그
요약
AI 모델의 성능을 평가하는 하네스(Harness) 구축 과정에서 발생한 세 가지 주요 버그와 해결 방안을 공유합니다. JSON 직렬화 문제와 다중 언어 지원 미비 등 테스트 도구 설계 시 주의해야 할 실무적인 오류를 다룹니다.
핵심 포인트
- JSON 직렬화 시 undefined 키가 삭제되는 특성을 주의해야 함
- 모든 반환 경로에서 명시적으로 null을 사용하여 데이터 일관성 유지
- 다양한 프로그래밍 언어(Python, JS 등)의 구문을 지원하는 패처 설계 필요
- 평가 도구가 에러 없이 소리 없이 실패(silent failure)하지 않도록 설계
지난주에 진행한 잘린 JSON(truncated JSON) 버그에 대한 평가(eval)는 우리에게 불편한 사실을 보여주었습니다. DebugAI는 버그를 정확하게 진단하고도, 92%의 주장된 신뢰도(confidence)를 가진 채 100% 실패하는 수정안을 제공할 수 있다는 점입니다.
데이터 포인트 하나는 패턴이 아닙니다. 그래서 우리는 엔진을 수정할 때마다 실제 코퍼스(corpus) 전체에 걸쳐 해당 체크를 자동으로 실행할 수 있는 하네스(harness)를 구축했습니다.
우리가 신뢰할 수 있는 수치를 하나라도 만들어내기 전까지, 하네스 자체에서 세 가지 버그가 발생했습니다. 이 버그들은 실패 모드(failure modes)가 매우 일반적이어서 테스트 도구를 만드는 사람이라면 누구나 겪을 수 있기에 공유하고자 합니다.
설정 (The Setup)
하네스는 자체 MCP 서버(npx @debugai/mcp)를 생성하고, debug_error 도구를 통해 실제 버그를 전송하며, 응답을 두 가지 방식으로 채점합니다. 첫째, 근본 원인(root cause)이 실제로 무엇이 고장 났는지를 언급하는지, 둘째, 반환된 수정안(fix)을 소스 코드의 깨끗한 복사본에 적용하고 실제 테스트를 실행했을 때 실제로 통과하는지 여부입니다. 신뢰도(Confidence)는 통과율(pass rate)에 따라 버킷(bucketed)으로 분류됩니다. 육안 확인(eyeballing)은 없습니다.
코퍼스(Corpus)는 11개의 버그로 구성됩니다. 8개는 일반적인 실패 클래스 전반에 걸친 고정 요소(fixtures)입니다. 3개는 재구성된 것이 아니라 우리의 커밋 히스토리에서 직접 가져온 실제 DebugAI 운영 사고 사례입니다.
존재하지 않았던 키 (The Key That Wasn't There)
채점기(grader)의 출력은 한 줄에 하나의 JSON 객체가 담긴 JSONL 파일에 기록되어, 터미널 스크롤이 지나간 후에도 결과가 유지됩니다. 첫 번째 실제 실행에서, 우리는 실패한 수정안의 fixes[i].reason을 읽으려 했으나 KeyError를 만났습니다. 키가 그곳에 없었던 것입니다.
JSON.stringify는 값이 undefined인 객체 키를 모두 삭제합니다. 우리의 채점 함수는 한 가지 코드 경로(아예 적용할 수 없었던 수정안)에서는 reason을 설정했지만, 다른 경로(적용은 잘 되었으나 테스트에는 실패한 수정안)에서는 이를 설정하지 않았습니다. 소스 코드상으로는 두 경로 모두 완전해 보였습니다. 하지만 직렬화(serialization) 과정에서 오직 한 경로만이 살아남았습니다.
해결책: 이제 모든 반환 경로(return path)에서 모든 키를 명시적으로 설정하며, 설정하지 않는 대신 null을 사용합니다. JSON.stringify는 null은 유지하지만, undefined만 삭제하기 때문입니다.
한 가지 언어만 구사하는 패처 (The Patcher That Only Spoke One Language)
코퍼스(Corpus)의 절반은 자체 Python 엔진 코드에서 포팅된 Python입니다. 나머지 절반은 JavaScript입니다. 하네스(Harness)는 모델이 반환하는 결과에 따라 두 가지 방식으로 수정 사항을 적용합니다. 정확한 라인 범위(line range)를 교체하거나, 라인 번호가 주어지지 않은 경우 해당 이름의 함수를 찾아 전체를 교체하는 방식입니다.
함수 탐색기(Function-finder)는 JavaScript 구문인 function foo, const foo = (...) =>만 인식했습니다. Python의 def나 async def에 대한 처리는 없었습니다. 라인 번호 없이 전달된 모든 Python 수정 사항은 첫 실행부터 소리 없이 실행 불가능한 상태가 되었습니다. 에러가 발생하지도 않았습니다. 그저
만약 당신의 평가 도구(eval tooling)가 소리 없이 실패할 수 있다면, 당신이 전혀 예상하지 못한 순간에 반드시 그렇게 될 것입니다. 이미 알려진 문서화된 정답을 사용하여 하나의 사례를 재현하는 셀프 테스트(self-test) 기능을 구축하세요. 그리고 새로운 데이터에 도구를 신뢰하기 전에, 하네스(harness)가 해당 정답을 제대로 재현하는지 확인하십시오. 저희의 도구는 그렇게 작동합니다. 애초에 두 번째 버그를 잡아낼 수 있었던 것도 바로 이 방식 덕분이었습니다.
저희는 아직 보정 수치(calibration numbers)를 공개하지 않을 예정입니다. 지금까지 진행된 세 번의 실행은 대부분 캐시 히트(cache hits)였으며, 새로운 시도(fresh trials)보다는 동일한 기저 모델 호출을 재현하는 방식이었기에, 이를 공개적인 주장을 뒷받침할 만큼 강력한 근거로 보기 어렵습니다. 다음 단계는 새로운 캐시 윈도우(cache window)를 대상으로 깨끗하게 실행하는 것입니다. 그 결과가 나오면 수치를 포함하여 별도의 포스트로 게시하겠습니다.
FAQ
Q: 실제 평가 결과 대신 왜 본인들의 테스트 도구에 있는 버그에 관한 포스트를 게시하나요?
A: 도구의 버그가 더 유용한 교훈이 되기 때문이며, 평가 결과는 아직 공개적으로 내세울 준비가 되지 않았기 때문입니다. 더 큰 수치가 준비될 때까지 기다리기보다는, 지금 이 포스트의 솔직한 버전을 전달하는 쪽을 택하겠습니다.
Q: 평가 하네스(eval harness)는 오픈 소스인가요?
A: 아직은 아닙니다. 이는 메인 제품과는 별개로 구축된 독립적인 리포지토리(repo)이며, 알려진 코퍼스(corpus)를 대상으로 debug_error를 채점하기 위해 특별히 제작되었습니다. 관심이 있다면 공개를 검토하겠습니다.
DebugAI는 단순히 스택 트레이스(stack trace)뿐만 아니라 코드베이스(codebase) 전체를 읽고, 클릭 한 번으로 적용할 수 있는 수정 사항을 제공합니다. 저희는 여러분께 저희의 수정 사항을 신뢰해 달라고 요청하는 것과 동일한 방식으로, 저희 자신의 수정 사항을 채점합니다. 바로 직접 실행해 봄으로써 말입니다.
원문은 debugai.io에 게시되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기