리더보드에서 선택한 코딩 에이전트가 우리 코드베이스에서는 실패한 이유
요약
벤치마크 점수만 믿고 선택한 코딩 에이전트가 실제 코드베이스에서 실패한 원인을 분석합니다. 벤치마크의 한계와 실제 개발 환경 간의 격차를 설명하며, 기업 내부의 Git 히스토리를 활용한 자체 테스트의 중요성을 강조합니다.
핵심 포인트
- 벤치마크 점수는 모델의 기억력을 측정할 위험이 있음
- 벤치마크 환경과 실제 개발 환경(컨텍스트) 사이의 큰 격차 존재
- 코드베이스 고유의 관습과 스타일을 에이전트가 인지하지 못함
- 가장 완벽한 테스트 데이터는 실제 머지된 Git 히스토리임
지난해 우리 팀은 코딩 에이전트 (coding agent)를 선정해야 했고, 저는 평가를 맡겠다고 자원했습니다. 저는 잘 해낼 수 있을 것 같았습니다. 공개 벤치마크 (benchmark) 점수를 찾아보고, 후보들을 나열한 뒤, 가장 높은 점수를 받은 것을 선택하여 모두에게 승자가 결정되었다고 말했습니다.
그러고 나서 실제로 그것을 우리 저장소 (repo)에 적용해 보았습니다.
극적으로 망가지는 수준은 아니었습니다. 그저 우리의 시간을 잡아먹는 방식으로 미세하게 계속 틀렸을 뿐입니다. 리뷰어들이 승인하지 않을 차이점 (diffs)을 작성했습니다. 함수 이름을 변경하더니 한 번도 열어본 적 없는 파일 세 개를 망가뜨렸습니다. 그것이 실행한 테스트는 통과했지만, 저장소는 여전히 망가진 상태였습니다. 저는 우리 상황에 대해서는 거의 아무것도 말해주지 못하는 숫자에 기반하여 자신 있게 도구를 추천했던 것입니다.
그 상황은 너무나 당혹스러워서 저는 왜 그런 일이 발생했는지 알아내기로 했습니다. 몇 주간의 독서와 몇 번의 잘못된 판단 끝에 드디어 제대로 작동하는 무언가를 찾아냈습니다. 이것은 그 과정에 대해 솔직하게 작성한 글이며, 여러분이 내놓았던 추천을 철회해야 하는 회의 시간을 아껴줄 수 있기를 바랍니다.
벤치마크 점수가 왜 나를 속였는가
점수는 가짜가 아니었습니다. 단지 다른 사람의 코드를 측정하고 있었을 뿐입니다. 제대로 살펴보니, 네 가지 격차가 이 모든 상황을 설명해 주었습니다:
- 에이전트가 이미 정답을 보았을 가능성이 있습니다. 이러한 공개 벤치마크 (benchmarks)의 문제들은 오래되었습니다. 모델들이 채점에 사용된 실제 수정 사항들을 학습 데이터로 포함했을 가능성이 매우 높습니다. 따라서 점수는 문제 해결 능력이 아닌, 부분적으로 기억력을 측정하는 셈입니다.
- 설정 환경이 실제 업무와 전혀 다릅니다. 벤치마크는 에이전트에게 깨끗한 저장소 (repo), 하나의 명확한 이슈 (issue), 그리고 테스트를 실행할 하나의 명령어를 제공합니다. 반면 제 엔지니어들은 반쯤 열린 에디터, 지저분한 브랜치 (branch), 슬랙 (Slack) 스레드, 그리고 리뷰어의 코멘트를 던져줍니다. 완전히 다른 종류의 업무입니다.
- 우리 코드베이스에는 우리만의 관습이 있습니다. 우리의 내부 라이브러리, 래퍼 (wrappers), 테스트 스타일, 그리고 우리가 금지하는 임포트 (imports) 규칙 등이 있습니다. 어떤 벤치마크도 이를 알지 못하므로, 에이전트는 교과서적으로는 완벽하지만 우리 리뷰어들이 보자마자 거절할 코드를 작성할 수 있습니다.
- 통과 기준이 훨씬 낮습니다. 벤치마크는 깨진 테스트가 이제 통과하기만 하면 패치 (patch)를 통과시킵니다. 하지만 제 팀은 패치가 테스트를 통과할 뿐만 아니라, 관련 없는 테스트를 망가뜨리지 않고, 파일 전체의 포맷을 바꾸지 않으며, 불필요한 임포트를 추가하지 않고, 실제로 리뷰할 수 있을 만큼 충분히 작아야 통과시킵니다. 벤치마크는 첫 번째 조건만 채점할 뿐, 나머지 조건은 전혀 고려하지 않습니다.
그래서 저는 리더보드를 결정의 근거로 삼는 대신, 일종의 '냄새 테스트 (sniff test)'로 취급하기 시작했습니다. 후보군을 좁히는 용도로는 괜찮지만, 실제 선택을 내리기에는 무용지물입니다.
테스트 세트는 처음부터 우리 git 히스토리에 있었습니다
이 부분은 제가 첫날부터 생각했더라면 좋았을 내용입니다. 코딩 에이전트를 위한 가장 완벽한 테스트 데이터는 우리가 이미 머지 (merged)했던 작업물입니다.
우리가 마지막으로 머지한 50개의 풀 리퀘스트 (pull requests)에는 벤치마크가 흉내 낼 수 없는 모든 것들이 포함되어 있었습니다: 실제 의도, 리뷰어의 코멘트, 통과된 테스트 스위트 (test suite), 그리고 검증된 최종 디프 (diff)까지 말이죠. 그래서 저는 이 작업들을 재현해 보았습니다:
- 마지막 50개의 머지 건을 가져오되, 버전 업데이트와 생성된 파일들은 제외하고, 우리가 실제로 배포하는 것과 유사한 혼합 상태를 유지합니다.
- 각 건에 대해, 작업이 시작된 커밋 (commit)과 이슈 (issue) 텍스트를 기록합니다.
- 저장소를 해당 시작 시점으로 되돌린 후, 에이전트에게 이슈를 전달하고 샌드박스 (sandbox) 환경에서 작업하게 합니다.
- 에이전트가 생성한 결과물을 우리가 실제로 머지했던 내용과 비교합니다.
저는 첫 번째 시도에서 이 부분을 실수했습니다. 그러니 제 경험을 통해 배우시기 바랍니다. 저는 텍스트가 얼마나 일치하는지를 기준으로 점수를 매겼습니다. 이는 불공정할 뿐만 아니라 쓸모도 없습니다. 인간 리뷰어라면 동일하게 동작하지만 표현 방식이 다른 솔루션이라도 기꺼이 머지(merge)할 것입니다. 질문은 "이것이 우리의 diff(차이점)와 똑같이 보이는가"가 아니라, "이것이 우리의 diff가 통과했던 것과 동일한 리뷰를 통과할 수 있는가"여야 합니다.
또한 저는 매주 새로 머지된 PR(Pull Request)을 교체하고 가장 오래된 것을 제거합니다. 그렇지 않으면 데이터 세트가 우리가 실제로 배포하는 것과 점차 닮지 않게 되기 때문입니다.
제가 최종적으로 점수를 매기게 된 다섯 가지 요소
충분한 리플레이(replay)를 실행한 후, 저는 추측을 멈추고 다섯 가지 요소로 결정했습니다. 각 요소는 벤치마크(benchmark)가 절대 포착하지 못하는 무언가를 잡아냅니다.
우리 리뷰를 통과할 수 있는 diff인가? 텍스트가 아닌 동작(behavior)을 봅니다. 아무도 건드려 달라고 요청하지 않은 파일의 형식을 재조정(reformatting)하거나, 머지된 버전은 통과했던 테스트를 깨뜨리거나, 동일한 결과를 내기 위해 diff의 두 배에 달하는 양을 생성하는 경우 점수를 깎습니다.
도구를 합리적인 순서로 사용했는가? 코딩 에이전트(coding agent)는 텍스트 생성기가 아니라 루프(loop)입니다. 읽고, 편집하고, 테스트를 실행하고, 반응하고, 커밋(commit)합니다. 저는 네 가지를 확인합니다: 파일을 편집하기 전에 읽었는지, 편집이 의도한 위치에 반영되었는지, 편집 후에 실제로 테스트를 실행했는지 아니면 그냥 맹목적으로 커밋했는지, 그리고 변경한 내용만 커밋했는지입니다. 열어보지도 않은 파일을 편집하는 것은 그저 자신감 있게 추측하는 것에 불과합니다.
변경 사항이 영향을 미친 모든 것을 업데이트했는가? 이것이 저희를 큰 실패로 몰아넣었던 요소입니다. 에이전트가 한 파일에서 헬퍼(helper) 함수의 이름을 변경하고 다른 파일 곳곳에 깨진 호출 지점(call sites)을 남겨두는 경우가 있습니다. 에이전트가 건드린 파일 내의 테스트는 통과하지만, 리포지토리(repo)는 망가집니다. 그래서 이제 저는 모든 변경된 파일로부터 임포트(import)를 따라 외부로 나가며, 하위 파일들이 여전히 일치하는지 확인합니다.
계획이 작업 과정에서 유지되었는가? 현대의 에이전트(Agents)들은 무언가를 건드리기 전에 계획(plan)을 작성하며, 그 계획은 평가할 가치가 있습니다. 두 가지 질문이 있습니다. 계획이 실제로 요청된 내용과 일치했는가, 그리고 최종 디프(diff)가 여전히 계획과 일치하는가입니다. 에이전트들은 작업 중간에 경로를 이탈하며, 테스트가 계속 통과되는 동안 원래의 요청을 조용히 위반하곤 합니다. 이것은 리뷰어들이 결국 어떤 에이전트를 신뢰하게 되는지를 보여주는 가장 강력한 예측 지표로 나타났는데, 이는 제가 전혀 예상하지 못했던 결과였습니다.
잘못된 아이디어를 언제 포기해야 하는지 아는가? 이것은 숨겨진 핵심 요소(sleeper)였으며, 현재 제가 가장 중요하게 생각하는 부분입니다. 수정 후 테스트가 실패할 때, 절제된(disciplined) 에이전트는 되돌리기(revert)를 수행하고 다른 접근 방식을 시도합니다. 반면 절제되지 않은 에이전트는 테스트 스위트(test suite)가 완전히 망가질 때까지 실패 위에 코드를 계속 쌓아 올립니다. 이는 트레이스(trace)에서 명확하게 확인할 수 있습니다. 실패한 테스트 실행당 얼마나 많은 수정(edits)을 수행하는지, 그리고 실제로 되돌리기를 수행하는지 말입니다. 되돌리기 없이 실패당 평균 여러 번의 수정을 반복하는 에이전트는 회귀(regressions)를 발생시킬 것입니다. 확실합니다.
앞의 세 가지는 정확성(correctness)을 포착합니다. 네 번째는 의도(intent)를 포착합니다. 다섯 번째는 감독 없이 신뢰할 수 있는지 여부를 포착합니다.
가슴 철렁했던 경험 이후 추가한 한 가지 점검
코딩 에이전트들은 제가 예상했던 것보다 더 자주 가짜 자격 증명(credentials)을 만들어내거나 위험한 쉘(shell) 패턴을 그대로 따라 합니다. 그래서 이제 생성된 모든 디프(diff)는 가짜 비밀 정보(secrets), eval이나 쉘 보간(shell interpolation) 같은 위험한 구조, 그리고 우리 저장소(repo)에서 금지한 패턴이 있는지 스캔합니다. 이는 밀리초 단위의 시간이 걸리지만 이미 여러 사례를 잡아냈습니다. 저렴한 보험인 셈입니다.
실제로 무엇이 변했는가
우리의 리플레이(replay)에서 승리한 에이전트는 리더보드 상단에 있던 에이전트가 아니었습니다. 계획을 정직하게 유지하고, 잘못된 수정에서 언제 물러나야 하는지를 아는 에이전트였습니다. 이 두 가지 특성은 공개 점수에는 나타나지 않지만, 우리 리뷰어들이 정확히 중요하게 여겼던 요소들이었습니다.
제가 내린 결론은 이렇습니다. 벤더(vendor)는 모델과 프롬프트(prompt)를 소유합니다. 나는 평가(evaluation)와 결정(decision)을 소유합니다. 그리고 나의 평가만이 우리 코드가 실제로 어떻게 생겼는지 알고 있는 유일한 기준입니다.
만약 여러분이 자신의 저장소(repo)에서 이와 유사한 작업을 실행해 보셨다면, 어떤 차원(dimension)이 여러분을 당혹스럽게 만들었는지 꼭 듣고 싶습니다. 저의 경우에는 롤백(rollback)이었습니다. 에이전트가 잘못된 아이디어를 포기하는 방법을 알고 있는지 측정해야 한다는 생각은 전혀 하지 못했는데, 결과적으로 그것이 다른 거의 모든 것보다 더 중요하다는 사실이 밝혀졌습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기