AI 에이전트 평가: 감이 아니라 신뢰도를 재는 evals 구축법
AI 에이전트 평가는 고정된 실제 과제 세트를 정기적으로 실행하고, 결과를 자동으로 검사해 지표를 계산하는 일입니다. 몇 번 잘 돌아간 세션의 인상으로 판단하지 않습니다. 핵심은 pass@k(k번 시도 중 한 번이라도 성공)뿐 아니라 pass^k(k번 모두 성공)도 재는 것입니다. 무인 작업에 에이전트를 맡길 수 있는지는 안정성에 달려 있기 때문입니다.
에이전트가 잘 작동하는 게 보이는데 왜 평가가 필요할까
사실 그게 잘 보이지 않습니다. 일주일 동안 에이전트가 프롬프트를 고친 뒤 한 번도 멈추지 않았다면, 그것은 개선인가요, 아니면 단순한 업무 몇 개가 우연히 쉬웠던 것인가요? 한 엔지니어는 새 템플릿이 모든 것을 바꿨다고 확신하고, 같은 로그를 본 다른 엔지니어는 차이를 느끼지 못합니다. 둘 다 아무것도 세어 보지 않았다는 점에서만 옳습니다.
AI 에이전트 평가(evals)는 느낌을 숫자로 바꿉니다. 고정된 과제 세트를 정하고, 에이전트를 실행하고, 결과를 자동으로 검사한 뒤, 변경 전후의 지표를 비교합니다. 그러면 '좋아졌는가'라는 논쟁이 권위가 아니라 표로 결정됩니다.
평가가 없다면 하네스의 어떤 변경이든, 즉 프롬프트, AGENTS.md, 도구 세트, 모델 교체든 모두 눈을 감고 거는 내기입니다. 평가가 있다면 측정 가능한 결과를 내는 실험이 됩니다.
'감'이 속이는 이유
에이전트 작업에 대한 인상에는 세 가지 체계적 오류가 있고, 모두 잘못된 확신으로 이끕니다.
- 생존자 편향. 성공한 실행은 기억에 남고 동료에게 보여 줍니다. 실패한 실행은 다시 돌리거나 잊어버립니다. 성공한 실행만으로 계산한 신뢰도는 에이전트가 실제로 어디서 무너지는지 알려주지 않습니다.
- 작은 표본. 절반의 확률로 성공하는 에이전트도 과제 세 개를 연속으로 성공할 수 있습니다. 그 확률은 0.5³ = 12.5%로, 대략 여덟 번에 한 번꼴입니다.
- 자신만만한 어조. 에이전트는 '완료, 모든 테스트 통과'를 맞을 때와 틀릴 때 똑같이 확신에 찬 어조로 말합니다. 모델의 확신은 교정되어 있지 않습니다. 답변의 어조는 정확성의 신호가 아닙니다.
그래서 규칙이 나옵니다. 기록되지 않았고 다시 계산되지 않은 것은 측정되지 않은 것으로 봅니다.
pass@k와 pass^k: 헷갈리기 쉬운 두 지표
에이전트는 비결정적입니다. 같은 과제를 다시 돌리면 통과할 수도 있고 실패할 수도 있습니다. 그래서 과제 하나를 여러 번 실행하고 두 지표 중 하나를 계산합니다.
pass@k: k번 시도 중 적어도 한 번 성공
이 지표는 코드 생성 평가에서 왔습니다. '여러 번 시도할 기회가 주어졌을 때 시스템이 원칙적으로 과제를 해결할 수 있는가'라는 질문에 답합니다. 사람이 여러 결과 중 가장 좋은 것을 고르는 상황이나, 실패한 결과를 걸러내는 믿을 만한 자동 검사가 있는 상황에서는 정직한 지표입니다.
pass^k: k번 모두 성공
이 지표는 다른 질문에 답합니다. '에이전트가 이 과제를 얼마나 안정적으로 해결하는가'. 사람이 지켜보지 않는 작업을 설명합니다. 에이전트를 많이 돌리고, 실패 한 번이 돈이나 시간을 잃게 만드는 상황입니다.
둘의 차이는 엄청납니다. 에이전트가 한 번 시도에 과제를 70% 확률로 해결하고, 시도가 서로 독립적이라고 가정해 봅시다.
| k | pass@k = 1 − 0.3^k | pass^k = 0.7^k |
|---|---|---|
| 1 | 70% | 70% |
| 3 | 97.3% | 34.3% |
| 5 | 99.8% | 16.8% |
| 10 | ~100% | 2.8% |
pass@5로 보면 에이전트는 거의 완벽해 보이고, pass^5로 보면 믿을 수 없습니다. 두 숫자는 모두 맞고, 다른 것을 설명할 뿐입니다. 에이전트를 자율적으로 풀어 놓을 생각이라면 pass^k를 보세요. 실제 작업에서 마주칠 모습에 더 가깝습니다.
실제로 계산하는 방법
과제마다 n번(예: 5번) 실행하고 성공 횟수 c를 기록합니다. pass^k는 대략 k번 연속 모두 성공한 실행의 비율이며, 더 간단히는 성공 확률 p = c / n을 추정해 k제곱하면 됩니다. 과제 세트 전체에 대해서는 과제별 값을 평균냅니다.
import json
from statistics import mean
# results.jsonl: {"task": "add-pagination", "runs": [true, true, false, true, true]}
rows = [json.loads(line) for line in open("results.jsonl")]
def pass_at_k(runs, k):
p = sum(runs) / len(runs)
return 1 - (1 - p) ** k
def pass_pow_k(runs, k):
p = sum(runs) / len(runs)
return p ** k
for k in (1, 3, 5):
print(k,
round(mean(pass_at_k(r["runs"], k) for r in rows), 3),
round(mean(pass_pow_k(r["runs"], k) for r in rows), 3))
n이 작을 때 p의 k제곱에 의한 추정은 거칠지만, 하네스 버전끼리 비교하기에는 보통 충분합니다. 더 중요한 것은 비교 사이에 n과 과제 세트를 바꾸지 않는 것입니다.
평가 과제 세트 만들기
과제 세트가 가장 중요한 자산입니다. 과제가 나쁘면 아무리 좋은 지표도 엉뚱한 것을 잽니다.
자신의 현실에서 과제를 가져오기
SWE-bench 같은 공개 벤치마크는 모델끼리 비교할 때 유용하지만, 그 과제들은 골라져 있습니다. 설명이 명확하고, 테스트가 준비되어 있습니다. 여러분의 저장소에서는 요구 사항이 모호하고 테스트가 군데군데 빠져 있기 마련이므로, 벤치마크 수치는 여러분의 프로젝트에서 에이전트가 보여 줄 수준보다 거의 항상 높게 나옵니다. 가장 좋은 과제 출처는 닫힌 티켓과 로그에 남은 에이전트의 실제 실패입니다.
세트 구성
- 처음에는 20~50개. 더 적으면 노이즈가 버전 간 차이를 가려 버립니다. 더 많으면 실행 비용이 커져 아무도 돌리지 않게 됩니다.
- 여러 유형을 섞기: 새 기능, 버그 수정, 리팩터링, 남의 모듈을 다루는 과제, 함정이 있는 과제(옳은 답이 '그렇게 하면 안 된다' 또는 '추가 확인이 필요하다'인 경우).
- 작업 중 실패한 과제는 모두 테스트가 됩니다. 이렇게 세트는 에이전트의 약점을 알아 갈수록 함께 자라고, 회귀는 눈에 띄지 않은 채 돌아오지 않습니다.
- 고정된 시작 상태: 특정 커밋, 고정된 의존성, 같은 환경. 그렇지 않으면 에이전트가 아니라 환경의 drift를 재게 됩니다.
과제 형식
과제 하나는 설명, 시작 상태, 사람 없이 확인할 수 있는 성공 기준으로 이루어집니다.
id: add-pagination
repo_ref: 3f2c1ab
prompt: |
GET /payments에 페이지네이션을 추가하세요. 파라미터 limit(기본값 20, 최대 100)과
cursor를 받고, 응답에는 next_cursor가 포함되어야 합니다.
check:
- pytest tests/test_payments_pagination.py
- mypy app/
limits:
max_changed_files: 6
성공 기준이 곧 Definition of Done이며, 명령으로 적혀 있다는 점에 주목하세요. 과제에 대한 기준을 적을 수 없다면, 그것은 에이전트에게도 과제가 모호하게 주어졌다는 신호입니다.
자동 검사냐, LLM 심판이냐
결과는 코드로도, 다른 모델로도 검사할 수 있습니다. 두 방식에는 각자의 영역이 있습니다.
| 자동 검사(코드) | LLM 심판 | |
|---|---|---|
| 무엇을 검사하나 | 테스트, 타입, 린터, E2E, diff 크기, 금지된 파일 | 요구 사항 부합, 가독성, 스타일, 설명의 품질 |
| 결정성 | 완전함 | 없음: 실행마다 점수가 흔들림 |
| 비용 | 거의 무료 | 과제마다 모델 호출이 하나 더 필요 |
| 주요 위험 | 모든 것을 검사하지 못함: 테스트는 초록색인데 기능은 엉뚱함 | 자신만만하지만 틀린 해법에 심판이 동의함 |
규칙: 먼저 코드, 심판은 그 위에
결정적으로 검사할 수 있는 것은 결정적으로 검사합니다. LLM 심판은 코드로 표현할 수 없는 속성에만 추가하고, 테스트를 대신하는 용도로는 절대 쓰지 않습니다.
LLM 심판을 쓸모 있게 만드는 방법
- 생성자와 다른 맥락. 심판은 에이전트의 추론을 보면 안 됩니다. 과제, 기준, 최종 diff만 봐야 합니다. 그렇지 않으면 같은 실수를 반복합니다. 이것이 N-버전 방식, 즉 하나 대신 두 개의 독립적인 시각입니다.
- 10점 척도 대신 루브릭. '예/아니오'로 답할 수 있는 구체적 질문을 씁니다. 예: 'limit > 100에 대한 검증이 있는가', 'app/payments 바깥의 파일이 바뀌었는가'.
- 수작업 라벨로 보정. 결과 20~30개를 직접 라벨링하고 심판의 판정과 비교합니다. 일치율이 낮으면 심판을 더 신뢰할 것이 아니라 루브릭을 고쳐야 합니다.
- 오류의 비대칭. 잘못된 '완료'는 잘못된 '미완료'보다 비쌉니다. 전자는 운영 환경으로 나가고, 후자는 재실행 한 번이면 됩니다. 의심스러울 때 거부하도록 심판을 설정하세요.
성공률 말고 세야 할 지표
해결된 과제의 비율은 맨 위에 보이는 숫자일 뿐입니다. 어디를 고쳐야 할지 알려면 고전적인 공학의 신뢰성 지표가 필요합니다.
- MTBF — 에이전트가 멈추기 전까지 평균 몇 단계나 과제를 해내는지. 얼마나 드물게 망가지는지 보여 줍니다.
- MTTR — 실패 후 정상 상태로 돌아오는 데 걸리는 시간. 이 값을 줄이는 것은 MTBF를 올리는 것보다 거의 항상 쌉니다. 좋은 진행 파일과 재현 가능한 환경은 복구 시간을 몇 배 줄입니다.
- 피해 반경 — 실패한 단계 하나가 망가뜨리는 코드의 양. diff 크기 제한과 작업 복사본의 격리로 제한합니다.
- 거짓 '완료' 비율 — 에이전트는 성공을 보고했지만 검사가 실패한 과제의 비율. 가장 위험한 지표입니다. 에이전트의 보고를 믿어도 되는지를 보여 주기 때문입니다.
- 비용과 시간 — 과제당 토큰과 분. 성공률을 5% 올렸지만 비용을 두 배로 만든 하네스가 항상 이기는 것은 아닙니다.
실패 로그: 일상 작업에서 얻는 평가
모든 팀이 바로 평가 러너를 만들 준비가 된 것은 아닙니다. 최소한의 평가는 세 열짜리 실패 로그입니다.
| 과제 | 결과 | 계층 |
|-------------------------|------------------------------------|---------------|
| /payments 페이지네이션 | '완료', 빨간 테스트 2개 | 검증 |
| webhook 타임아웃 수정 | pip install에 30분 | 환경 |
| refunds 리팩터링 | 구식 ORM 문법 | 맥락 |
계층은 에이전트가 무너지는 다섯 곳 중 하나입니다. 과제 명세, 맥락, 실행 환경, 검증 피드백, 상태 관리입니다. 기록이 스무 개쯤 쌓이면 실제 병목이 보입니다. 그리고 그것은 거의 항상 예상한 곳이 아닙니다. 로그의 각 줄은 평가 과제 세트의 후보입니다.
강한 모델이 해결해 주지 않는 이유
에이전트가 실패하면 가장 먼저 떠오르는 생각은 더 비싼 모델을 쓰는 것입니다. 가끔은 도움이 되지만, 벤치마크와 현실 사이의 격차를 메우는 것은 모델보다 그 주변 환경인 경우가 더 많습니다.
에이전트가 30분 동안 환경을 세팅하고 있다면, 그것은 환경 계층의 문제입니다. 가장 강한 모델도 같은 30분을 세팅에 씁니다. 테스트가 빨간데도 에이전트가 '완료'라고 말한다면, 그것은 검증 계층의 문제입니다. 진실을 말해 줄 명령이 없는 것입니다. 에이전트가 프로젝트와 다른 스타일로 코드를 쓴다면, 프로젝트 규칙을 주지 않은 것입니다. 이 문제들 중 어느 것도 모델의 지능과 관계가 없습니다.
평가로 이를 정직하게 확인할 수 있습니다. 현재 모델로 세트를 돌리고, 더 비싼 모델로도 돌립니다. 그다음 현재 모델에 검증 명령이 적힌 AGENTS.md를 추가해서 돌립니다. 대개 두 번째 변경이 첫 번째보다 더 큰 향상을 주고 비용은 0입니다. 하지만 말만 믿지 마세요. 이런 비교를 하라고 세트가 있는 것입니다.
일주일 계획: 에이전트 평가를 시작하기
- 1일차. 실패 로그를 만들고, 에이전트의 실패마다 계층과 함께 기록합니다.
- 2일차. 닫힌 티켓과 로그에서 과제 20개를 고릅니다. 과제마다 커밋을 고정하고 성공 기준을 명령으로 적습니다.
- 3일차. 러너를 작성합니다. 깨끗한 작업 복사본, 과제 프롬프트로 에이전트 실행, 검사 실행, JSONL에 결과 기록.
- 4일차. 과제마다 3~5번 실행합니다. pass@1, pass^3, 거짓 '완료' 비율, 평균 비용을 계산합니다.
- 5일차. 하네스에서 한 가지만 바꾸고 다시 돌립니다. 같은 세트와 같은 반복 횟수로만 비교합니다.
한 번에 변수 하나만 바꾸세요. 한 실험에서 모델, 프롬프트, 도구를 함께 바꾸면 무엇이 효과가 있었는지 알 수 없습니다.
에이전트 평가에서 흔한 실수
- 과제당 한 번만 실행. 에이전트의 비결정성 때문에 그 결과는 복권과 같습니다. 최소 세 번은 반복하세요.
- 비교 사이에 세트가 바뀜. 새 과제를 추가하면서 동시에 프롬프트를 바꾸면, 숫자는 비교할 수 없게 됩니다.
- 에이전트가 검증 테스트를 봄. 테스트가 저장소에 있으면 에이전트는 코드를 테스트에 맞추거나 테스트 자체를 고칠 수 있습니다. 숨겨진 검사는 작업 복사본 바깥에 두고, 에이전트가 테스트 파일을 건드리지 않았는지 확인하세요.
- 검증 대신 에이전트의 보고. 모델의 답변에 적힌 '모든 테스트 통과'는 결과가 아닙니다. 결과는 러너가 실행한 명령의 종료 코드입니다.
- pass@k만 보기. 발표용으로는 보기 좋은 숫자지만, 자율 작업에 대한 예측으로는 나쁩니다.
정리
AI 에이전트 평가는 고정된 실제 과제 세트, 자동 검사, 그리고 하네스를 바꿀 때마다 다시 계산되는 지표입니다. pass@k는 에이전트가 무엇을 할 수 있는지 보여 주고, pass^k는 무인 작업을 맡겨도 되는지를 보여 줍니다. 자동 검사는 진실을 주고, 루브릭과 별도 맥락을 갖춘 LLM 심판은 코드가 닿지 못하는 곳을 보완합니다. 다섯 계층으로 나눈 실패 로그는 무엇을 먼저 고쳐야 하는지 보여 주며, 대개 그것은 모델이 아닙니다. 신뢰성 지표와 진단 루프는 강의에서 실제 사례로 자세히 다룹니다.
자주 묻는 질문
에이전트 평가에 과제가 몇 개나 필요한가요?
시작하려면 자동 성공 기준이 있는 실제 과제 20~50개와 과제당 3~5회 반복이면 충분합니다. 더 작은 세트로는 개선과 노이즈를 구분할 수 없고, 더 큰 세트는 비용이 커져 아무도 돌리지 않게 됩니다.
공개 벤치마크만으로 에이전트를 평가할 수 있나요?
아닙니다. 공개 벤치마크는 골라진 과제와 준비된 테스트로 모델을 비교하며, 여러분의 저장소는 요구 사항과 테스트가 다릅니다. 자신의 하네스에 대한 결정에는 자체 과제 세트가 필요합니다.
pass@1과 pass^1의 차이는 무엇인가요?
없습니다. 시도가 한 번이면 두 지표 모두 성공한 실행의 비율과 같습니다. k가 1보다 커지면 비로소 달라집니다.
LLM 심판이 에이전트와 같은 모델이어도 되나요?
맥락을 분리하면 됩니다. 심판은 과제, 루브릭, 최종 diff만 보고 에이전트의 추론은 보지 않아야 합니다. 그래도 심판의 판정은 수작업 라벨과 대조해 확인해야 합니다.
평가는 얼마나 자주 돌려야 하나요?
하네스에 눈에 띄는 변경을 할 때마다, 즉 프롬프트, 지침 파일, 도구 세트, 모델을 바꿀 때마다 돌리세요. 세트를 일정에 맞춰 주기적으로 돌리면 환경과 의존성의 drift도 잡아낼 수 있습니다.
이 주제는 “Harness Engineering: инженерия надёжности AI-агентов” 강의에서 실습과 함께 자세히 다룹니다.
- Метрики надёжности AI-агента: MTBF, MTTR, радиус поражения и закон Литтла
- Почему AI-агент ломается в проде: пять слоёв отказа и диагностическая петля