멀티에이전트 eval에서 먼저 정할 세 가지: 게이트·환경·판정
sympo의 PRD→WBS 파이프라인에서 검증 게이트, 평가 환경, LLM-as-judge를 함께 설계하는 기준을 정리합니다.
sympo는 PRD를 받아 WBS로 나누는 다단계 에이전트입니다. 최종 WBS만 채점하면 실패가 보이지만, 어느 단계가 원인인지는 남지 않습니다. 그래서 eval을 추가할 때는 점수표보다 먼저 세 가지를 정해야 했습니다. 검증 게이트를 둘 위치, 실제 입력을 다룰 환경, 그리고 LLM-as-judge를 믿어도 되는 범위입니다.
적어 둠 eval은 끝에 붙이는 점수표가 아닙니다. sympo에서는 스텝 경계의 값싼 검증, 섀도우에서 찾은 새 실패, 사람 라벨로 보정한 judge를 하나의 순환으로 묶는 편이 낫습니다.
검증 게이트의 위치
단일 모델 호출이면 입력과 출력만 보면 됩니다. 멀티에이전트는 그사이에 플래닝, 분해, tool 호출, 병합이 줄줄이 들어갑니다. 최종 WBS가 엉성할 때, 플래너가 태스크를 잘못 쪼갠 건지 병합 단계가 멀쩡한 조각을 뭉갠 건지, 끝만 봐서는 구분이 안 됩니다. 검증을 파이프라인 끝에 몰면 실패를 만날 때마다 로그를 거슬러 스텝을 하나씩 다시 돌려야 합니다. 이 왕복이 개발 시간을 제일 많이 먹었습니다.
그래서 모든 출력을 judge에 넘기지 않습니다. 판단은 비싸고 느립니다. 스키마, 빈 필드, 태스크 수, 의존성 순환은 코드로 먼저 걸러야 합니다. 여기서 깨진 출력까지 모델에 채점시키면 비용도 늘고, 원인도 흐려집니다.
| 검증 | 어디에 | 비용 | 잡는 것 |
|---|---|---|---|
| 스키마·형식 | 모든 스텝 출력 | 거의 0 | 깨진 JSON, 빈 필드 |
| 제약 규칙 | 스텝 경계 | 낮음 | 태스크 과다, 의존성 순환 |
| LLM-as-judge | 게이트 (몇 군데) | 높음 | 품질, 논리 |
| 사람 | 최종 1곳 | 제일 높음 | 방향이 맞나 |
값싼 검증은 스텝마다 두고, LLM-as-judge는 분해 결과가 다음 단계로 넘어가기 전 같은 좁은 게이트에 둡니다. 이 배치를 택하면 judge 비용을 줄이는 것보다, 나쁜 분해가 뒤 단계의 입력이 되는 일을 막는 효과가 큽니다.
평가 환경의 역할
검증 위치를 정한 다음에는 그 평가를 어디서 돌릴지 정합니다. 오프라인, 섀도우, 프로덕션은 서로 대체재가 아닙니다. 잡는 실패와 감수하는 위험이 다릅니다.
| 무대 | 무엇을 잡나 | 정답 라벨 | 위험 |
|---|---|---|---|
| 오프라인 eval | 회귀, 고친 게 유지되나 | 있음(고정 셋) | 없음 |
| 섀도우 | 실제 분포에서의 실패 | 없음 | 낮음(응답 미노출) |
| 프로덕션 | 진짜 사용 신호 | 없음 | 높음(사용자에게 노출) |
오프라인 벤치마크는 고정된 입력 셋에 정답을 붙여 두고 코드 고칠 때마다 돌립니다. 빠르고 싸고, 무엇보다 회귀를 잡습니다. 지난주에 고친 케이스가 이번 수정으로 다시 깨졌는지 여기서 걸러집니다. 한계는, 고정 셋은 내가 미리 상상한 입력만 담는다는 것입니다. 분포 밖 입력이나 여러 스텝이 얽히며 생기는 실패는 못 봅니다.
섀도우는 실제 트래픽을 에이전트에 흘리되, 그 응답을 사용자에게는 내보내지 않고 로그만 쌓는 방식입니다. 실사용 분포를 그대로 받으면서 위험은 거의 없습니다. 정답 라벨이 없으니 LLM-as-judge나 값싼 규칙으로 1차 분류를 하고, 의심스러운 것만 사람이 봅니다.
프로덕션은 실제 사용자에게 응답이 나가는 자리입니다. 사용자의 수정, 재시도, 이탈 같은 신호는 오프라인 셋으로 만들 수 없습니다. 대신 나쁜 응답이 노출되므로, 관측 지표를 eval 점수와 같은 뜻으로 읽으면 안 됩니다. 수정이 많다고 항상 답이 나빴던 것도 아닙니다.
LLM-as-judge의 신뢰 범위
섀도우 로그를 훑거나 게이트를 채점할 때 LLM-as-judge, 즉 모델이 다른 모델의 출력을 채점하는 방식을 쓸 수 있습니다. 하지만 judge 점수는 정답이 아닙니다. MT-Bench 연구는 position, verbosity, self-enhancement bias를 한계로 짚었습니다. self-preference 연구도 모델이 사람이 아닌 자신에게 더 익숙한 문체에 높은 점수를 줄 수 있다고 보고합니다.
| 편향 | 증상 | 완화 |
|---|---|---|
| position | A/B 중 앞자리를 편애 | 순서 바꿔 두 번 채점(swap), 뒤집히면 사람에게 |
| verbosity | 내용과 무관하게 긴 답을 후하게 | 길이 페널티를 프롬프트에 명시 |
| self-preference | 익숙한 문체나 자기 계열 출력을 높게 | 후보의 모델명 제거, 사람 라벨과 비교 |
position bias는 코드로 바로 점검할 수 있습니다. 같은 쌍을 순서만 바꿔 두 번 채점한 뒤, 후보 기준으로 환산한 승자가 다르면 사람에게 넘깁니다. 단순히 두 응답의 A/B 라벨이 같은지만 비교하면 스왑 뒤의 의미가 바뀌므로 잘못된 판정이 됩니다.
# 순서만 바꿔 두 번 채점한 뒤, 실제 후보 기준으로 승자를 비교
def robust_judge(judge, a, b):
first_slot = judge(a, b) # "A" 또는 "B"
second_slot = judge(b, a)
first_winner = a if first_slot == "A" else b
second_winner = b if second_slot == "A" else a
if first_winner == second_winner:
return first_winner
return "needs_human"
판정자를 리더보드처럼 쓰기 전에 사람이 라벨한 작은 셋과 먼저 맞춰봅니다. 이때 단순 일치율만 보면 한쪽 후보만 자주 이기는 셋에서 착시가 생길 수 있습니다. 후보별 승률과 불일치 사례를 같이 보고, 필요하면 Cohen’s kappa 같은 우연 일치 보정 지표도 확인합니다.
sympo의 한 개 게이트
세 결정은 따로 노는 게 아니라 분해 직후의 judge 게이트에서 만납니다.
- 게이트: 분해 직후 한 곳에 둡니다. 여기서 걸러야 뒤 단계가 나쁜 입력을 받지 않습니다.
- 환경: 고정 PRD 셋으로 회귀를 보고, 실제 PRD는 섀도우에서 결과를 노출하지 않은 채 로그로만 살핍니다.
- 판정: 후보의 모델명과 agent 이름은 judge 입력에서 뺍니다. 그래도 편향은 사라지지 않으므로, 사람 라벨과의 불일치를 계속 확인합니다.
위치만 바꾸거나 judge만 바꿔서는 충분하지 않습니다. 이 세 결정을 같이 기록해야 다음 수정에서 왜 게이트를 바꾸는지도 설명할 수 있습니다.
주의 검증도 판정도 공짜가 아닙니다. 스텝마다 judge를 붙이면 파이프라인이 검증 비용에 잡아먹히고, 판정자-사람 일치율을 안 재고 judge 점수를 성능으로 읽으면 편향을 실력으로 착각합니다. 값싼 층으로 거를 수 있는 건 거기서 끝내고, 판정은 믿을 만한지 확인한 만큼만 씁니다.
섀도우 로그를 회귀 셋으로 옮기는 과정
섀도우 로그에서 새 실패를 찾았으면, 사람이 원인과 기대 출력을 적어 오프라인 PRD 셋에 넣습니다. 다음 변경부터는 그 실패가 회귀로 잡힙니다. judge와 사람의 불일치가 많은 유형은 자동 판정에서 빼고, 일치가 안정적인 유형만 judge에 맡기는 편이 낫습니다.
token-stack에는 먼저 스텝 이름, 검증 결과, judge 점수, 사람 재검토 여부를 남길 생각입니다. 이 네 값이 모이면 실패가 특정 단계에 쏠리는지, judge가 어떤 유형에서 흔들리는지 확인할 수 있습니다. 그 뒤에야 게이트를 옮기거나 판정 프롬프트를 고칠 근거가 생깁니다.