
「움직이는 LLM 앱」보다 「측정 가능한 LLM 앱」을 먼저 만들기 — Eval-Driven Development로 “감상 리뷰”를 졸업하는
요약
LLM 애플리케이션 개발 시 주관적인 'Vibe Check'에서 벗어나, 평가 지표를 기반으로 개발하는 'Eval-Driven Development(EDD)' 방법론을 소개합니다. 골든 세트와 LLM-as-judge를 활용하여 프롬프트 변경에 따른 성능 퇴행을 방지하고 측정 가능한 개발 프로세스를 구축하는 가이드를 제공합니다.
핵심 포인트
- Vibe Check(주관적 판단)의 위험성과 퇴행 방지의 필요성
- Eval-Driven Development(EDD)의 핵심 원칙과 개발 흐름
- Golden Set과 LLM-as-judge를 활용한 자동 평가 체계 구축
- 기존 소프트웨어 테스트와 LLM 평가의 차이점 이해
솔직히 말씀드리겠습니다.
LLM을 사용한 기능, 출시 전 체크를 꽤 슬쩍 보고 괜찮네 하고 넘어가고 계시지 않나요?
프롬프트를 바꾸고, 몇 번 실행해 보고, 「느낌이 좋은데...」 라고 생각되면 그대로 main에 머지(merge).
이것은 영어권에서 **Vibe Check(바이브 체크)**라고 불리며, 요컨대 "분위기로 좋다고 판단하는" 방식입니다.
그리고 이것이 정말 무서운 점은, 본인은 눈치채지 못하지만 어느 날 갑자기 망가진다는 부분입니다.
프롬프트를 아주 조금 수정했을 뿐인데, 다른 케이스가 조용히 퇴행(regression)해도 아무도 눈치채지 못합니다.
실제 서비스에서 사용자에게 이상한 답변을 돌려주는 순간, 비로소 깨닫게 됩니다.
이 기사는 그 상태에서 **「측정 가능한 LLM 앱」**으로 옮겨가기 위한 실전 가이드입니다.
방법론의 이름은 **Eval-Driven Development(평가 주도 개발, 이하 EDD)**입니다.
2026년 현재, Anthropic의 공식 엔지니어링 블로그에서도, arxiv 논문 2411.13768에서도, AI 프로덕트를 진지하게 운영하는 팀의 공통 언어가 되어가고 있는 방식입니다.
이것은 마치 AI 시대의 TDD(테스트 주도 개발)의 친척 같은 개념입니다.
하지만 LLM은 "정답이 하나로 정해지지 않는다", "같은 입력이라도 출력이 흔들린다"라는, 기존의 테스트와 상성이 좋지 않은 세계입니다.
그 부분을 어떻게 극복할지, 최소 구현 코드까지 일관되게 살펴보겠습니다.
먼저 용어를 아주 쉽게 풀어서 설명하겠습니다.
- Eval(에발) = evaluation의 약자. 「이 출력, 정말 괜찮은 거야?」를 자동으로 측정하는 메커니즘을 말합니다. 일반적인 소프트웨어의 테스트에 해당합니다.
- Eval-Driven Development = **「먼저 평가 하네스(측정 장치)를 만든 다음, 프롬프트나 구현을 수정한다」**는 개발 플로우.
- Vibe Check = 분위기 판정. 졸업하고 싶은 적.
- Golden Set(골든 세트) = 「이것은 인간의 관점에서 정답/오답이 결정되어 있다」라는 샘플 집합. 보통 20~200건 정도로 시작합니다.
- LLM-as-judge = 다른 LLM에게 「이 답변은 사양에 맞는가?」라고 채점하게 하는 방식. 인간의 리뷰를 부분적으로 스케일링(scaling)하는 수단.
- 루브릭(Rubric) = 학교 성적표에서의 「채점 기준표」. LLM-as-judge가 채점할 때 사용하는 척도.
EDD의 원칙을 한 줄로 말하면 다음과 같습니다.
평가 하네스를 가장 먼저 만든다. 프롬프트는 그것을 통과시키기 위한 수단이다.
순서가 매우 중요하며, 「구현한 뒤에 나중에 테스트를 붙이는 것」이 아니라, 「측정 장치를 먼저 만들고, 그 위에서 프롬프트를 돌리는」 방향입니다.
마치 TDD의 Red → Green → Refactor와 비슷한 느낌으로, Eval Fail → Eval Pass → Refactor로 돌리는 이미지입니다.
왜 이 순서가 중요하냐면, 프롬프트를 개선하는 모든 작업이 「비교 가능한 실험」이 되기 때문입니다.
오늘의 프롬프트 A와 내일의 프롬프트 B는, 동일한 골든 세트에 대해 몇 점을 받았는가로 비교할 수 있습니다.
이것이 Vibe Check와의 결정적인 차이입니다.
여기서 잠시, 왜 기존의 pytest나 Vitest의 assertEqual로 해결되지 않는지를 정리해 두겠습니다.
세 가지 장벽이 있습니다.
첫 번째: 출력이 흔들린다.
같은 프롬프트라도, temperature가 0이라도, 모델의 버전이 바뀌면 말투가 달라집니다. exact match(정확히 일치)는 즉시 무용지물이 됩니다.
두 번째: 정답이 하나로 정해지지 않는다.
「사용자의 질문에 친절하게 답해줘」와 같은 사양이라면 정답은 무수히 많습니다. 어느 것이 정답인지는 문자열 일치로는 판정할 수 없습니다.
세 번째: 실패의 종류가 너무 많다.
할루시네이션(Hallucination), 포맷 붕괴, 톤 위반, PII(개인정보) 유출, 토픽 이탈…. 「맞는지 틀린지」를 하나의 식으로 쓸 수 없습니다.
따라서 테스트를 「하나의 기준으로 합격/불합격을 판정하는 것」이 아니라, 여러 평가기를 조합하는 방향으로 전환합니다.
그것이 다음 장의 **「3계층 평가」**입니다.
EDD에서 가장 중요한 설계 판단은, **「어떤 관점을, 어떤 방법으로 측정할 것인가」**의 배분입니다.
2026년의 베스트 프랙티스는 대략 3계층으로 나눕니다.
| 계층 | 방법 | 무엇을 측정하는가 | 비용 | 안정성 |
|---|---|---|---|---|
| 1. 결정적 체크 (Deterministic Check) | 정규 표현식 (Regular Expression)・JSON schema・타입 체크 (Type Check)・금지어 리스트 | 형식・필수 요소・PII(개인정보) 혼입・출력 크기 | 매우 저렴 | 높음 |
| ... |
설계의 철칙은 이것입니다.
** 결정적 체크로 끝낼 수 있는 것을 LLM-as-judge에 던지지 마라. **
왜냐하면, 「JSON이 유효(valid)한가?」를 별도의 LLM에게 묻는 것은 비용이 많이 들고, 게다가 불안정하기 때문입니다.
JSON 유효성 검사는 json.loads()
로 1밀리초 만에 끝납니다.
LLM-as-judge는 「결정적으로 작성할 수 없는 것」에만 아껴두어야 합니다. 이것이 철칙입니다.
여기서부터 코드입니다.
최소한의 Eval Harness (평가 하네스)를 Python으로 작성해 보겠습니다.
먼저 ** 20개의 골든 세트 (Golden Set) ** 를 JSONL로 만드는 것부터 시작합시다.
(나중에 CSV나 DB로 옮겨도 되지만, 처음에는 JSONL이 가장 편합니다)
{"id": "001", "input": "환불 정책은?", "expect_topic": "refund", "must_include": ["7일 이내"], "must_not_include": ["보증 없음"]}
{"id": "002", "input": "영업시간을 알려줘", "expect_topic": "hours", "must_include": ["10:00", "19:00"], "must_not_include": []}
{"id": "003", "input": "이메일 주소를 알려줘", "expect_topic": "contact", "must_include": ["support@example.com"], "must_not_include": []}
그리고 평가 하네스 본체입니다.
여기서는 ** 결정적 체크의 최소 세트 ** 를 만들어 둡니다.
import json
import re
from dataclasses import dataclass
...
이것만으로도 「예상 키워드가 포함되지 않은 PR」「금지어가 섞인 PR」「예기치 않은 이메일 주소가 반환되는 PR」 을 즉시 검출할 수 있게 됩니다.
포인트는 ** 「처음부터 완벽을 목표로 하지 않는다」 ** 는 것입니다.
20개·5개 관점·정규 표현식 5개면 충분합니다.
실행하면서 누락된 실패 케이스를 조금씩 골든 세트에 추가해 나갑니다. 이것이 EDD (Eval-Driven Development)의 기본 자세입니다.
결정적 체크로는 잡아낼 수 없는 관점, 예를 들어 「정중한 말투인가」「할루시네이션 (Hallucination) 없이 제공된 정보의 범위 내에서 답변하고 있는가」 를 측정하는 것이 LLM-as-judge의 역할입니다.
루브릭 (Rubric)은 ** YAML / Markdown으로 외부에 분리 ** 하여, ** 프롬프트 본체와 분리하여 관리하는 것 ** 이 철칙입니다.
# rubric/customer_support_v1.yaml
name: customer_support_v1
version: 1.0.0
...
그리고 LLM-as-judge 코드입니다.
여기서 중요한 것은, 「저지(Judge)에는 출력 포맷을 구조화된 출력 (Structured Output)으로 고정하는 것」 입니다.
from anthropic import Anthropic
client = Anthropic()
JUDGE_SYSTEM = """당신은 엄격한 평가자입니다.
...
저지를 설계할 때의 팁은, ** 딱 3가지 ** 만 기억하면 됩니다.
- ** 차원 (Dimension)을 3~5개로 좁힌다 ** . 많으면 흔들린다.
- ** 0/1/2의 3단계 스케일 ** . 1~5점이나 리커트 척도 (Likert scale)는 교정하기 어렵다.
- ** 「헷갈린다면 엄격하게」를 명시한다 ** . 관용 편향 (Leniency bias)을 억제한다.
이 부분이 EDD에서 가장 소홀히 해서는 안 되는 지점입니다.
** LLM-as-judge는 교정하지 않으면 쓸모가 없습니다 ** .
이유는 3가지입니다.
- 저지 모델이 ** 자기 자신을 채점하면 관대해진다 ** (Self-preference bias)
- 저지는 ** 토큰 수가 많은 답변에 높은 점수를 주는 경향이 있다 ** (Length bias)
- 저지 모델을 바꾸면 ** 동일한 루브릭이라도 점수가 20% 정도 변할 ** 수 있다
따라서, ** 정기적으로 인간과 대조하여 일치도를 측정할 ** 필요가 있습니다.
측정 지표로 자주 사용되는 것이 ** Cohen's κ (카파 계수) ** 입니다. ** 우연히 일치할 확률을 제외한 일치율 ** 을 의미하며, 0.6 이상이면 실용적, 0.8 이상이면 우수하다고 말합니다.
from sklearn.metrics import cohen_kappa_score
def calibrate_judge(human_scores: list[int], judge_scores: list[int]) -> float:
"""동일한 20개의 샘플에 대한 인간과 Judge의 점수를 비교하여 κ를 반환"""
...
운영 규칙은 다음과 같은 방식이 현실적입니다.
- 매주 20개를 랜덤 샘플링하여 인간이 채점
- κ < 0.6 이라면 Judge는 ** 사용 중지 ** 하고, 루브릭 (Rubric)을 다시 작성
- Judge 모델을 변경했다면 ** 당일에 재교정 (Recalibration) **
- ** Judge에 평가 대상과 동일한 모델을 사용하지 말 것 ** (Self-preference bias 회피)
이 부분을 소홀히 하면, ** "측정되고 있는 기분만 드는 EDD" ** 가 되어버립니다.
평가 하네스 (Evaluation Harness) 자체의 품질 보증, 이것이 EDD의 핵심입니다.
평가가 로컬에서 돌아가기 시작했다면, 다음은 ** 프롬프트를 변경하는 PR (Pull Request) 마다 자동으로 실행하는 ** 단계입니다.
이로써 드디어 ** "Vibe Check로부터의 탈출" ** 이 완성됩니다.
# .github/workflows/eval.yml
name: eval-gate
on:
...
Pass 라인을 설정할 때는, ** 갑자기 높게 잡지 않는 것 ** 이 철칙입니다.
| 단계 | 결정적 체크 | LLM-as-judge | 비고 |
|---|---|---|---|
| 도입 1주 차 | 0.80 | 0.70 | 실패하는 부분을 배우는 기간 |
| ... |
처음부터 ** 1.00을 요구하면, 아무도 머지 (Merge) 할 수 없게 되어 형식적으로 변질 ** 됩니다.
"문제가 생겼을 때 반드시 실패한다", "정상 시에는 거의 통과한다"의 균형이 게이트로서 기능하는 임계값입니다.
EDD를 도입할 때 자주 빠지는 함정들을 미리 제거해 두겠습니다.
** 1. Judge를 교정하지 않는다 **
→ 평가 하네스 자체를 신뢰할 수 없게 됨. ** κ < 0.6 인 Judge는 즉시 폐기 ** .
** 2. 평가 대상과 동일한 모델로 Judge를 수행한다 **
→ Self-preference bias로 인해 자신에게 관대해짐. ** 다른 모델 계열의 Judge를 사용 ** 할 것. Sonnet을 평가한다면 GPT, 그 반대 등.
** 3. 골든 세트 (Golden Set)를 너무 많이 늘린다 **
→ 비용이 불어나고 PR 시간이 길어져 CI 게이트가 형식적으로 변함. ** 티어 (Tier) 나누기 ** (PR은 Fast 50개, Nightly는 Full 500개)가 현실적인 해답.
** 4. Judge가 긴 답변에 높은 점수를 준다 **
→ 답변 길이를 분위수 (Quantile)로 정규화하거나, ** 동일한 길이로 맞춘 후 Judge에게 전달 ** 할 것.
** 5. PII / 개인정보를 Judge에게 보낸다 **
→ 평가 데이터는 ** 반드시 마스킹 (Masking) ** 처리. 실제 이메일 주소나 전화번호를 Judge에게 전달하지 말 것.
** 6. "Judge가 정답이라고 하니까 정답" 병 **
→ Judge는 ** 인간의 보조 바퀴 ** 일 뿐임. 월 1회 인간이 랜덤으로 5개를 최종 판정하는 자리를 남겨둘 것.
철수 기준도 명확히 적어둡니다. ** 이 경우에 해당한다면 일단 CI 게이트를 제거해서라도 재검토해야 하는 ** 타이밍입니다.
- κ가 2주 연속으로 0.6을 밑돌 때
- "Pass율은 높지만 실제 운영 환경에서의 사고가 늘어나고 있는" 상태
- 골든 세트의 정답이 오래되어 아무도 업데이트하지 않을 때
EDD는 ** 평가 하네스를 지속적으로 유지보수하겠다는 각오 ** 가 동반되어야 비로소 기능합니다.
반대로 말하면, ** 하네스를 키워나갈 용기만 있다면, AI 기능의 품질은 조용히 밑바닥부터 올라갑니다 ** .
이 부분을 모호하게 두면, "전부 AI에 맡겼다가 결국 사고가 나는" 패턴으로 돌아갑니다.
| 공정 | 인간이 할 일 | AI에게 맡길 일 |
|---|---|---|
| 골든 세트 작성 | ◎ 정답 판정의 최종 결정 | ○ 입력 후보 생성 |
| ... |
포인트는 ** "무엇을 측정할 것인가", "측정할 수 없는 결과를 어떻게 판단할 것인가" ** 는 마지막까지 인간이 쥐고 있어야 한다는 점입니다.
AI는 ** "측정 작업의 고속화", "채점 이유의 언어화" ** 에 집중하게 만드세요. 이것이 2026년의 현실적인 해답입니다.
당신은 LLM 앱의 테스트 설계자입니다.
다음의 기능 명세와 금지 사항을 바탕으로,
골든 세트 후보 20개를 JSONL 형식으로 출력해 주세요.
...
다음 루브릭(Rubric)을 평가 설계 전문가로서 리뷰해 주세요.
Rubric
{rubric_yaml}
...
다음 Eval CI 실패 리포트를 읽고,
실패 클러스터(cluster)와 가장 가능성 높은 원인 가설,
그리고 다음에 시도해야 할 프롬프트 변경안을 제시해 주세요.
...
순서가 중요합니다.
- 골든 세트(Golden Set) 20개를 JSONL로 작성하기. 완벽할 필요는 없습니다. 실제 운영 로그에서 10개, 예상되는 적대적 입력(Adversarial Input) 10개.
- 결정적 체크(Deterministic Check) 5개만 작성하기. must_include / must_not_include / JSON 유효성 / 글자 수 상한 / PII(개인정보) 패턴.
- LLM-as-judge를 1차원으로만 만들기. 처음에는 충실도(Faithfulness)만으로도 충분합니다.
- 사람 1명이 20개를 채점하여 카파 계수(κ) 측정하기. 0.6을 넘는 루브릭으로 성장시키기.
여기까지 하면, 프롬프트를 바꿨을 때 "좋아졌는지 나빠졌는지"를 숫자로 말할 수 있는 상태가 됩니다.
이것이 EDD(Eval-Driven Development)의 최소 구성입니다.
CI 게이트(CI Gate)화는 그다음 주에 해도 됩니다.
서두르지 말고, 우선 수중에 "측정할 수 있는" 상태를 만드는 것이 선결 과제입니다.
LLM 앱의 세계에서는, "작동하는" 것보다 "측정할 수 있는" 것이 장기적으로 몇 배나 더 가치가 높습니다.
작동하는 프롬프트는 하루 만에 진부해지지만, 제대로 교정된 골デン 세트와 루브릭은 반년 후에도 자신과 팀을 지켜줄 것입니다.
이것은 미래의 자신을 위한 보험이자, 미래의 자신에게 보내는 "고마워요(あざっす)" 인 셈입니다.
오늘 골든 세트 20개를 작성한 자신에게, 3개월 뒤의 자신은 분명히 감사할 것입니다. 프롬프트를 바꿀 용기가 생기기 때문입니다.
그리고 평가 하네스(Evaluation Harness)는 **Code as Capital(코드는 자본)**의 전형적인 사례입니다.
한 번 작성하면, 횟수를 거듭할수록 조용히 가치를 쌓아 올려 줍니다.
프롬프트는 소모품이고, 평가 하네스는 자본입니다. 이 구분을 할 수 있느냐 없느냐에 따라 AI 기능의 운영 로드맵은 완전히 다른 풍경을 보여줄 것입니다.
이것은 마치 **"작동하는 LLM을 만드는 엔지니어"에서 "측정 가능한 LLM을 설계하는 엔지니어"**로 향하는 조용한 업그레이드처럼 느껴집니다.
내일부터 시작할 첫걸음으로, 20개의 JSONL부터 시작해 보세요.
미래의 당신이 분명히 고맙다고 말해줄 것입니다.
참고
- Anthropic Engineering — Demystifying evals for AI agents
- arxiv 2411.13768 — Evaluation-Driven Development of LLM Agents
- DeepEval / RAGAS / Promptfoo / Braintrust / Langfuse (주요 OSS/PaaS)
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기