
에이전트를 위한 평가(Evals): 작업 성공, 궤적(Trajectory) 및 인간 검토 점수 매기기
요약
에이전트 시스템의 성능을 평가할 때 단일 결과값만으로는 부족하며, 작업 성공, 실행 궤적, 인간의 판단이라는 세 가지 축을 모두 고려해야 함을 설명합니다. 최종 상태뿐만 아니라 과정의 합리성을 함께 검증해야 신뢰할 수 있는 AI 서비스를 구축할 수 있습니다.
핵심 포인트
- 단일 숫자 기반의 평가는 에이전트의 잠재적 실패 사례를 놓칠 수 있음
- 작업 성공(Task success)은 최종 상태가 요청을 충족했는지 확인하는 것
- 궤적(Trajectory) 평가는 도구 사용의 순서와 인자의 적절성을 검증함
- 인간 판단(Human judgment)은 자동화가 어려운 리스크 높은 상황의 정답 역할을 함
- 도서: Observability for LLM Applications — Tracing, Evals, and Shipping AI You Can Trust
- 저자의 다른 저서: Agents in Production — The AI Engineer's Library (2권 시리즈)의 동반 도서
- 내 프로젝트: Hermes IDE | GitHub — Claude Code 및 기타 AI 코딩 도구를 사용하여 작업하는 개발자를 위한 IDE
- 자기소개: xgabriel.com | GitHub
에이전트가 작업을 마쳤습니다. 트레이스(Trace)는 초록색입니다. 모든 스팬(Span)이 ok라고 표시됩니다. 사용자는 답변을 받았고, 대시보드의 성공 카운터는 1만큼 올라갔습니다. 당신이라면 금요일에 바로 배포하고 퇴근했을 것입니다.
그런 다음 누군가 실행 기록을 읽습니다. 고객 지원 티켓을 처리하기 위해 에이전트는 지식 베이스(Knowledge base)를 검색했지만 아무것도 찾지 못했습니다. 동일한 쿼리로 다시 검색했지만 역시 아무것도 찾지 못했습니다. 결국 해당 등급에서는 건드려서는 안 될 refund_customer 도구로 에스컬레이션(Escalation)하여 37달러를 환불하고, 쾌활한 종료 메시지를 작성한 뒤 티켓을 해결됨으로 표시했습니다. 작업 성공도: 1.0. 하지만 실행 자체는 작은 화재와 같았습니다.
그 간극이 바로 에이전트에게 단일 숫자 기반의 평가(Evals)가 실패하는 지점입니다. 일반적인 LLM 호출을 평가할 때는 출력(Output)이 단위였습니다. 입력을 넣고, 출력을 내보내고, 등급을 매기고, 넘어가면 되었습니다. 하지만 에이전트는 등급을 매길 단일 출력이 없습니다. 에이전트에게는 입력, 궤적(Trajectory), 그리고 최종 상태(Final state)가 있습니다. 최종 상태만 점수를 매기면 흥미로운 모든 실패 사례를 놓치게 됩니다. 궤적만 점수를 매기면 우연히 성공한 사례들을 놓치게 됩니다. 두 가지 모두가 필요하며, 어느 쪽도 확실하지 않을 때 인간을 개입시킬 방법도 필요합니다.
하나의 축이 아닌 세 개의 축
이미 알고 있는 평가 루프(Eval loop)는 여전히 유효합니다. 데이터셋을 수집하고, 판사(Judge)를 통해 점수를 매기고, 점수를 추적하며, 점수가 떨어지면 CI(지속적 통합)를 실패 처리합니다. 에이전트의 경우 변하는 것은 평가의 단위입니다. 하나의 실행(Run)에는 점수를 매길 가치가 있는 세 가지 요소가 있습니다:
- 작업 성공 (Task success). 마지막에 세상이 사용자가 요청한 대로 변했나요? 최종 상태 확인 (Final-state check)입니다.
- 궤적 (Trajectory). 진행 과정에서 단계들이 합리적이었나요? 올바른 도구, 올바른 순서, 올바른 인자(Arguments)를 사용했는지, 그리고 방황하지 않았는지 확인합니다.
- 인간 판단 (Human judgment). 자동화된 체크 방식 중 어느 것도 확신할 수 없거나 리스크가 큰 경우, 사람이 실행 결과에 점수를 매깁니다. 이러한 점수들은 앞선 두 가지 요소에 대한 정답(Ground truth)이 됩니다.
모든 프로덕션 에이전트 평가 스택(Eval stack)은 이 세 가지의 혼합 형태입니다. 도구마다 사용하는 어휘는 다를 수 있지만, 그 형태는 동일합니다.
축 1: 작업 성공 (Task success)
작업 성공은 설명하기 가장 쉽지만, 이것만 믿기에는 가장 위험합니다. 원래의 요청과 최종 상태를 가져와서, 그 상태가 요청을 충족하는지 묻는 방식입니다. LLM 판사(Judge)가 실행 과정을 읽고 이유와 함께 0에서 1 사이의 점수를 반환합니다.
DeepEval의 TaskCompletionMetric은 트레이스(Trace)를 읽고, 첫 번째 사용자 턴에서 작업을 추론하며, 마지막 어시스턴트 턴과 도구 호출(Tool calls)로부터 결과를 추론하여 점수를 매깁니다. 수동으로 작성된 테스트 케이스가 필요하지 않습니다.
from deepeval.metrics import TaskCompletionMetric
from deepeval.test_case import LLMTestCase, ToolCall
from deepeval.models import AnthropicModel
...
일반적인 호출(Plain calls)에 대해 충실도(Faithfulness)를 연결했던 방식 그대로 CI에 연결하세요. 매일 밤 프로덕션 트레이스 샘플을 추출하여 점수를 매기고, 이동 평균(Rolling average)이 임계값 아래로 떨어지면 빌드를 실패 처리합니다.
한 가지 솔직하게 말하자면, 판사 모델(Judge model)이 매우 중요합니다. 판사는 자신이 채점하는 에이전트만큼은 강력해야 합니다. 강력한 에이전트를 채점하는 약한 판사는 관대하고 너그러운 점수를 남발하게 되며, 이는 점수가 상향 편향되어 사용자에게 아무런 정보도 주지 못하게 만듭니다. 합리적인 기본 설정은 에이전트가 프런티어 모델(Frontier model)에서 작동할 때, Claude Haiku와 같이 감당할 수 있는 가장 저렴한 프런티어 모델로 판단하는 것입니다. 그러다 나중에 인간이 문제를 제기한 실행 건을 통과하는 것을 발견하게 되면, 그때 판사의 등급을 에이전트의 등급으로 업그레이드하세요.
하지만 환불 이슈가 장부에 계속 남게 되는 함정이 있습니다. 환불 에이전트의 엔드포인트(endpoint)는 "환불이 발행된 상태로 티켓 종료"였습니다. 만약 요청이 "마지막 청구 금액을 환불하라"였다면, 해당 엔드포인트는 완벽한 1.0점을 받습니다. 판사(judge)는 에이전트가 무료 티어(free-tier) 계정에서 해당 환불을 발행할 권한이 없었다는 사실을 전혀 알지 못합니다. 이것은 최종 상태(final-state)에 대한 질문이 아닙니다. 이것은 궤적(trajectory)에 대한 질문입니다.
두 번째 축: 궤적 (trajectory)
궤적 점수 매기기(Trajectory scoring)는 '글래스 박스(glass-box, 내부가 보이는 구조)' 측면입니다. 이제 "완료했는가"를 묻는 대신 "에이전트가 이미 알고 있던 정보를 바탕으로 단계들이 합리적이었는가"를 묻기 시작합니다. 이에 관한 연구는 하나의 레시피로 수렴합니다. 즉, 실행 과정을 작은 조각으로 나누고, 명확한 기준에 따라 각 조각을 채점한 뒤, 이를 합산(roll up)하는 것입니다. 궤적 판사(trajectory judge)에게는 최종 답변이 아닌 단계(steps)를 입력해야 합니다. 왜냐하면 판사들은 경로가 아무리 엉망이었더라도 결과가 좋게 끝난 실행 건에 대해 체계적으로 점수를 과하게 높게 주는 경향이 있기 때문입니다.
이를 처음부터 직접 만들 필요는 없습니다. LangChain 조직의 agentevals 패키지에는 이미 만들어진 궤적 평가기(trajectory evaluators)가 포함되어 있습니다: 정확 일치 도구 시퀀스(exact-match tool sequence), 부분 집합(subset), 순서 없는 집합(unordered set), 그리고 LLM 기반 채점(LLM-graded) 방식이 있습니다. 트레이스(trace)를 연결하고, 예상되는 형태를 입력하면 판결을 얻을 수 있습니다.
from agentevals.trajectory.match import (
create_trajectory_match_evaluator,
)
...
환불 시나리오는 이 검사를 명백히 통과하지 못합니다. 예상된 경로는 escalate_to_human으로 끝나야 합니다. 실제 경로는 refund_charge로 끝납니다. 작업 성공(Task success)은 1.0이라고 말했지만, 궤적(Trajectory)은 거짓(false)이라고 말합니다. 어느 한 축만으로는 다른 축이 놓치는 실패 유형을 잡아낼 수 없습니다.
비용이 문제가 되지 않는 한, 모든 단계를 LLM 판사로 채점하지 마십시오. 실용적인 스택은 중요한 단계(파괴적인 도구 호출, 최종 답변 형태)에 대해서는 정확 일치(exact-match) 방식을 사용하고, 그 사이의 모호한 추론에 대해서는 LLM 판사를 사용하며, 마지막에 루브릭(rubric)을 한 번 통과시키는 방식입니다. 저렴한 단계별 채점기(step-level scorers)는 단순한 함수로 구현됩니다:
AUTHORIZED = {"free": set(), "pro": {"refund_charge"}}
def destructive_authorized(step, **_):
...
첫 번째는 단계별(per-step basis)로 승인되지 않은 파괴적인 호출을 포착합니다. 두 번째는 방황(wandering)에 대해 페널티를 부여합니다. 두 방식 모두 모델을 호출하지 않으며, 모든 재현(replay) 시 실행됩니다. 또한, 엔드포인트 메트릭(endpoint metric)으로는 절대 파악할 수 없는 실패 유형을 모두 잡아냅니다.
세 번째 축: 인간 검토 (human review)
자동화된 평가(Automated evals)는 비용이 저렴하지만 오류가 빈번하여, 그 불일치가 가장 중요한 사례들에 집중되는 경향이 있습니다. 해결책은 판사 프롬프트(judge-prompt)를 한 번 더 튜닝하는 것이 아닙니다. 해결책은 모호한 실행(runs)을 운영 환경에서 추출하여 사람에게 보여주고, 그 결과로 나온 등급을 정답(ground truth)으로 다시 피드백하는 인간 검토 큐(human review queue)를 구축하는 것입니다.
이는 세 가지 부분으로 구성됩니다. 라우터(Router)는 트레이스 스트림(trace stream)을 읽고 플래그가 지정된 트레이스 ID를 큐 테이블(queue table)에 기록합니다. 리뷰어 UI(Reviewer UI)는 다음 플래그가 지정된 트레이스를 띄워 전체 내용을 보여주고 레이블(label)을 캡처합니다. 라이터(Writer)는 완료된 레이블을 오프라인 평가 데이터셋(offline eval dataset)에 추가합니다. Langfuse, Braintrust, LangSmith는 이 세 가지를 모두 제공하며, 직접 구축한다면 대략 200줄의 코드와 6개 컬럼의 테이블이 필요합니다.
가장 중요한 UI 선택 사항은 다음과 같습니다: 리뷰어는 최종 메시지만 보는 것이 아니라 궤적(trajectory)을 반드시 봐야 합니다. 유창한 마지막 답변만 보여준다면 리뷰어들은 모든 것에 무조건 승인(rubber-stamp)을 내릴 것입니다. 요청(request)을 먼저 토씨 하나 틀리지 않고 그대로 보여주세요. 그다음 궤적을 보여주되, 한 행에 한 단계씩 배치하고 파괴적인 도구(destructive tools)에는 배지(badge)를 달아 표시하세요. 마지막으로 최종 메시지를 배치합니다. 그다음 자동화된 점수(automated scores)를 보여주되, 가시적이지만 권위적이지는 않게 배치합니다. 마지막으로 최대 3개의 필드로 구성된 짧은 채점 양식을 제공합니다. 리뷰어는 3개 필드 양식을 사용할 때는 시간당 50개의 실행을 레이블링할 수 있지만, 10개 필드 양식을 사용하면 시간당 10개밖에 하지 못합니다.
라우팅 규칙(Routing rules)은 큐의 신뢰성을 유지합니다. 모든 것을 보내버리면 리뷰어들은 밀린 업무를 처리하기 위해
루프(Loop) 자체가 핵심입니다. 플래그(Flagged)가 지정된 트레이스(Trace)에 대한 인간의 라벨링은 백 개의 합성 케이스(Synthetic cases)보다 가치가 있습니다. 왜냐하면 그것이 실제 요청과 실제 실패로부터 나왔기 때문입니다. 라벨을 매주 내보내고(Export), 이를 바탕으로 판사(Judge)를 다시 실행하며, 판사가 사람과 의견이 다를 때 판사를 튜닝(Tune)하세요. 한 분기가 지나면 판사는 더 저렴해지고 더 정확해질 것이며, 여러분은 큐(Queue)에 대한 의존도를 양적 확보보다는 롱테일(Long tail) 대응을 위해 사용하게 될 것입니다.
공개 벤치마크(Public benchmarks)에 대하여
누군가는 항상 SWE-bench와 GAIA가 어디에 해당하는지 묻습니다. 짧은 답변은 다음과 같습니다: 여러분의 CI(지속적 통합)에 포함되지 않습니다. 이들은 어떤 모델이 특정 도메인의 베이스라인을 통과하는지 알려주지만, SWE-bench Verified를 해결하지 못하는 모델은 여러분의 코드베이스(Codebase)도 해결하지 못할 것입니다. 하지만 이를 상위권에 올린 모델이라 할지라도 여러분의 코드베이스에서는 여전히 형편없을 수 있습니다. 벤치마크의 입력 분포(Input distribution)가 여러분의 사용자 분포와 다르기 때문입니다. SWE-bench 작업은 격리된 수정 사항이 포함된 깨끗한 Python 프로젝트에서 가져옵니다. 반면 여러분의 버그 리포트(Bug reports)는 Slack 스크린샷 형태로 도착하며, 하나의 "수정"은 세 개의 서비스와 마이그레이션(Migration)에 걸쳐 발생합니다.
대신 지루한 것을 만드세요. 지난 한 주 동안의 실제 요청 100개를 가져와서, 원하는 결과에 따라 각각 수동으로 라벨링(Hand-label)하고, 그 쌍을 JSON으로 저장하세요. 그 골든 세트(Golden set)는 그 어떤 리더보드(Leaderboard)보다 규모가 작고 노이즈가 많지만, 여러분의 에이전트가 비용을 지불하는 사람들에게 제대로 작동하는지를 추적할 수 있는 유일한 데이터셋입니다. 에이전트를 건드리는 모든 PR(Pull Request)에서 이를 실행하고, 리뷰 큐(Review queue)를 통해 이를 키워나가세요. 모델 업그레이드가 공개 리더보드와 여러분의 골든 세트를 서로 반대 방향으로 움직이게 만든다면, 골든 세트를 신뢰하세요.
이것이 여러분에게 남기는 것
이제 여러분은 세 가지 축을 기준으로 에이전트를 평가할 수 있습니다: 엔드포인트(Endpoint)에 대한 작업 성공(Task success), 경로에 대한 궤적(Trajectory), 그리고 앞의 두 가지가 판단할 수 없는 실행에 대한 인간 검토(Human review)입니다. 작업 성공만 따졌다면 환불 관련 화재(Refund fire) 상황을 그대로 배포했을 것입니다. 이 세 가지가 함께 있어야 이를 잡아낼 수 있습니다. 이 중 어느 것도 에이전트가 처음에 환불해서는 안 될 돈을 환불하는 것을 막지는 못합니다. 트레이싱(Tracing)은 사후에 이를 확인하고, 평가(Evals)는 사후에 점수를 매기며, 큐(Queue)는 사후에 이를 잡아냅니다. _사후(After)_는 세 가지 모두가 공유하는 단어이며, 이것이 바로 여러분이 다음 금요일 배포 전에 이 세 가지를 모두 실행하고 싶어 하는 정확한 이유입니다.
만약 여러분이 이러한 실행(runs)을 생성하는 에이전트를 구축하고 있다면, _Agents in Production_은 도구 루프(tool loops), 권한 부여(authorization), 그리고 제품을 안전하게 출시(shipping)하는 법에 관한 책입니다. 만약 그 아래에서 트레이싱(tracing) 및 평가(eval) 파이프라인을 연결하고 있다면, _Observability for LLM Applications_는 위에서 언급한 루프를 실제로 구현 가능하게 만드는 트레이스(traces), 판사(judges), 그리고 비용 회계(cost accounting)를 다룹니다. 이 책들은 함께 _The AI Engineer's Library_를 구성합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기