LLM & Generative AI/RAG in Practice

[LangChain RAG 구축 시리즈 Ep.22] 📊 RAG 성능 평가 지표와 테스트 전략 설계하기

ygtoken 2025. 4. 5. 22:30
728x90

 

RAG 시스템은 단순한 문서 검색이나 GPT 응답 생성 기능만으로는 충분하지 않습니다.
실제 사용자에게 응답을 제공하기 전에, 그 응답이 과연 정확한지, 신뢰할 수 있는지
정량적으로 평가할 수 있는 테스트 체계가 반드시 필요합니다.

그래서 이 글에서는
👉 LangChain 기반 Retrieval QA 체인을 활용한 자동화 테스트 스크립트를 구현합니다.

  • 📄 질문-정답 쌍을 담은 JSON 파일을 읽고
  • 🤖 Retrieval QA 체인을 통해 GPT 응답을 생성하고
  • 🎯 기대 정답과 비교하여 **정확도(Exact Match)**와 F1 점수를 계산합니다

이러한 테스트 루틴은
🔍 RAG 시스템이 업데이트되었을 때 품질이 유지되는지,
🧪 모델 변경 또는 컬렉션 추가 시 성능 회귀 테스트에 활용 가능한 기반 도구가 됩니다.


🎯 목표

  • RAG QA 체인을 통해 자동으로 질문에 답변
  • 기대 정답과 생성된 응답을 비교하여 품질 측정
  • 정확도(Exact Match), F1 점수 기반으로 정량적 평가 수행

🧾 Step 1. 테스트 샘플 (JSON 형식)

[
  {
    "question": "상품 테이블의 파티션 기준은 뭐야?",
    "expected_answer": "category 컬럼 기준으로 파티셔닝 되어 있습니다."
  },
  {
    "question": "고객 테이블의 컬럼 목록은?",
    "expected_answer": "고객 테이블은 customer_id, name, region 컬럼으로 구성되어 있습니다."
  }
]
  • question: 실제 유저가 던질 수 있는 질문
  • expected_answer: 모델이 생성해야 할 이상적인 답변 (정답)

⚙️ Step 2. 성능 평가 스크립트

# src/eval_rag.py

import json                               # 테스트 샘플(JSON) 파일을 읽기 위한 표준 라이브러리
import re                                 # 텍스트 정규화 및 토큰 분리를 위한 정규표현식 처리
from langchain.vectorstores import Chroma             # 벡터 검색용 ChromaDB
from langchain.embeddings import OpenAIEmbeddings     # 문서 임베딩 처리용 OpenAI 모델
from langchain.chat_models import ChatOpenAI          # GPT-3.5 기반 LLM 모델
from langchain.chains import RetrievalQA              # 문서 검색 + 응답 생성을 위한 QA 체인

def normalize(text: str) -> list[str]:
    """
    답변과 정답을 비교하기 위해,
    텍스트를 소문자로 바꾸고 특수문자를 제거하여 단어 리스트로 변환합니다.
    이 처리는 F1 점수를 계산하기 위한 사전 작업입니다.
    """
    text = text.lower()                                # 모두 소문자로 변환
    text = re.sub(r"[^a-z0-9가-힣 ]+", "", text)        # 특수문자 제거 (한글, 영문, 숫자, 공백만 남김)
    return text.strip().split()                         # 단어 단위로 분리하여 리스트 반환

def run_eval(questions_path: str, chroma_dir: str, collection: str):
    """
    주어진 질문-정답 쌍을 기반으로 RAG 시스템의 응답 품질을 자동으로 평가합니다.
    출력 지표: 정확도(정답 일치율), 평균 F1 점수
    """

    # ✅ 1. JSON 파일로부터 테스트 샘플 로딩
    with open(questions_path, "r", encoding="utf-8") as f:
        samples = json.load(f)

    # ✅ 2. QA 체인 구성
    vectordb = Chroma(
        persist_directory=chroma_dir,                   # 벡터 DB 저장 경로
        embedding_function=OpenAIEmbeddings(),          # 동일한 임베딩 모델 사용
        collection_name=collection                      # 평가할 컬렉션 이름
    )

    retriever = vectordb.as_retriever(search_kwargs={"k": 2})  # 상위 2개 문서 검색 설정

    llm = ChatOpenAI(
        model_name="gpt-3.5-turbo",                     # 평가용 LLM 모델
        temperature=0                                   # 변동성 최소화 → 일관된 결과 확보
    )

    qa = RetrievalQA.from_chain_type(
        llm=llm,                                        # 응답 생성용 LLM 연결
        retriever=retriever                             # 검색기 연결
    )

    # ✅ 3. 평가 지표 초기화
    total = len(samples)            # 전체 질문 개수
    exact_match = 0                 # 정답과 정확히 일치한 응답 수
    f1_total = 0                    # F1 점수 합계

    # ✅ 4. 각 질문-정답 쌍에 대해 평가 수행
    for i, sample in enumerate(samples):
        question = sample["question"]
        expected = sample["expected_answer"]

        result = qa.run(question)                      # QA 체인을 통해 GPT 응답 생성
        pred_tokens = normalize(result)                # 생성된 응답 전처리
        true_tokens = normalize(expected)              # 정답 전처리

        # 정확도 체크: 텍스트가 완전히 일치할 경우
        if result.strip() == expected.strip():
            exact_match += 1

        # F1 점수 계산: 정답 단어와 예측 단어의 교집합 기준
        common = set(pred_tokens) & set(true_tokens)
        if common:
            precision = len(common) / len(pred_tokens)
            recall = len(common) / len(true_tokens)
            if precision + recall > 0:
                f1 = 2 * (precision * recall) / (precision + recall)
            else:
                f1 = 0
        else:
            f1 = 0

        f1_total += f1

        # ✅ 개별 결과 출력
        print(f"[{i+1}] Q: {question}")
        print(f"    🔹 예상: {expected}")
        print(f"    🔸 실제: {result}")
        print(f"    🎯 F1 점수: {f1:.2f}\n")

    # ✅ 5. 전체 지표 요약 출력
    print("====== 최종 평가 결과 ======")
    print(f"✅ 정답 일치율: {(exact_match / total) * 100:.2f}%")
    print(f"✅ 평균 F1 점수: {(f1_total / total) * 100:.2f}%")

▶️ 실행 예시

python src/eval_rag.py
[1] Q: 상품 테이블의 파티션 기준은 뭐야?
    🔹 예상: category 컬럼 기준으로 파티셔닝 되어 있습니다.
    🔸 실제: category 컬럼 기준으로 파티셔닝 되어 있습니다.
    🎯 F1 점수: 1.00

[2] Q: 고객 테이블의 컬럼 목록은?
    🔹 예상: 고객 테이블은 customer_id, name, region 컬럼으로 구성되어 있습니다.
    🔸 실제: 고객 테이블은 customer_id, name, region 컬럼으로 구성되어 있습니다.
    🎯 F1 점수: 1.00

====== 최종 평가 결과 ======
✅ 정답 일치율: 100.00%
✅ 평균 F1 점수: 100.00%

📎 요약 및 핵심 정리

  • RAG 시스템의 응답 품질을 정량적으로 평가할 수 있는 자동 테스트 루틴을 구성했습니다.
  • 정확도(Exact Match)는 완전 일치를 기준으로, F1 점수는 단어 유사도 기준으로 측정됩니다.
  • 이 구조는 모델 변경 시 회귀 테스트, 성능 비교, 컬렉션 추가 전 검증 등에 적극 활용될 수 있습니다.

 

728x90