정확 일치(Exact Match)와 F1은 의미가 같은 답을 놓칠 수 있습니다. 사람이 직접 검수하는 방식은 규모를 키우기 어렵습니다. 실무에서는 LLM-as-judge가 답이 될 수 있지만, 그 숫자를 신뢰하려면 보정(calibration)이 함께 필요합니다.
유형: Build
언어: Python
선수 강의: Phase 5 · 13 (Question Answering), Phase 5 · 14 (Information Retrieval)
예상 시간: 약 75분
학습 목표
RAG 평가에서 정확 일치(Exact Match), F1 같은 정적 지표가 놓치는 문제를 설명합니다.
RAGAS의 네 가지 핵심 지표(metric)를 구분합니다.
DeepEval과 G-Eval로 LLM 평가를 CI에 연결하는 흐름을 이해합니다.
심판 모델(judge model)을 보정하고 버전을 고정해야 하는 이유를 설명합니다.
문제
여러분의 RAG 시스템이 "June 29th, 2007."라고 답했습니다.
정답 참조(reference)는 "June 29, 2007."입니다.
정확 일치(Exact Match)는 0점, F1은 약 75%를 줍니다. 사람이라면 100점을 줄 가능성이 큽니다.
이제 이 상황을 10,000개의 테스트 케이스(test case)로 늘려봅니다. 검색기(retriever), 청크 분할(chunking), 프롬프트(prompt), 모델을 바꿀 때마다 다시 평가해야 한다고 생각해봅니다. 의미를 이해하고, 큰 규모에서 저렴하게 돌릴 수 있으며, 회귀(regression)를 속이지 않고, 어떤 실패 양상이 발생했는지 드러내는 평가기(evaluator)가 필요합니다.
2026년 기준으로 이 문제를 다루는 대표 프레임워크(framework)는 세 가지입니다.
RAGAS. 검색 증강 생성 평가(Retrieval-Augmented Generation ASsessment; RAGAS)입니다. 충실도(faithfulness), 답변 관련성(answer relevance), 문맥 정밀도(context precision), 문맥 재현율(context recall)이라는 네 가지 RAG 지표를 제공하며, 자연어 추론(Natural Language Inference; NLI)과 LLM 심판(judge) 백엔드(backend)를 사용합니다. 연구 기반이고 가볍습니다.
DeepEval. LLM을 위한 pytest에 가깝습니다. G-Eval, 과제 완료(task completion), 환각(hallucination), 편향(bias) 지표를 제공하며 CI/CD와 함께 쓰기 좋습니다.
G-Eval. 평가 방법론이자 DeepEval의 한 지표입니다. LLM-as-judge에 사고 사슬(chain-of-thought), 사용자 정의 기준(custom criteria), 0-1 점수를 결합합니다.
세 가지 모두 LLM-as-judge에 기대고 있습니다. 이 강의에서는 그 방법의 직관과, 그 숫자를 신뢰하기 위한 신뢰 계층(trust layer)을 함께 만듭니다.
사전 테스트
2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요
1.RAG 시스템이 'June 29th, 2007.'이라고 답했는데 참조(reference)는 'June 29, 2007.'입니다. 정확 일치(Exact Match)는 0점, F1은 약 75%를 줍니다. 정적 지표(static metric)가 왜 실패하나요?
2.LLM-as-judge 평가의 핵심 아이디어는 무엇인가요?
0/2 답변 완료
개념
LLM-as-judge. 고정된 지표 대신 LLM이 평가 기준(rubric)에 따라 출력(output)을 채점하게 하는 방식입니다. (query, context, answer)가 주어지면 심판 LLM에 "충실도를 0-1로 점수화하라"는 프롬프트를 주고 점수(score)를 받습니다.
이 방식이 작동하는 이유는 LLM이 사람의 판단에 가까운 의미 평가를 훨씬 낮은 비용으로 근사하기 때문입니다. 예를 들어 저렴한 심판 모델을 쓰면 1,000개 회귀 평가를 몇 달러 수준에서 돌릴 수 있습니다.
하지만 조용히 실패할 수 있는 이유도 있습니다.
심판 편향(judge bias). 심판은 더 긴 답변, 같은 모델 계열(model family)이 만든 답변, 프롬프트 스타일과 잘 맞는 답변을 선호할 수 있습니다.
JSON 파싱 실패(JSON parsing failure). 잘못된 JSON은 NaN 점수가 되어 집계(aggregate)에서 조용히 빠질 수 있습니다. RAGAS 사용자들이 자주 겪는 문제입니다. try/except와 명시적 실패 모드(failure mode)로 막아야 합니다.
모델 버전 드리프트(model version drift). 심판 모델을 업그레이드하면 모든 지표가 바뀔 수 있습니다. 심판 모델과 버전을 고정해야 합니다.
RAG의 네 가지 지표.
지표
질문
백엔드
충실도(Faithfulness)
답변의 각 주장(claim)이 검색된 문맥에서 나온 것인가?
NLI 기반 함의(entailment)
답변 관련성(Answer relevance)
답변이 질문에 답하고 있는가?
답변에서 가상 질문을 만들고 실제 질문과 비교
문맥 정밀도(Context precision)
검색된 청크 중 관련 있는 청크의 비율은 얼마인가?
LLM 심판
문맥 재현율(Context recall)
필요한 정보를 검색이 모두 가져왔는가?
정답(gold answer) 기준 LLM 심판
G-Eval. "답변이 올바른 출처(source)를 인용했는가?" 같은 사용자 정의 기준을 정의합니다. 프레임워크가 이를 사고 사슬 기반 평가 단계(evaluation step)로 확장하고 0-1 점수를 반환합니다. RAGAS가 기본 제공하지 않는 도메인 특화 품질 차원(domain-specific quality dimension)을 평가할 때 좋습니다.
보정(Calibration). 사람이 부여한 라벨(human label)과의 상관관계를 보기 전에는 원시 심판 점수(raw judge score)를 믿지 않습니다. 사람이 라벨링한 100개 예제를 준비하고, 심판 점수와 사람 점수를 비교합니다. 스피어만 상관계수(Spearman rho)를 계산합니다. rho가 0.7보다 낮다면 심판의 평가 기준을 다시 작성해야 합니다.
직접 만들기
Step 1: NLI 기반 충실도 (RAGAS 방식)
from typing importCallablefrom transformers import pipeline
nli = pipeline("text-classification",
model="MoritzLaurer/DeBERTa-v3-large-mnli-fever-anli-ling-wanli",
top_k=None)
# `llm`은 prompt str -> generated str 형태의 callable입니다.# 예: llm = lambda p: client.messages.create(model="claude-haiku-4-5", ...).content[0].text
LLM = Callable[[str], str]
defatomic_claims(answer: str, llm: LLM) -> list[str]:
prompt = f"""Break this answer into simple factual claims (one per line):
{answer}
"""return llm(prompt).splitlines()
deffaithfulness(answer: str, context: str, llm: LLM) -> float:
claims = atomic_claims(answer, llm)
ifnot claims:
return0.0
supported = 0for claim in claims:
result = nli({"text": context, "text_pair": claim})[0]
entail = next((s for s in result if s["label"] == "entailment"), None)
if entail and entail["score"] > 0.5:
supported += 1return supported / len(claims)
답변을 원자 단위 주장(atomic claim)으로 나눕니다. 각 주장을 검색된 문맥에 대해 NLI로 검사합니다. 충실도는 뒷받침된 주장의 비율입니다.
Step 2: 답변 관련성
import numpy as np
from sentence_transformers import SentenceTransformer
# encoder: .encode(texts, normalize_embeddings=True) -> ndarray를 구현한 model입니다.# 예: encoder = SentenceTransformer("BAAI/bge-small-en-v1.5")defanswer_relevance(question: str, answer: str, encoder, llm: LLM, n: int = 3) -> float:
prompt = f"Write {n} questions this answer could be the answer to:\n{answer}"
generated = [line for line in llm(prompt).splitlines() if line.strip()][:n]
ifnot generated:
return0.0
q_emb = np.asarray(encoder.encode([question], normalize_embeddings=True)[0])
g_embs = np.asarray(encoder.encode(generated, normalize_embeddings=True))
sims = [float(q_emb @ g_emb) for g_emb in g_embs]
returnsum(sims) / len(sims)
답변이 암시하는 질문이 실제 질문과 다르면 관련성이 떨어집니다.
Step 3: G-Eval 사용자 정의 지표
from deepeval.metrics import GEval
from deepeval.test_case import LLMTestCaseParams, LLMTestCase
metric = GEval(
name="Correctness",
criteria="The answer should be factually accurate and match the expected output.",
evaluation_steps=[
"Read the expected output.",
"Read the actual output.",
"List factual claims in the actual output.",
"For each claim, mark supported or unsupported by the expected output.",
"Return score = fraction supported.",
],
evaluation_params=[LLMTestCaseParams.INPUT, LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.EXPECTED_OUTPUT],
)
test = LLMTestCase(input="When was the first iPhone released?",
actual_output="June 29th, 2007.",
expected_output="June 29, 2007.")
metric.measure(test)
print(metric.score, metric.reason)
평가 단계 자체가 평가 기준입니다. 단순히 "0-1로 평가하라"는 프롬프트보다 명시적인 단계가 더 안정적입니다.
pytest 파일로 배포합니다. 모든 PR에서 실행하고 회귀가 발생하면 병합(merge)을 막습니다.
Step 5: 처음부터 만드는 장난감 평가(toy eval)
code/main.py를 확인합니다. 충실도는 답변 주장과 문맥의 어휘 중첩(lexical overlap)으로, 관련성은 답변 토큰과 질문 토큰의 중첩으로 근사합니다. 운영 환경(production)용은 아니지만 평가 루프(evaluation loop)의 형태를 보여줍니다.
흔한 함정
보정 없음. 사람 라벨과 상관계수 0.3인 심판은 잡음(noise)입니다. 배포 전에 보정 실행이 반드시 필요합니다.
자기 평가(self-evaluation). 생성에 사용한 LLM과 같은 LLM을 심판으로 쓰면 점수가 10-20% 부풀 수 있습니다. 심판은 다른 모델 계열을 사용합니다.
쌍대 비교(pairwise judging)의 위치 편향(positional bias). 심판이 먼저 제시된 선택지를 선호할 수 있습니다. 순서를 무작위화하고 양방향으로 돌립니다.
원시 집계가 실패를 숨김. 평균 점수 0.85는 5%의 치명적 실패(catastrophic failure)를 숨길 수 있습니다. 항상 하위 분위(bottom quantile)를 확인합니다.
정답 데이터셋의 노후화(golden dataset rot). 버전 관리(versioning)되지 않은 평가 세트가 시간이 지나며 바뀌면 장기 비교가 깨집니다. 데이터셋 변경마다 태그(tag)를 남깁니다.
LLM 비용. 규모가 커지면 심판 호출이 비용을 지배합니다. 보정 임계값(calibration threshold)을 만족하는 가장 저렴한 모델을 사용합니다. GPT-4o-mini, Claude Haiku, Mistral-small 등이 후보입니다.
사용하기
2026년 스택은 다음과 같습니다.
사용 사례(Use case)
프레임워크
RAG 품질 모니터링
RAGAS (4개 지표)
CI/CD 회귀 게이트
DeepEval + pytest
도메인 특화 사용자 정의 기준
DeepEval 안의 G-Eval
실시간 트래픽 온라인 모니터링
참조 없는 모드(reference-free mode)의 RAGAS
사람이 개입하는(human-in-the-loop) 표본 점검
LangSmith 또는 Phoenix의 주석 UI
레드팀 / 안전성 평가
Promptfoo + DeepEval
일반적인 조합은 모니터링에는 RAGAS, CI에는 DeepEval, 새로운 품질 차원에는 G-Eval입니다. 세 가지를 함께 돌리면 서로 다른 방식으로 유용하게 어긋난 결과를 보여줍니다.
산출물 만들기
outputs/skill-eval-architect.md로 저장합니다.
---
name: eval-architect
description: Design an LLM evaluation plan with calibrated judge and CI gates.
version: 1.0.0
phase: 5
lesson: 27
tags: [nlp, evaluation, rag]
---
Given a use case (RAG / agent / generative task), output:
1. Metrics. Faithfulness / relevance / context-precision / context-recall + any custom G-Eval metrics with criteria.
2. Judge model. Named model + version, rationale for cost vs accuracy.
3. Calibration. Hand-labeled set size, target Spearman rho vs human > 0.7.
4. Dataset versioning. Tag strategy, change log, stratification.
5. CI gate. Thresholds per metric, regression-window logic, bottom-quantile alert.
Guide the student in Korean.
Refuse to rely on a judge untested against >=50 human-labeled examples. Refuse self-evaluation (same model generates + judges). Refuse aggregate-only reporting without bottom-10% surfacing. Flag any pipeline where judge upgrade lands without parallel baseline eval.
연습문제
쉬움. 알려진 환각(known hallucination)이 있는 RAG 예제 10개에 RAGAS를 사용합니다. 충실도 지표가 각 환각을 잡아내는지 확인합니다.
중간. QA 답변 50개를 정답성(correctness) 기준으로 0-1로 직접 라벨링(hand-labeling)합니다. G-Eval로 점수를 만들고, 심판과 사람 사이의 스피어만 상관계수를 측정합니다.
어려움. DeepEval로 pytest CI 게이트를 만듭니다. 검색기를 의도적으로 회귀시키고 게이트가 실패하는지 확인합니다. 하위 10%에 대한 임계값 검사로 하위 분위 알림(bottom-quantile alerting)을 추가합니다.