RAGAS로 간단한 RAG 시스템 평가하기

RAGAS로 간단한 RAG 시스템 평가하기

RAG 시스템을 만들었다면 이제 '실제로 잘 동작하는지'를 확인해야 해요. Ragas를 쓰면 비교적 간단한 워크플로우로 RAG 파이프라인을 평가할 수 있는데요, 이 문서에서는 LangChain 기반의 아주 단순한 RAG를 만들어 질문을 던지고, 그 결과를 ragas의 평가 메트릭으로 점수화하는 과정을 따라가 볼게요. RAG 구축에 대한 최소한의 지식만 있어도 충분해요.

출처: RAGAS Evaluate a simple RAG system

기본 설정

먼저 RAG를 구성할 LLM과 임베딩 모델을 준비해요. 예시에서는 langchain_openai로 LLM을, ragas.embeddings.OpenAIEmbeddings로 임베딩을 설정해요. 다른 LLM·임베딩 모델을 쓰고 싶다면 LangChain의 모델 커스터마이징 문서를 참고하면 돼요.

from langchain_openai import ChatOpenAI
from ragas.embeddings import OpenAIEmbeddings
import openai

llm = ChatOpenAI(model="gpt-4o")
openai_client = openai.OpenAI()
embeddings = OpenAIEmbeddings(client=openai_client)

여기서 한 가지 짚고 넘어갈 점이 있어요. ragas.embeddings.OpenAIEmbeddingsembed_text(단건)와 embed_texts(배치)를 제공하고, LangChain 래퍼의 embed_query·embed_documents는 없어요. 아래 예시에서 문서용으로 embed_texts, 쿼리용으로 embed_text를 쓰는 이유가 이거예요.

간단한 RAG 시스템 만들기

RAG를 구성하려면 세 가지를 정의해야 해요 — 문서를 벡터화하는 메서드, 관련 문서를 검색하는 메서드, 응답을 생성하는 메서드. 이를 RAG 클래스로 묶어볼게요.

import numpy as np

class RAG:
    def __init__(self, model="gpt-4o"):
        import openai
        self.llm = ChatOpenAI(model=model)
        openai_client = openai.OpenAI()
        self.embeddings = OpenAIEmbeddings(client=openai_client)
        self.doc_embeddings = None
        self.docs = None

    def load_documents(self, documents):
        """Load documents and compute their embeddings."""
        self.docs = documents
        self.doc_embeddings = self.embeddings.embed_texts(documents)

    def get_most_relevant_docs(self, query):
        """Find the most relevant document for a given query."""
        if not self.docs or not self.doc_embeddings:
            raise ValueError("Documents and their embeddings are not loaded.")

        query_embedding = self.embeddings.embed_text(query)
        similarities = [
            np.dot(query_embedding, doc_emb)
            / (np.linalg.norm(query_embedding) * np.linalg.norm(doc_emb))
            for doc_emb in self.doc_embeddings
        ]
        most_relevant_doc_index = np.argmax(similarities)
        return [self.docs[most_relevant_doc_index]]

    def generate_answer(self, query, relevant_doc):
        """Generate an answer for a given query based on the most relevant document."""
        prompt = f"question: {query}\n\nDocuments: {relevant_doc}"
        messages = [
            ("system", "You are a helpful assistant that answers questions based on given documents only."),
            ("human", prompt),
        ]
        ai_msg = self.llm.invoke(messages)
        return ai_msg.content

get_most_relevant_docs는 쿼리 임베딩과 각 문서 임베딩의 코사인 유사도를 계산해 가장 유사한 문서 하나를 반환하고, generate_answer는 그 문서만을 근거로 답변을 만들어요.

평가 데이터 수집

RAG 평가에는 먼저 쿼리 세트가 필요해요. 쿼리를 RAG에 넣어 돌린 뒤 각 쿼리에 대한 responseretrieved_contexts를 모으고, 선택적으로 각 쿼리에 대한 정답(golden answer)도 준비할 수 있어요.

evaluation_dataset = EvaluationDataset.from_list(dataset)

dataset은 각 항목에 user_input, retrieved_contexts, response, reference를 담은 딕셔너리 리스트예요. 이렇게 모은 데이터를 EvaluationDataset.from_list(dataset)EvaluationDataset 객체에 로드해요.

평가 실행

데이터가 준비됐으면, 흔히 쓰는 RAG 평가 메트릭 세트로 평가를 돌려요. 평가용 LLM(evaluator LLM)은 원하는 모델을 선택할 수 있어요.

from ragas import evaluate
from ragas.llms import LangchainLLMWrapper

evaluator_llm = LangchainLLMWrapper(llm)
from ragas.metrics import LLMContextRecall, Faithfulness, FactualCorrectness

result = evaluate(dataset=evaluation_dataset, metrics=[LLMContextRecall(), Faithfulness(), FactualCorrectness()], llm=evaluator_llm)

evaluate()에 데이터셋과 메트릭 목록, 평가용 LLM을 넘기면 결과가 나와요. 예를 들어 다음과 같은 식으로요.

{'context_recall': 1.0000, 'faithfulness': 0.8571, 'factual_correctness': 0.7280}

검색이 완벽해 컨텍스트 재현율이 1.0이 나왔지만 생성 단계에서 정합성이 0.8571, 사실 정확도가 0.7280으로 떨어지는데요, 이렇게 컴포넌트별 점수를 나누어 보면 어느 단계를 개선해야 할지 힌트를 얻을 수 있어요.

더 알아보기