AI 기능을 붙이고 나서 가장 곤란한 순간은 배포 직전이다. 데모는 잘 된다. 팀에 보여주면 반응도 좋다. 그런데 “이거 내보내도 됩니까”라는 질문 앞에서는 할 말이 없다. 잘 되는 것 같긴 한데 얼마나 잘 되는지 말할 방법이 없기 때문이다. 테스트를 짜자니 무엇을 기댓값으로 둘지부터 막힌다. Airbnb 엔지니어링 팀이 이 문제를 정면으로 다룬 글을 냈길래 읽고 정리해 둔다.
글은 이 습관에 이름을 붙인다. Eval 주도 개발(Eval-Driven Development, EDD), 원문 표현으로는 “테스트 주도 개발의 GenAI 판”이다. 목표와 배포 기준을 먼저 정하고, 실제로 관찰한 실패에서 평가자를 만들고, 그 기준을 통과해야 내보낸다.
다만 TDD와 결정적으로 다른 데가 있다. TDD는 원하는 동작을 먼저 적을 수 있다. 여기서는 무엇이 실패인지를 미리 알 수 없다. 그래서 기준을 세우는 일보다 실패를 데이터에서 찾아내는 일이 앞에 온다. 평가에 프로젝트 공수의 상당한 몫을 쓸 각오를 하라고 글은 못을 박는다.
왜 하던 대로 하면 안 되나
우리가 평소 쓰는 테스트는 세 가지 전제 위에 서 있다. 같은 입력에 같은 출력이 나오고, 정답이 하나로 정해져 있고, 결과는 통과 아니면 실패다. GenAI 기능은 이 셋을 전부 어긴다.
우선 같은 입력에도 답이 달라진다. 어제 통과한 케이스가 오늘 깨질 수 있다. 그러면 실패 하나가 진짜 회귀인지 그냥 흔들림인지 구분이 안 된다.
“좋다”의 기준도 주관적이다. 두 사람에게 같은 답변을 보여줘도 한 명은 친절하다고 하고 다른 한 명은 장황하다고 한다. 기댓값을 문자열로 박아둘 수가 없다.
마지막 하나가 제일 성가시다. 요즘 AI 기능은 한 번에 답을 내지 않는다. 검색으로 자료를 끌어오고, 그걸 근거로 추론하고, 필요하면 도구를 호출하고, 마지막에 문장을 만든다. 이 단계들이 각자 따로 망가진다. 최종 답이 이상할 때 어디서 틀어졌는지는 결과만 봐서는 알 수 없다.
이 셋이 겹치면 익숙한 상태에 빠진다. 프롬프트를 고치면 이쪽은 좋아지는데 저쪽이 나빠진다. 나빠진 걸 알아채는 건 며칠 뒤 누가 이상한 캡처를 올렸을 때다. 두더지잡기를 계속 하게 되는 건 실력 문제가 아니다. 무엇이 좋아지고 무엇이 나빠졌는지를 재지 못하기 때문이다.
막히면 데이터를 봐라
글 전체에서 가장 세게 밀고 있는 규칙은 의외로 소박하다. 평가 도구를 만들기 전에, 출력 100개를 직접 읽어라.
읽기만 하는 게 아니라 읽으면서 실패를 유형별로 분류한다. “이건 근거 없는 말을 지어냈다”, “이건 질문에 답을 안 했다”, “이건 맞는 말인데 너무 길다” 같은 식으로 이름을 붙인다. 100개쯤 읽으면 이름 붙인 유형이 대여섯 개로 수렴하는데, 그중 두세 개가 전체 실패의 대부분을 차지한다는 걸 알게 된다.
이 과정을 건너뛰면 어떻게 되냐면, 남의 프레임워크에서 지표 목록을 가져오게 된다. 정확성, 관련성, 유용성, 안전성, 간결성… 다 그럴듯하다. 그런데 이 지표들은 우리 제품에서 실제로 나는 실패와 맞지 않는다. 정작 자주 나는 실패는 아무도 안 재고 있고, 재고 있는 지표는 항상 초록불이다.
100개를 읽는 건 지루하다. 하루가 통째로 날아간다. 그런데 그 하루를 안 쓰면 그 뒤에 만드는 모든 게 감으로 만든 것이 된다.
다섯 가지 원칙
글은 EDD를 다섯 개의 원칙으로 정리한다. 하나씩 옮기고 내가 이해한 걸 덧붙인다.
1. 성공 지표와 배포 기준을 미리 정한다. 만들고 나서 정하면 결과에 기준을 맞추게 된다. 숫자를 보고 “이 정도면 됐지”라고 말하는 순간, 그 지표는 판단 도구가 아니라 사후 정당화가 된다. 지표와 함께 배포 기준(shipping gate)도 정해야 한다. 몇 점을 넘어야 내보내는지를 안 정하면 지표가 아무리 많아도 그냥 대시보드다.
2. 관찰한 오류에서 지표를 만든다. 앞에서 말한 그것이다. 지표는 데이터에서 발견한다.
3. 평가자는 3~5개만 유지한다. 20~30개로 늘리면 그때부터는 소음이다. 이 항목이 제일 반가웠다. 평가 항목은 늘리기가 너무 쉬워서, 회의에서 누가 “이것도 봐야 하지 않나요”라고 하면 반대하기 어렵다. 그렇게 늘어난 지표는 줄이기는 또 어렵다. 정신 차려 보면 아무도 안 보는 대시보드가 되어 있다. 지표가 30개면 우선순위가 없다.
4. 판정이 갈릴 때 결정할 사람 한 명을 정한다. 평가에는 주관이 섞일 수밖에 없다. 그래서 합의로는 끝나지 않는다. “이건 통과인가 아닌가”를 최종적으로 말할 사람이 정해져 있어야 논의가 끝난다.
5. 엔지니어링, 제품, 도메인 전문가가 계속 같이 본다. 무엇이 좋은 답인지는 엔지니어 혼자 정할 수 없다. 한 번 모여서 기준을 정하고 헤어지는 것도 아니다. 실패 유형이 바뀌면 기준도 다시 봐야 한다.
평가는 세 층으로 나눈다
모든 걸 사람이 볼 수는 없다. 그렇다고 모든 걸 기계에 맡길 수도 없다. 그래서 층을 나눈다.
1층은 코드로 짠 규칙 검사다. JSON 스키마를 지켰는지, 필수 항목이 비었는지, 금칙어가 섞였는지처럼 기계가 딱 잘라 판정할 수 있는 것들을 본다. 여기서 걸린 출력은 뒤로 넘기지 않는다. 명백한 실패에 비싼 판정을 쓸 이유가 없고, 형식이 깨진 출력은 채점해 봐야 나오는 점수가 의미 없기 때문이다.
글에서 강조하는 건 출력 구조화다. 애초에 스키마를 강제해 두면 형식이 무너지는 사고가 크게 줄어든다. 평가 이전에 예방인 셈이다.
2층은 LLM 심판(LLM-as-Judge)이다. 더 성능 좋은 모델에게 채점 기준표(루브릭)를 주고 출력을 채점하게 한다. 톤이 적절한지, 앞뒤가 맞는지, 질문에 실제로 답했는지를 여기서 본다. 규칙으로는 못 쓰는 것들이다. 싸고 빠르니까 대량으로 돌릴 수 있다.
3층은 사람이다. 위험이 큰 영역을 맡는다. 아래 두 층의 판단이 갈릴 때도 3층이 최종 기준이다. 그런데 3층의 진짜 역할은 따로 있다. 2층이 제대로 채점하고 있는지를 여기서 확인한다. 사람이 매긴 라벨이 없으면 LLM 심판이 맞는지 틀리는지 알 방법 자체가 없다.
LLM 심판을 쓸 때 지킬 것
2층이 제일 매력적이면서 제일 위험하다. 싸고 빠른데, 조용히 틀린다. 틀린 채점은 틀린 코드처럼 에러를 내지 않는다. 그냥 그럴듯한 숫자를 뱉는다. 글에서 짚는 조건을 옮긴다.
루브릭을 모호함 없이 쓴다. “답변이 자연스러운가, 1~5점”은 루브릭이 아니다. 심판마다, 실행마다 다르게 읽는다. 글에 실린 가독성 루브릭은 이런 모양이다.
1점 — 매끄럽게 읽힌다.
0점 — 다음 중 하나라도 걸린다: 톤, 내부 용어, 서식, 문법, 복잡도
눈여겨볼 건 척도를 잘게 쪼개지 않았다는 점이다. 5단계로 나누면 3점과 4점 사이에서 심판이 흔들린다. 통과냐 아니냐로 두고, 대신 떨어뜨릴 조건을 구체적으로 나열한다. 판단의 여지를 점수가 아니라 조건 쪽으로 옮긴 셈이다.
사람 판정과 80~90%대 일치율이 나올 때까지 보정한다. 이 숫자가 검증 장치다. 심판이 이 선을 못 넘으면 그 심판이 내는 점수는 근거가 못 된다. 못 넘을 때는 대개 루브릭이 모호하거나, 애초에 사람끼리도 합의가 안 되는 기준을 재려 하고 있다. 둘 다 심판 탓이 아니다. 정의를 안 해둔 탓이다.
골든 데이터셋에 잘된 예시와 망한 예시를 같이 넣는다. 좋은 것만 모아두면 심판이 전부 통과시켜도 만점이 나온다. 나쁜 예시가 있어야 “떨어뜨릴 줄 아는지”를 잴 수 있다. 실패 사례를 모아두는 게 귀찮아서 미루기 쉬운데, 이게 없으면 심판을 검증할 수가 없다.
주기적으로 다시 보정한다. 모델 버전이 올라가고, 프롬프트가 바뀌고, 사용자가 물어보는 것들이 바뀌면 실패 유형도 바뀐다. 반년 전에 맞춰둔 심판이 지금도 맞는다는 보장은 없다.
그래서 실제로는 이렇게 돈다
1단계, 직접 읽기. 테스트 케이스 100개를 돌리고 출력을 눈으로 읽는다. 실패를 유형별로 분류하고 이름을 붙인다.
2단계, 지표 만들기. 1단계에서 발견한 실패만 겨냥해 평가자를 만든다. 유형마다 하나씩, 그중 자주 나는 것부터. 여기서 3~5개 제한이 걸린다.
3단계, 심판 보정. 전문가가 라벨을 매긴 데이터에 심판을 돌려보고, 사람 판정과 얼마나 맞는지 센다. 안 맞으면 루브릭을 고쳐서 다시 돌린다.
4단계, 확장과 관찰. 검증된 심판을 5,000건 규모로 돌린다. 그다음 운영에 붙인다. 원문이 든 예는 꽤 구체적이다. 비식별 처리한 실서비스 트래픽의 5%를 매일 샘플링해 평가하고, 주 1회 PM이 그 결과를 훑는다. 이 주간 리뷰가 루프를 닫는 고리다.
4단계는 끝이 아니다. 운영에서 돌리다 보면 처음에 못 본 실패 유형이 나온다. 그러면 다시 1단계로 돌아간다. 평가 체계는 한 번 만들고 끝나는 산출물이 아니라 계속 도는 루프다.
시작할 때 필요한 라벨은 20~100개 정도면 된다. 처음부터 큰 데이터셋을 모으려다 아무것도 시작 못 하는 쪽이 훨씬 흔하다.
에이전트는 결과만 보면 안 된다
여러 단계를 스스로 밟는 에이전트라면 최종 출력만 채점해서는 부족하다.
봐야 할 게 셋 더 있다. 추론 경로가 말이 되는가, 도구를 정확하게 골라 불렀는가, 그 경로가 쓸데없이 길지 않았는가.
세 번째가 특히 놓치기 쉽다. 답은 맞았는데 도구를 열 번 부른 것과 두 번 부르고 끝낸 것은 같은 점수를 받으면 안 된다. 비용도 다르고, 사용자가 기다리는 시간도 다르고, 무엇보다 열 번 부른 쪽은 다음번에 실패할 확률이 높다.
그리고 운 좋게 답만 맞은 경우와 제대로 풀어낸 경우를 구분하려면 중간 단계를 기록으로 남겨야 한다. 하위 에이전트가 있다면 그 동작도 따로 검증한다. 결국 관측 가능성(observability)을 미리 깔아두지 않으면 에이전트 평가는 시작조차 못 한다.
읽으면서 뜨끔했던 것
내가 실제로 걸렸던 것 세 가지만 적어둔다.
범용 지표의 유혹. “정확성 4.2점”은 아무것도 말해주지 않는다. 무엇이 얼마나 부정확한지 모르니 고칠 수도 없다. 지표는 고칠 수 있는 단위로 쪼개져 있어야 한다.
좋은 예시만 모아둔 데이터셋. 데모용으로 잘 나온 결과만 저장해 두는 습관이 있었다. 그게 평가 데이터로는 최악이다. 실패 사례가 훨씬 비싼 자산이다.
한 번 만들고 방치한 평가. 평가 스크립트를 만들어 두고 두 달쯤 안 봤다. 그사이 모델도 프롬프트도 바뀌었으니 그 점수는 이미 다른 걸 재고 있었을 것이다.
남는 것
글의 마지막 문장이 오래 남는다. 성패를 가르는 건 더 좋은 모델이 아니라 명확한 제품 기준과 팀 간 소통이라는 것.
무엇을 잘한 것으로 볼지 팀이 합의하지 못하면 평가 지표는 아무리 정교해도 그냥 숫자다. 결국 평가 체계를 만드는 일의 절반은 엔지니어링이고, 나머지 절반은 “우리는 이걸 좋다고 부르기로 했다”를 문서로 못 박는 일이다. 그리고 후자가 더 어렵다.
당장 해볼 것 하나만 고르라면 첫 번째다. 지표를 설계하기 전에, 출력 100개를 직접 읽어보기. 도구도 예산도 필요 없고 하루면 된다.
원문: Eval-Driven Development: Lessons from Evaluating GenAI at Scale (Airbnb Engineering)