나를 3단계 디버깅 토끼굴로 빠뜨린 평가 기준 한 줄
요약
멀티 에이전트 시스템인 RedCouncil 개발 과정에서 단일 에이전트 베이스라인 대비 성능을 측정하기 위한 평가(eval) 체계를 구축하며 겪은 시행착오를 다룹니다. 지표 설계 오류와 데이터 해석의 함정을 통해 올바른 에이전트 평가 방법론의 중요성을 강조합니다.
핵심 포인트
- 멀티 에이전트 시스템은 단일 모델 호출(Single model call) 대비 효율성을 반드시 증명해야 함
- 평가 구축 시 기존 자원을 재사용하여 결정론적 점수 함수를 만드는 것이 빠름
- 지표 설계 시 구조적 오류로 인해 발생하는 인공물(artifact)을 경계해야 함
- 에이전트 간 충돌 노출과 고심각도 발견 사항 포착이 핵심 지표임
해커톤 마감 이틀 전, 나는 내가 제출하려는 트랙의 심사 기준을 다시 읽다가 처음에는 분명히 놓쳤던 문장 하나를 발견했다:
"...단일 에이전트 베이스라인 (single-agent baselines) 대비 측정 가능한 효율성 이득."
내 프로젝트인 RedCouncil은 적대적 멀티 에이전트 시스템 (adversarial multi-agent system)이다. 다섯 명의 특화된 AI 에이전트 (Growth, Risk, Legal, TechDebt, Customer)가 비즈니스 결정에 대해 토론하고, 서로를 교차 검증하며, Synthesizer가 심각도 점수가 매겨진 보고서를 생성한다. 68개의 테스트 통과, 인증 (auth), 저장소 (storage), 배포 (deployment)까지 모두 완료된 상태였다. 하지만 나는 이것이 실제로 단일 모델 호출 (single model call)보다 나은 성능을 보이는지 단 한 번도 측정해 본 적이 없었다.
나의 첫 번째 본능은 포기하는 것이었다. 하지만 두 번째, 더 나은 본능은 다음과 같이 질문하는 것이었다. 이것이 괜찮거나 혹은 가망이 없다고 가정하는 대신, 어떻게 하면 실제로 이에 대한 실질적인 증거를 가장 빠르게 얻을 수 있을까?
평가 (eval) 구축하기, 빠르게
이 과정의 가장 빠르고 정직한 버전은 새로운 서브시스템 (subsystem)을 만드는 것이 아니라, 이미 가지고 있는 모든 것을 재사용하는 것이다. 동일한 시나리오, 동일한 모델, 그리고 다섯 명의 에이전트가 결합하여 수행하는 일을 단 한 번의 Qwen 호출로 수행하도록 요청하는 하나의 새로운 프롬프트 (prompt), 그리고 LLM 판사 (LLM judge) 없이 단순히 개수와 집합 수학 (set math)만을 사용하는 순수하게 결정론적인 점수 함수 (deterministic scoring function)를 사용하는 것이다. 가장 중요한 두 가지 수치는 다음과 같았다:
- 에이전트 간 충돌 (Cross-agent conflicts) 노출. 단일 모델 호출은 전문가적 관점 사이의 의견 불일치를 나타낼 수 있는 메커니즘이 없다. 베이스라인의 경우 구조적으로 이 수치가 0이 되므로, 이는 구조적으로 보장된 공짜 데이터 포인트가 된다.
- 고심각도 발견 사항 (High-severity findings) 포착. 동일한 1~10 척도와 동일한 모델을 사용하여 정면으로 비교함으로써, 모델 크기가 아닌 아키텍처 (architecture)를 분리하여 비교할 수 있다.
나는 실행했다. 결과 수치가 나왔다. 결과는 좋아 보였다. 나는 슬라이드 덱 (slide deck)을 만들기 시작했다.
그러다 요약본 대신 실제 결과 파일을 읽게 되었고, 첫 번째 문제를 발견했다.
버그 1: 설계상 비어있던 지표
내가 구축했던 필드 중 하나인 domains_missed_by_baseline은 모든 시나리오에서 비어 있었다. 나는 처음에 이것을 'RedCouncil이 기준선(baseline)이 포착하는 것을 결코 놓치지 않는다'는 의미로 해석했다. 하지만 다시 살펴보니, 이 필드는 내가 정의한 방식 때문에 항상 비어 있을 수밖에 없었다. 이 필드는 RedCouncil의 출력에는 존재하지만 기준선에는 없는 도메인만 확인했기 때문이다. 기준선 프롬프트가 매번 도메인당 하나의 발견을 강제했기 때문에, 실제 RedCouncil의 수행 능력과 관계없이 그 집합 차이는 구조적으로 비어 있을 수밖에 없었다. 이것은 발견이 아니라 내가 지표를 정의한 방식에서 비롯된 인공물(artifact)이었다.
더 심각하게는, 실제로 중요한 _역방향_을 확인했을 때, RedCouncil이 5개 시나리오 중 3개에서 전체 도메인인 Growth를 놓치고 있다는 것을 발견했다. 내가 만든 다중 에이전트 토론 시스템(multi-agent debate system)이 이기려고 했던 단일하고 어리석은 기준선보다 더 적은 범위를 커버하고 있었던 것이다.
버그 2: 너무 일찍 포기한 재시도(retry)
실제 추적 데이터(speculation 아님)로 근본 원인을 분석해 보니, 두 가지 부분의 실패가 있었다. 첫 번째는 커버리지 검사기(coverage checker)가 발견된 내용의 심각도 점수(severity threshold)가 특정 임계값을 초과할 때만 도메인을 '누락'으로 플래그를 지정한다는 것이었다. 따라서 합법적으로 낮은 심각도의 Growth 관련 발견은 감지되지 않고 지나갈 수 있었다. 그리고 재시도가 실제로 트리거되었을 때, 승인 로직(acceptance logic)은 이전 시도보다 엄격하게 개선된 경우에만 재시도를 유지했다. 만약 모델의 재시도 시도가 누락된 도메인을 복구하는 데 실패하면, 시스템은 포기하고 손상된 결과를 그대로 유지했는데, 이는 '이전보다 나을 것이 없다'와 '여전히 손상되었다'가 승인 검사에서 동일하게 보였기 때문이다.
수정: 심각도에 관계없이 모든 도메인이 확인되도록 임계값을 낮추고, 재시도 루프를 한 번이 아닌 여러 제한된 시도(bounded attempts)로 변경하며, 단일한 엄격한 비교 대신 수렴(convergence)을 제대로 추적하도록 했다.
비교를 다시 실행했다. Growth가 5개 모든 시나리오에서 나타났다. 나는 슬라이드를 업데이트했다. 이제 끝났다고 생각했다.
버그 3: 또다시 제로(zero)
그러고 나서 데모 영상을 녹화하기 위해 실제 라이브 제품 UI를 열었는데, "충돌 발생(conflicts surfaced)" 카운터가 0으로 표시되었다. 대화 기록(transcript)을 읽어보면 에이전트들이 명백히 영어로 서로의 핵심 주장에 반박하고 있는 시나리오였음에도 말이다.
이 문제에 대한 1차 디버깅(debugging) 결과, 실제 배관(plumbing) 버그를 발견했다. 코드의 한 부분은 conflicts라는 필드에 결과를 기록하고 있었고, 다른 부분은 conflict_count라는 필드에서 값을 읽어오고 있었다. 아무도 두 번째 키에 값을 작성한 적이 없었던 것이다. 전형적인 실수였다. 이를 수정하고, 재배포(redeployed)한 뒤 다시 테스트했다.
여전히 0이었다.
2차 디버깅은 더 깊게 들어갔고, 이 부분이 내가 실제로 무언가를 배운 지점이다. 충돌 탐지기(conflict detector)가 후보 간의 의견 불일치를 평가하기도 전에, 어휘 중첩(lexical-overlap) 확인을 통해 이를 차단(gating)하고 있었다. 나의 Growth 에이전트는 _절감액(savings), 비용(cost), ROI_의 관점에서 논쟁한다. 나의 Customer 에이전트는 _이탈(churn), 마찰(friction), 유지(retention)_의 관점에서 논쟁한다. 공유되는 어휘가 전혀 없었기에, 근본적인 의견 불일치가 완전히 실재함에도 불구하고 실제 입장 비교(stance-comparison) 로직이 실행되기도 전에 게이트(gate)가 해당 쌍을 걸러내 버린 것이다.
이것은 일단 발견하고 나면 명백하지만, 발견하기 전까지는 보이지 않는 종류의 버그다: 진정으로 서로 다른 임무를 가진 전문가들은 설계상 진정으로 다른 단어를 사용한다. 모두가 같은 주제에 대해 이야기하는 시스템을 위해 만들어진 유사성 게이트(similarity gate)는, 에이전트들이 서로 다르게 생각한다는 것이 전제인 시스템에는 정확히 잘못된 도구다.
해결책: 어휘 중첩으로부터 의견 불일치를 추론하는 것을 중단한다. 교차 검증(cross-examination) 중에 에이전트들이 서로를 명시적으로 반박할 때 이미 생성하고 있는 신호(signal)를 신뢰한다.
버그 4: 수정 사항 내부에 숨어있던 것
각 에이전트의 자체적인 반박 신호(rebuttal signal)를 신뢰하는 것은 거의 즉각적으로 새로운 실패 모드(failure mode)를 유발했다. 반박은 종종 _상호적(mutual)_이기 때문이다. 만약 Growth가 Risk를 반박하고 Risk가 Growth를 동일한 교환 과정에서 반박한다면, 그리고 각 반박이 개별적인 충돌 항목(conflict entry)이 된다면, 양방향으로 발생하는 모든 의견 불일치를 중복 계산하게 된다. 제품을 출시하기 전에 이 문제를 발견했으며, 간단한 해결책을 적용했다: 방향성이 있는 에지(directional edge)가 아니라, 순서가 지정되지 않은 에이전트 쌍(unordered agent pair)에 대해 중복을 제거(dedupe)하는 것이다.
결국 무엇이 변했는가
충돌 탐지(Conflict detection)는 고장 난 0에서, 여전히 0을 읽는 배관 수정(plumbing fix) 단계로, 다시 실제적이지만 부분적인 수정 단계로, 그리고 마침내 어휘(vocabulary)를 공유하지 않는 의견 불일치까지 포착할 수 있는 탐지기로 발전했다. 이 시점에서 실제 충돌 횟수는 어휘 제한 버전이 찾아낸 것보다 대략 두 배 정도 증가했다. 이러한 모든 도약은 다음 단계의 문제가 드러나기 직전까지는 마치 "수정이 성공했다"는 것처럼 보였다.
평가 하네스(eval harness)를 구축하는 누군가에게 실제로 해주고 싶은 말
- 항상 같은 값만 나오는 지표는 지표가 아니다 — 신뢰하기 전에 그것이 구조적으로 무엇을 보여줄 수 있는지 확인하라. 나의 지표는 특정 카테고리의 결과를 찾는 데 있어 정의상 항상 비어 있는 상태였다.
- 시스템의 전체 전제가 구성 요소들이 다르게 생각한다는 것이라면, 그들이 유사하게 설명할 것이라고 가정하는 탐지 로직을 만들지 마라. 그것은 예외적인 상황(edge case)이 아니라, 의도대로 작동하는 설계가 그 반대를 가정하는 탐지기와 충돌하는 상황이다.
- 평가(eval)를 수정하는 것은 제품을 수정하는 것만큼이나 많은 엔지니어링 작업이 될 수 있다 — 어쩌면 그 이상일 수도 있다. 고장 난 제품은 요란하게 실패하지만, 고장 난 평가는 확신에 찬 틀린 숫자로 조용히 실패하기 때문이다.
- 고정(pin down)하지 않은 모든 계층을 통해 비결정론(Non-determinism)은 증폭된다. 실행 간의 모델 분산(model variance)과 시스템 자체의 재시도 로직(retry logic) 계층 사이에서, "그 숫자"는 결코 단 하나의 숫자가 아니었다. 그것은 내가 한 번 샘플링하고 최종값이라고 부른 분포(distribution)였다. 세 번의 재실행 후에 우연히 발견하기보다는, 그것이 당신의 유스케이스(use case)에 충분한지 의도적으로 결정할 가치가 있다.
RedCouncil는 Qwen Cloud (Qwen-Max) 위에서 엔드 투 엔드 (end-to-end)로 실행됩니다. 즉, 5명의 토론 에이전트 (debate agents)와 합성기 (Synthesizer) 모두가 여기서 돌아갑니다. 만약 당신이 의도적으로 여러 모델 호출 (model calls)이 서로 의견이 일치하지 않도록 설계된 무언가를 구축하고 있다면, 제품 코드 (product code)뿐만 아니라 평가 코드 (evaluation code)를 위한 실시간 예산 (real time budget)도 확보해 두어야 합니다. 제 코드 역시 그만큼의 디버깅 (debugging)이 필요했습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기