AI가 작성한 코드를 다른 모델이 검토해야 하는 이유
자신의 결과물을 검토하는 모델은 그 모델의 사각지대를 공유합니다. diff를 다른 공급자의 독립적인 모델로 라우팅하고 결과를 교차 확인하세요.
하나의 AI 모델이 패치를 작성하고 동일한 모델이 이를 검토할 때, 그 검토는 중요한 버그에 대해서는 거의 가치가 없습니다. 코드를 생성한 모델은 그 코드의 근거가 되는 추론도 함께 생성했으므로, 검토 시에도 그 추론을 그대로 가져와 자신의 작업을 확인하게 됩니다. 만약 존재하지 않는 API를 환각(hallucination)으로 만들어냈다면, 그 호출을 올바른 것으로 읽을 것입니다. 만약 사양을 잘못 읽었다면, 동일하게 잘못 읽은 내용을 기준으로 코드를 평가할 것입니다. 이것은 마치 사람이 자신이 쓴 글을 교정하면서 열 번이나 읽었던 오타를 무심코 지나치는 것의 기계 버전입니다.
해결책은 더 나은 프롬프트가 아닙니다. 해결책은 코드를 작성하지 않은 모델, 가급적이면 다른 데이터로 훈련된 다른 제공업체의 모델로부터 두 번째 의견을 구하는 것입니다. 동일한 diff를 두세 개의 독립적인 모델에 보내고, 각 모델에 버그와 위험 요소를 물어본 다음, 고정된 규칙에 따라 그들의 판정을 종합하십시오. 그들의 맹점이 겹치지 않는 부분에서 교차 확인은 단일 검토자가 놓치는 것을 잡아냅니다. 이 게시물은 이를 실행하는 방법, 비용, 그리고 단일 검토만으로도 충분한 경우에 대해 다룹니다.
셀프 리뷰는 작성자의 맹점을 그대로 답습합니다
코드 리뷰는 리뷰어가 작성자가 보지 못한 것을 볼 수 있을 때만 유용합니다. 두 사람이 협력하는 것이 효과적인 이유는 서로 다른 정신 모델, 과거의 여러 버그로 인한 각기 다른 경험, 그리고 코드가 수행해야 할 기능에 대한 다른 가정을 가지고 있기 때문입니다. 리뷰어는 작성자가 전혀 상상하지 못했던 경우를 지적합니다.
자신의 결과물을 검토하는 모델은 그러한 거리감이 전혀 없습니다. 코드에 대한 모델의 "의견"은 애초에 코드를 생성했던 것과 동일한 가중치, 동일한 훈련 데이터, 그리고 종종 동일한 컨텍스트 창의 연장선에 있습니다. 함수를 작성한 직후 모델에게 "여기에 버그가 있나요?"라고 묻는 것은, 작성자에게 작성자 자신의 의견에 반대하라고 요청하는 것과 같습니다. 모델은 누락된 nil 체크나 명백한 오타 같은 표면적인 문제는 찾아낼 수 있지만, 잘못된 가정에서 비롯된 미묘한 논리 오류는 검토 과정에서도 그 가정을 그대로 물려받기 때문에 살아남는 경향이 있습니다.
경험적으로 실패는 몇 군데에 집중되는 경향이 있습니다. 환각으로 만들어낸 메서드나 필드는 모델이 여전히 해당 메서드가 존재한다고 믿기 때문에 셀프 리뷰를 통과합니다. 경계 조건에서의 하나 차이 오류(off-by-one)는 경계에 대한 모델의 생각이 두 번의 과정 모두에서 동일하게 잘못되었기 때문에 통과됩니다. 누락된 권한 확인과 같은 보안 격차는 모델이 두 번 모두 정상 경로(happy path)에만 집중했기 때문에 통과됩니다. 이러한 결함들은 바로 나중에 발견하면 수정 비용이 많이 들고, 셀프 리뷰가 가장 취약한 바로 그 유형의 결함들입니다.
다른 모델은 다른 지점에서 실패합니다
두 번째 모델이 도움이 되는 이유는 모델들이 하나의 맹점 집합을 공유하지 않기 때문입니다. 각 모델은 서로 다른 데이터 조합으로 학습되고, 다른 목표로 조정되었으며, 신중함과 확신 사이의 트레이드오프에서 각기 다른 지점에 도달합니다. 한 모델은 동시성 위험을 발견하는 데는 강하지만 SQL에는 약합니다. 또 다른 모델은 데이터 흐름은 잘 읽지만 경쟁 상태는 그냥 통과시킵니다. 그들의 오류는 동일하지 않으며, 그것이 바로 핵심입니다.
이는 앙상블이 단일 분류기를 이기는 것과 같은 아이디어입니다. 세 명의 검토자가 각각 실제 결함의 30%를 놓치더라도, 그들이 놓치는 30%가 서로 다르다면, 세 명 모두가 동일한 특정 버그를 놓칠 확률은 30%보다 훨씬 낮습니다. 독립적인 오류는 상쇄됩니다. 문제는, 그리고 이것은 실제적인 문제인데, 바로 '독립적'이라는 단어입니다. 동일한 계열의 두 모델이나 동일한 모델에 두 번 프롬프트를 입력하는 것은 독립적인 오류를 제공하지 않습니다. 그것들은 더 높은 확신을 가지고 동일한 오류를 두 번 제공할 뿐입니다. 이점을 얻으려면 진정한 소스의 다양성이 필요합니다. 즉, 다른 제공업체, 다른 모델 계열이어야 하며, 한 모델의 두 가지 온도가 아닙니다.
따라서 설계 목표는 "더 많은 검토"가 아닙니다. 그것은 "실수가 서로 상관없는 검토"입니다. 진정으로 다른 모델의 단일 검토는 작성자 모델을 다섯 번 다시 실행하는 것보다 더 가치가 있습니다.
워크플로, 단계별 설명
파이프라인은 작습니다. 하나의 모델이 작성하고, 여러 독립적인 모델이 판단하며, 규칙이 판단을 결합하고, 사람이 동률을 처리합니다.
| 단계 | 주체 | 출력 |
|---|---|---|
| 1. 구현 | 작성자 모델 | diff 또는 패치 |
| 2. 병렬 검토 | N개의 독립적인 모델(작성자 제외) | 모델별 평결: GO 또는 NO-GO, 그리고 발견 사항 |
| 3. 합의 규칙 적용 | 자동화 | 통과, 실패 또는 에스컬레이션 |
| 4. 불일치 해결 | 사람 | 분할된 평결에 대한 최종 결정 |
두 가지 속성으로 인해 이 작업이 가능합니다. 검토자는 독립적으로 실행되므로 서로에게 앵커링할 수 없습니다. 그리고 각 검토자는 동일한 중립적인 프롬프트를 받으므로 NO-GO는 모든 모델에서 동일한 의미를 갖습니다. 작성자 모델은 검토 풀에서 완전히 제외하십시오. 그 모델의 투표는 이미 가지고 있는 것이며 가장 신뢰할 수 없는 것입니다.
의사 코드(pseudocode)로 표현하면 다음과 같습니다.
def multi_model_review(diff, author_model, reviewers, rule):
verdicts = []
for model in reviewers: # reviewers excludes author_model
v = model.review(
diff=diff,
prompt="List concrete bugs, security risks, and correctness "
"issues in this change. Then answer GO or NO-GO.",
)
verdicts.append(v)
passed = rule(verdicts) # unanimous or majority, see below
if passed is UNDECIDED:
return escalate_to_human(diff, verdicts)
return passed, verdicts
review 호출은 두 가지를 반환합니다. 구체적인 발견 사항 목록과 단일 GO 또는 NO-GO입니다. 발견 사항은 검토가 실패할 때 사람이 읽는 것입니다. GO 또는 NO-GO는 규칙이 사용하는 것입니다.
중요도에 맞는 합의 규칙을 선택하세요
여러 판정을 하나의 결정으로 바꾸는 규칙은 엄격함을 조정하는 부분입니다. 두 가지 합리적인 기본값과 그 사이의 스펙트럼이 있습니다.
만장일치 GO는 엄격한 규칙입니다. 모든 리뷰어가 GO라고 해야만 변경 사항이 통과되며, 단 하나의 NO-GO가 이를 차단합니다. 이것은 버그에 대한 재현율을 극대화하여 어떤 모델이든 볼 수 있는 모든 것을 잡아내지만, 지나치게 신중한 리뷰어 한 명이 변경을 중단시키기 때문에 더 많은 거짓 양성(false alarm)이 발생한다는 대가가 따릅니다. 되돌리기 어려운 코드에 사용하세요.
과반수 GO는 관대한 규칙입니다. 대부분의 리뷰어가 GO라고 하면 변경 사항이 통과됩니다. 헛된 경고를 자주 하는 경향이 있는 모델의 단독 NO-GO는 병합을 막지 않지만, 셋 중 둘이 문제에 동의하면 병합을 막습니다. 이것은 약간의 버그 발견 능력을 포기하는 대신 마찰을 훨씬 줄여줍니다. 놓친 문제를 다음 커밋에서 저렴한 비용으로 수정할 수 있는 일반적인 변경 사항에 사용하세요.
def unanimous(verdicts):
if all(v.decision == "GO" for v in verdicts):
return PASS
if all(v.decision == "NO_GO" for v in verdicts):
return FAIL
return UNDECIDED # split -> human decides
def majority(verdicts):
gos = sum(v.decision == "GO" for v in verdicts)
if gos > len(verdicts) / 2:
return PASS
return FAIL
만장일치 규칙이 의견 분할 시 어떻게 작동하는지 주목하세요. 이 규칙은 조용히 한쪽 편을 들지 않습니다. 문제를 상위로 전달합니다. 유능하고 독립적인 리뷰어들 간의 의견 불일치는 변경 사항이 정말로 모호하다는 강력한 신호이며, 바로 이런 경우가 사람이 시간을 들여 살펴볼 가치가 있는 때입니다. 의견 분할을 "에이, 다수가 이기지"와 같이 취급하는 것은 이 모든 과정에서 얻는 가장 가치 있는 결과물을 버리는 것입니다.
적대적 프롬프트가 "이거 올바르게 보이나요?"를 이깁니다
누구에게 묻는지만큼 어떻게 묻는지도 중요합니다. 기본 검토 프롬프트인 "이거 올바르게 보이나요?"는 모델이 동의하도록 유도합니다. 동의하는 것이 쉬운 완성이기 때문입니다. 면밀한 검토가 아닌 확인을 받게 됩니다. 두 가지 조정으로 이에 대응할 수 있습니다.
첫 번째는 검토자가 코드에 반대하도록 만드는 것입니다. "이것을 검토해 줘" 대신 "당신의 임무는 이 변경 사항이 왜 잘못되었는지 찾는 것입니다. 버그가 있다는 가장 강력한 근거를 제시해 주세요."라고 요청하세요. 작업을 반박으로 구성하면 동의하려는 경향을 줄일 수 있습니다. 이제 모델은 변경 사항을 승인하는 것이 아니라 결함을 찾는 것에 대해 보상을 받으며, 중립적인 프롬프트에서는 그냥 넘어갔을 우려 사항을 표면으로 드러낼 것입니다. 결함에 대해 옳기를 바라는 것이 아니라, 열심히 찾아보라고 요청하는 것이며, 나중에 사람이 잘못된 경고를 걸러냅니다.
두 번째는 각 검토자에게 다른 렌즈를 부여하는 것입니다. 세 개의 일반적인 검토 대신 역할을 할당하세요. 한 검토자는 사양에 대한 정확성을 확인하고, 다른 검토자는 보안 및 입력 처리를 확인하며, 또 다른 검토자는 성능 및 리소스 사용을 확인합니다. 동일한 변경 사항, 세 가지 관점. 이는 의도적으로 검토 범위를 넓히고, 두 검토자가 동일한 명백한 문제에 모든 예산을 사용하는 동안 세 번째 영역이 검토되지 않는 것을 방지합니다.
Reviewer A: "Find correctness bugs. Where does this violate the stated behavior?"
Reviewer B: "Find security holes. Assume the input is hostile."
Reviewer C: "Find performance and resource problems under load."
반박과 역할 다양성은 중첩 효과를 냅니다. 작성자와 다른 모델을 사용하는 적대적 보안 검토자는 자동화만으로 얻을 수 있는 자기 확인에서 가장 먼 지점에 있습니다.
비용 및 생략 시점
이 중 어느 것도 무료가 아닙니다. 변경 사항 하나당 모델 호출을 두세 번 추가로 실행하므로 토큰 비용이 두세 배로 들고, 검토자들이 병렬로 실행되더라도 가장 느린 검토자가 속도를 결정하기 때문에 지연 시간이 추가됩니다. 작거나 되돌릴 수 있는 변경 사항의 경우, 이러한 오버헤드는 거의 아무런 이득이 없습니다. 로그 라인의 오타를 수정하는 데 심판 위원회는 필요하지 않습니다.
그 가치는 중요하고 되돌리기 어려운 변경 사항에서 드러납니다:
- 데이터를 제자리에서 다시 작성하는 데이터베이스 마이그레이션.
- 실수가 프로덕션 환경을 중단시킬 수 있는 배포 및 인프라 코드.
- 미묘한 버그가 보안이나 금전 문제로 이어지는 인증 또는 청구 경로.
- 일단 배포되면 깔끔하게 되돌릴 수 없는 단방향 변경.
이러한 경우, 세 번의 모델 호출 비용은 버그로 인한 비용에 비하면 사소하며, 엄격한 만장일치 규칙은 오탐(false alarm)을 감수할 가치가 있습니다. 몇 초 만에 끌 수 있는 기능 플래그 뒤에 있는 일상적인 변경의 경우, 단일 검토 또는 검토 없이가 올바른 결정입니다. 영향 반경(blast radius)에 맞춰 절차를 조정하세요.
계층화된 정책은 매번 고민할 필요 없이 이를 반영합니다:
| 변경 유형 | 검토자 | 규칙 |
|---|---|---|
| 사소하거나 되돌릴 수 있는 변경 | 1명 (또는 작성자 자체 점검) | 권고용 |
| 일반적인 기능 작업 | 독립적인 2명 | 과반수 GO |
| 마이그레이션, 배포, 인증, 청구 | 독립적인 3명 | 만장일치 GO |
실패 모드: 동의하는 모델들이 모두 틀릴 수 있다
이 기술의 솔직한 한계는 상호 연관된 사각지대입니다. 독립적인 오류는 오류가 실제로 독립적일 때만 상쇄됩니다. 만약 풀에 있는 모든 모델이 공개 인터넷의 동일한 인기 있지만 버그가 있는 예제에서 동일한 잘못된 관용구를 학습했다면, 모든 모델이 그것을 반복하고, 만장일치로 자신 있게 승인할 것입니다. 합의는 증거이지, 증명이 아닙니다. 세 모델로부터의 청신호는 세 모델이 문제를 보지 못했다는 것을 의미하며, 이는 문제가 없다는 것과 같지 않습니다.
이는 최신 종류의 버그와 가장 도메인 특화된 버그에서 가장 중요합니다. 아주 새로운 프레임워크의 미묘한 결함이나, 회사의 머릿속에만 있고 어떤 훈련 세트에도 없는 비즈니스 규칙은 모든 모델에게 한 번에 보이지 않습니다. 아무리 교차 확인을 해도 검토자 중 누구도 가지고 있지 않은 지식을 만들어낼 수는 없습니다. 다중 모델 검토는 빠져나가는 버그의 종류를 줄여주지만, 완전히 없애지는 못합니다.
따라서 중대한 사안이 걸려 있을 때는 인간을 계속 참여시키고, 단순히 GO 또는 NO-GO 집계가 아닌 실제 결과를 읽으십시오. 판정은 인간의 주의력을 더 확장시키는 필터이지, 그것을 대체하는 것이 아닙니다. AI가 작성한 코드를 검토할 때는 다른 모델을 사용하십시오. 사각지대가 하나인 것보다 둘인 편이 낫기 때문입니다. 단지 기계 간의 합의를 진실로 착각하지 마십시오.
관련 글
Session Handoffs가 여러 AI 코딩 세션에 걸쳐 컨텍스트를 유지하는 방법
AI 코딩 에이전트는 세션이 끝나면 모든 것을 잊어버립니다. 짧은 인계 메모는 수행한 작업, 미완성된 작업, 그리고 다음에 할 일을 전달합니다.
별칭을 더 만드는 대신, AI 에이전트를 셸 프런트엔드로 사용하세요
잊혀진 셸 스크립트의 무덤에는 대안이 있습니다. 작업을 평이한 말로 설명하면 AI가 여러분을 위해 grep, awk, jq를 조합해 줍니다.