Skip to content
Published on

RAGAS 완전 가이드: RAG 시스템을 어떻게 정량적으로 평가하는가

Authors

RAG 평가가 어려운 이유

"잘 작동하는 것 같은데, 실제로 얼마나 좋은지 모르겠어요."

RAG를 운영하는 팀에서 가장 많이 듣는 말이다. 개발하다 보면 직관적으로 "좋아진 것 같다"는 느낌은 있는데, 정확히 얼마나 좋아졌는지를 수치로 보여주기가 어렵다.

전통적인 소프트웨어처럼 테스트 케이스를 짜기도 어렵다. 정답이 하나가 아니고, 같은 질문에도 맥락에 따라 좋은 답변이 달라진다.

**RAGAS(RAG Assessment)**는 이 문제를 해결하기 위해 설계된 프레임워크다. LLM을 judge로 활용해서 RAG 시스템의 여러 측면을 0-1 사이 점수로 정량화한다.

이 글에서는 RAGAS의 4가지 핵심 지표를 이해하고, 실제 코드로 구현하고, 자동화된 평가 파이프라인을 구축하는 방법을 다룬다.

RAGAS가 측정하는 4가지 지표

RAGAS는 RAG 시스템을 두 가지 차원으로 평가한다: **검색(Retrieval)**의 품질과 **생성(Generation)**의 품질.

RAG 파이프라인 전체:

[사용자 질문]
[검색 단계]Context Precision, Context Recall로 평가
[생성 단계]Faithfulness, Answer Relevancy로 평가
[최종 답변]

지표 1: Faithfulness (충실성)

질문: 생성된 답변이 검색된 컨텍스트에 기반하는가?

할루시네이션 탐지 지표다. 모델이 컨텍스트에 없는 내용을 "만들어내면" Faithfulness가 낮아진다.

계산 방법:

  1. 생성된 답변에서 개별 claim(주장)을 추출
  2. 각 claim이 검색된 컨텍스트에서 지지되는지 LLM이 판단
  3. Faithfulness = 지지되는 claim 수 / 전체 claim 수

예시:

질문: "제품의 배터리 수명은 얼마인가요?"
컨텍스트: "배터리는 최대 12시간 지속됩니다. 충전 시간은 2시간입니다."
답변: "배터리는 12시간이며, 방수 기능도 있습니다."

Claim 분석:
- "배터리는 12시간" → 컨텍스트에 지지됨 ✓
- "방수 기능도 있습니다" → 컨텍스트에 없음 ✗

Faithfulness = 1/2 = 0.5 (낮음! 할루시네이션 발생)

지표 2: Answer Relevancy (답변 관련성)

질문: 생성된 답변이 사용자의 질문에 실제로 대답하는가?

컨텍스트에 충실하지만 질문과 관련 없는 답변을 탐지한다.

계산 방법:

  1. 생성된 답변으로부터 역으로 synthetic 질문을 여러 개 생성
  2. 이 synthetic 질문들의 임베딩과 원래 질문의 임베딩 간 평균 코사인 유사도 계산
  3. Answer Relevancy = 이 유사도 점수

예시:

원래 질문: "배터리 수명이 얼마인가요?"
생성된 답변: "이 제품은 고급 재질로 만들어졌으며 다양한 색상이 있습니다."

Synthetic 질문 생성:
- "이 제품은 어떤 재질로 만들어졌나요?"
- "어떤 색상이 있나요?"

원래 질문과의 유사도: 매우 낮음
Answer Relevancy0.1 (낮음! 답변이 질문을 무시함)

지표 3: Context Precision (컨텍스트 정밀도)

질문: 검색된 컨텍스트 중 실제로 도움이 된 것이 얼마나 되는가?

검색의 "노이즈"를 측정한다. 5개 청크를 검색했는데 1개만 실제로 유용했다면 낮은 점수를 받는다.

계산 방법:

  • 상위 k개 컨텍스트 중 관련있는 것의 비율 (Precision@k의 가중 합)
  • Ground truth 답변과 비교해서 각 컨텍스트의 관련성 판단

높은 Context Precision = 검색이 정확해서 LLM이 관련 없는 정보에 혼란받지 않음

지표 4: Context Recall (컨텍스트 재현율)

질문: 답변에 필요한 정보를 검색으로 모두 가져왔는가?

빠진 정보가 있는지 측정한다. Ground truth 답변이 필요로 하는 정보를 검색이 모두 찾아왔는지 확인한다.

계산 방법:

  • Ground truth 답변의 각 문장이 검색된 컨텍스트에 의해 지지되는지 판단
  • Context Recall = 지지되는 문장 수 / 전체 Ground truth 문장 수

높은 Context Recall = 검색이 포괄적이어서 LLM이 답변에 필요한 정보를 모두 갖고 있음

실제 RAGAS 구현

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from datasets import Dataset

# 평가 데이터셋 준비
# 각 샘플: question + 시스템이 생성한 answer + retrieved contexts + ground_truth
eval_data = {
    "question": [
        "제품의 배터리 수명은 얼마인가요?",
        "환불 정책은 어떻게 되나요?",
        "배송 기간은 얼마나 걸리나요?"
    ],
    "answer": [
        "배터리는 최대 12시간 지속됩니다.",
        "구매 후 30일 이내에 환불 신청이 가능합니다.",
        "일반 배송은 3-5 영업일이 소요됩니다."
    ],
    "contexts": [
        # 각 질문에 대해 검색된 컨텍스트 청크 목록
        [
            "배터리는 최대 12시간 지속됩니다. 충전 시간은 2시간입니다.",
            "제품 크기는 15cm x 10cm x 2cm입니다."
        ],
        [
            "고객은 구매 후 30일 이내에 환불을 신청할 수 있습니다.",
            "환불은 영업일 기준 5-7일 내에 처리됩니다."
        ],
        [
            "일반 배송: 3-5 영업일, 빠른 배송: 1-2 영업일",
            "배송비는 3만원 미만 주문 시 3,000원이 부과됩니다."
        ]
    ],
    "ground_truth": [
        "배터리 수명은 최대 12시간이며, 충전 시간은 2시간입니다.",
        "환불은 구매 후 30일 이내 신청 가능하고 5-7 영업일 내 처리됩니다.",
        "일반 배송은 3-5 영업일, 빠른 배송은 1-2 영업일이 소요됩니다."
    ]
}

dataset = Dataset.from_dict(eval_data)

# 평가 실행
results = evaluate(
    dataset,
    metrics=[
        faithfulness,
        answer_relevancy,
        context_precision,
        context_recall,
    ]
)

print(results)
# 출력 예시:
# {
#   'faithfulness': 0.95,
#   'answer_relevancy': 0.88,
#   'context_precision': 0.82,
#   'context_recall': 0.91
# }

# 결과를 DataFrame으로 변환해서 상세 분석
df = results.to_pandas()
print(df.to_string())

LLM Judge 설정

RAGAS는 기본적으로 OpenAI를 judge LLM으로 사용하지만, 로컬 모델이나 다른 프로바이더로 교체 가능하다:

from ragas.llms import LangchainLLMWrapper
from langchain_openai import ChatOpenAI
from langchain_openai import OpenAIEmbeddings
from ragas.embeddings import LangchainEmbeddingsWrapper

# Judge LLM 설정 (비용 절감을 위해 GPT-4o-mini 사용 가능)
judge_llm = LangchainLLMWrapper(
    ChatOpenAI(model="gpt-4o-mini", temperature=0)
)

# 임베딩 설정 (Answer Relevancy 계산에 사용)
embeddings = LangchainEmbeddingsWrapper(OpenAIEmbeddings())

results = evaluate(
    dataset,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
    llm=judge_llm,
    embeddings=embeddings,
)

자동화 평가 파이프라인 구축 (CI/CD for RAG)

RAG 시스템의 변경이 있을 때마다 자동으로 평가가 실행되도록 하는 파이프라인이다.

# evaluate_rag.py - CI/CD에서 실행되는 평가 스크립트

import json
import sys
from datetime import datetime
from pathlib import Path
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset


def load_test_dataset(path: str) -> Dataset:
    """저장된 테스트 데이터셋 로드"""
    with open(path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    return Dataset.from_dict(data)


def run_rag_on_testset(testset: Dataset, rag_chain) -> Dataset:
    """테스트셋의 각 질문을 RAG로 실행해서 answer와 contexts 수집"""
    answers = []
    contexts_list = []

    for question in testset["question"]:
        # RAG 실행
        result = rag_chain.invoke({"query": question})
        answers.append(result["result"])
        contexts_list.append([
            doc.page_content
            for doc in result["source_documents"]
        ])

    return testset.add_column("answer", answers).add_column("contexts", contexts_list)


def evaluate_and_gate(
    dataset: Dataset,
    thresholds: dict,
    save_path: str = None
) -> bool:
    """
    평가 실행 후 임계값 기반 통과/실패 판정.

    thresholds 예시:
    {
        "faithfulness": 0.8,
        "answer_relevancy": 0.75,
        "context_precision": 0.7,
        "context_recall": 0.75
    }
    """
    results = evaluate(
        dataset,
        metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
    )

    print("\n" + "="*50)
    print(f"RAG 평가 결과 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("="*50)

    all_passed = True
    for metric_name, threshold in thresholds.items():
        score = results[metric_name]
        passed = score >= threshold
        status = "PASS" if passed else "FAIL"
        print(f"{metric_name:25s}: {score:.3f} (임계값: {threshold}) [{status}]")
        if not passed:
            all_passed = False

    # 결과 저장 (히스토리 추적용)
    if save_path:
        result_data = {
            "timestamp": datetime.now().isoformat(),
            "scores": {k: float(v) for k, v in results.items()},
            "passed": all_passed
        }
        Path(save_path).parent.mkdir(parents=True, exist_ok=True)
        with open(save_path, 'a') as f:
            f.write(json.dumps(result_data) + "\n")

    return all_passed


# 실행
if __name__ == "__main__":
    # 테스트셋과 RAG 체인 로드
    testset = load_test_dataset("tests/rag_testset.json")
    # rag_chain = load_your_rag_chain()  # 실제 RAG 체인 로드

    # testset = run_rag_on_testset(testset, rag_chain)

    thresholds = {
        "faithfulness": 0.80,
        "answer_relevancy": 0.75,
        "context_precision": 0.70,
        "context_recall": 0.75,
    }

    passed = evaluate_and_gate(
        testset,
        thresholds=thresholds,
        save_path="results/rag_eval_history.jsonl"
    )

    sys.exit(0 if passed else 1)  # CI/CD에서 실패 시 빌드 중단

GitHub Actions에서 이렇게 사용한다:

# .github/workflows/rag-evaluation.yml
name: RAG Quality Gate

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  evaluate-rag:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: pip install ragas langchain openai datasets
      - name: Run RAG Evaluation
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: python evaluate_rag.py

평가 데이터셋 자동 생성

테스트셋을 수동으로 만드는 건 시간이 많이 걸린다. RAGAS의 TestsetGenerator로 자동화할 수 있다.

from ragas.testset.generator import TestsetGenerator
from ragas.testset.evolutions import simple, reasoning, multi_context
from langchain_community.document_loaders import DirectoryLoader
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

# 문서 로드
loader = DirectoryLoader("./docs", glob="**/*.md")
documents = loader.load()

# 테스트셋 생성기 초기화
generator = TestsetGenerator.with_openai()

# 테스트셋 생성 (질문 유형 지정 가능)
testset = generator.generate_with_langchain_docs(
    documents,
    test_size=50,  # 총 50개 질문
    distributions={
        simple: 0.5,        # 단순 사실 질문 50%
        reasoning: 0.25,    # 추론 필요 질문 25%
        multi_context: 0.25 # 여러 문서 참조 필요 25%
    }
)

# 데이터셋을 파일로 저장
testset_df = testset.to_pandas()
testset_df.to_json("tests/rag_testset.json", orient='records', force_ascii=False)

print(f"생성된 질문 수: {len(testset_df)}")
print("\n샘플 질문:")
print(testset_df[['question', 'ground_truth']].head())

생성되는 질문 유형 예시:

  • Simple: "반품 기간은 며칠인가요?" (단순 사실 검색)
  • Reasoning: "A 제품과 B 제품 중 어떤 것이 더 오래 쓸 수 있나요?" (비교 추론)
  • Multi-context: "최신 제품 라인업에서 배터리 용량이 가장 큰 제품은?" (여러 문서 필요)

RAGAS 점수 해석 가이드

지표위험 수준수용 가능좋음우수
Faithfulness0.5 미만0.5-0.70.7-0.90.9 이상
Answer Relevancy0.6 미만0.6-0.750.75-0.90.9 이상
Context Precision0.5 미만0.5-0.70.7-0.850.85 이상
Context Recall0.6 미만0.6-0.750.75-0.90.9 이상

점수가 낮을 때 어떻게 개선하는가

Faithfulness 낮음 → 할루시네이션 발생

  • System prompt에 "컨텍스트에 없는 내용은 답변하지 말 것" 강화
  • 더 구체적인 citation 지시 추가
  • LLM을 더 conservative한 모델로 교체

Answer Relevancy 낮음 → 답변이 질문을 무시

  • Prompt에서 질문을 더 명확하게 강조
  • Retrieved context가 너무 많아서 질문을 잊는 경우 → 컨텍스트 수 줄이기
  • Re-ranking 도입

Context Precision 낮음 → 관련 없는 청크가 너무 많이 검색됨

  • Retrieval top-k 수를 줄임 (5 → 3)
  • Re-ranking 추가 (Cohere Rerank, BGE Reranker 등)
  • 청킹 크기 조정

Context Recall 낮음 → 필요한 정보를 검색에서 놓침

  • Retrieval top-k 수를 늘림
  • Hybrid Search 도입 (BM25 + 벡터)
  • 청킹 전략 재검토 (너무 큰 청크는 관련 정보를 희석시킴)

실전 평가 루프

이 모든 것을 합쳐서 지속적인 개선 사이클을 만들자:

1. 기준선 측정 (RAGAS 실행)
2. 가장 낮은 지표 확인
3. 해당 지표 개선 (청킹/검색/프롬프트 수정)
4. 다시 측정 → 개선 확인
5. CI/CDQuality Gate 추가
6. 코드 변경마다 자동 평가

마무리

RAGAS는 RAG 시스템 개발을 "느낌" 기반에서 "데이터" 기반으로 전환시켜주는 도구다. 처음 도입할 때는 점수가 생각보다 낮게 나와서 당황할 수 있다. 하지만 그게 정상이다 — 현실을 직시하는 것이 첫걸음이다.

작은 테스트셋(20-50개)으로 시작하고, CI/CD에 Quality Gate를 추가하고, 매 변경마다 점수를 추적하자. 몇 주 지나면 체계적으로 개선되는 수치를 보게 된다.

이 포스트 시리즈의 4개 글이 RAG 구축의 핵심 요소를 커버했다: 접근법 선택 → 청킹 → 검색 → 평가. 이 네 가지를 제대로 하면 프로덕션 수준의 RAG 시스템을 만들 수 있다.