메트릭 기반 RAG 개발
최근 Zilliz Unstructured Data Meetup에서 진행된 발표에서 Ragas의 유지 관리자 Jithin James와 Shahul Es는 Retrieval Augmented Generation(RAG) 시스템을 평가하기 위해 메트릭 기반 개발을 활용하는 방법에 대한 인사이트를 공유했습니다. 개발자는 더 나은 성능을 위해 평가 결과를 바탕으로 시스템을 조정할 수 있습니다.
발표에서 Jithin과 Shahul은 RAG 시스템 평가의 이론적 기반과 실제 적용 사례를 모두 논의했습니다. 그들은 평가 코드 뒤에 있는 이론을 이해하면 그 기능에 대해 더 깊은 인사이트를 얻을 수 있다고 설명했습니다. 이 부분에 이어, 유사도 검색 및 AI 애플리케이션에서 효율성으로 잘 알려진 선도적인 오픈 소스 벡터 데이터베이스인 Milvus로 구동되는 실제 RAG 시스템을 사용해 평가 프로세스를 시연했습니다.
더 자세한 이해를 위해 meetup 발표 다시 보기를 시청하세요.
RAG 시스템의 성능을 평가하는 방법
Shahul은 Ragas가 RAG 시스템이 생성한 답변의 진실성을 계산하는 방법을 다뤘습니다. 이는 최종 사용자가 보게 되는 것이 답변이기 때문에 중요한 평가 메트릭입니다. 조금 천천히 살펴보며 깊이 들어가 보겠습니다. Ragas는 답변의 진실성을 계산하기 위해 다음 공식을 사용합니다:
그림 1- Ragas answer_truthfulness 메트릭 계산 공식
Retrieval-Augmented Generation(RAG) 시스템에서 답변 정확성 계산하기
이 과정에는 두 가지 주요 메트릭이 포함됩니다: 사실적 유사도와 의미적 유사도입니다. 이러한 메트릭은 정답 기준과 비교하여 생성된 답변의 품질과 관련성을 판단하는 데 도움이 됩니다.
주요 메트릭
사실적 유사도:
- 이 메트릭은 정답 기준과 생성된 답변 사이에 사실 정보가 존재하는지를 비교하여 생성된 답변의 사실적 정확성을 측정합니다.
의미적 유사도:
- 이 메트릭은 생성된 답변이 정답 기준과 의미적으로 얼마나 유사한지를 측정하여, 생성된 답변이 전달하는 의미가 정답 기준과 일치하는지 확인합니다.
답변 정확성을 계산하는 단계
참양성(TP), 거짓양성(FP), 거짓음성(FN)을 식별합니다:
참양성: 정답 기준과 생성된 답변 모두에 올바르게 존재하는 진술.
거짓양성: 생성된 답변에는 존재하지만 정답 기준에는 없는 진술.
거짓음성: 정답 기준에는 존재하지만 생성된 답변에는 없는 진술.
F1 점수를 계산합니다:
- F1 점수는 정밀도와 재현율의 평균으로, 둘 사이의 균형을 제공합니다. F1 점수 공식은 그림 1에 표시되어 있습니다.
실제 예시
정답 기준 진술과 생성된 답변을 고려해 보겠습니다:
정답 기준: "Alan Turing developed the concept of the Turing machine."
생성된 답변: "Alan Turing is known for developing the concept of the Turing machine and artificial intelligence."
1단계: TP, FP, FN 식별
참양성(TP):
- "Alan Turing developed the concept of the Turing machine."
거짓양성(FP):
- "Alan Turing is known for developing artificial intelligence."
거짓음성(FN):
- 없음(생성된 답변이 정답 기준의 모든 요소를 포함하기 때문).
2단계: F1 점수 계산
TP = 1
FP = 1
FN = 0
F1 = 1 / (1 + 0.5 * (1 + 0))
= 1 / 1.5
≈ 0.67
해석<
사실적 유사도: 생성된 답변은 정답 기준의 핵심 정보를 포함하므로 사실적으로 정확합니다.
의미적 유사도: 생성된 답변은 추가 정보를 포함하고 있지만, 정답 기준과의 의미적 정렬을 유지합니다.
F1 점수는 이러한 측면들을 결합하여 정답의 정확성을 측정하는 지표를 제공하며, 정밀도와 재현율의 균형을 맞춥니다. 이 방법은 생성된 답변이 사실적 내용과 전반적인 의미 측면에서 정답 기준과 얼마나 잘 일치하는지 평가하는 데 도움이 됩니다.
하지만 답변의 진실성은 rag 시스템을 평가하는 데 사용할 수 있는 유일한 지표가 아닙니다. 다른 지표로는 충실성, 답변 관련성, 컨텍스트 재현율, 컨텍스트 정밀도가 있습니다. Mivus 기반 RAG 시스템을 평가하고 개선하는 방법을 실용적으로 살펴보겠습니다.
Milvus 기반 RAG 시스템 평가 및 개선
이제 RAG 시스템 평가를 위한 이론적 기반을 이해했으니, Milvus 벡터 데이터베이스로 구축된 실용적인 RAG 예제로 들어가 보겠습니다. RAG 시스템을 설정, 구현, 평가하는 과정을 살펴보겠습니다. 결과를 바탕으로, 시스템을 어떻게 개선할 수 있는지도 살펴보겠습니다.
환경 설정
먼저 개발 환경을 설정해야 합니다. 이를 위해 먼저 필요한 모든 라이브러리를 설치합니다:
!pip install pymilvus[model] ragas langchain langchain_openai python-dotenv nest_asyncio pypdf langchain_community
코드에서 각 라이브러리가 어떤 용도로 사용되는지 살펴보겠습니다:
Pymilvus[model]는 Milvus에 연결하고, 컬렉션을 생성하고, 데이터를 삽입하고, 유사도 검색을 수행하는 데 도움이 됩니다.Ragas는 평가 지표를 제공하고, 합성 테스트 세트를 생성하며, RAG 파이프라인을 평가합니다.Langchain은 문서를 로드하고, 청크로 분할하며, 임베딩 및 쿼리를 위해 텍스트 데이터를 준비합니다.Langchain_openai는 OpenAI 모델과 인터페이스하고, 임베딩을 생성하며, API 호출을 사용해 질문에 답변합니다.Python-dotenv는 API 키와 같은 민감한 정보를 관리하기 위해 .env 파일에서 환경 변수를 로드합니다.Nest_asyncio는 동기 환경 내에서 중첩된 비동기 작업을 가능하게 합니다. 특히 Jupiter 노트북을 사용하는 경우에 유용합니다.Pypdf는 PDF 문서를 로드하고, 콘텐츠를 추출하며, 처리를 위해 텍스트를 준비합니다.Langchain_community는 커뮤니티에서 기여한 리소스를 통합하기 위한 추가 문서 로더와 유틸리티를 제공합니다. 라이브러리를 설치한 후, 코드로 가져오고 API 키를 구성합니다:
import os
import pandas as pd
import nest_asyncio
import openai
from pymilvus import MilvusClient, model
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_recall,
context_precision,
answer_correctness,
)
from ragas.testset.generator import TestsetGenerator
from ragas.testset.evolutions import simple, reasoning, multi_context
from langchain_community.document_loaders import DirectoryLoader
from langchain.document_loaders import PyPDFLoader
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from dotenv import load_dotenv
from datasets import Dataset
# Load environment variables
# Apply nest_asyncio to allow nested event loops
nest_asyncio.apply()
# Set up OpenAI API key
os.environ["OPENAI_API_KEY"] = "Your API Key"
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
라이브러리와 모듈을 코드로 가져오면 해당 함수들을 호출하고 사용할 수 있습니다. OpenAI 키는 API 호출을 할 때 OpenAI에 인증하는 데 도움이 됩니다. 키가 없다면 OpenAI API page로 이동하여 하나를 생성하세요.
문서 로드 및 준비
다음으로, 문서를 로드하고 처리를 위해 준비합니다:
# Load PDF documents from a directory
pdf_loader = DirectoryLoader("/content/data", loader_cls=PyPDFLoader)
documents = pdf_loader.load()
# Ensure each document has a filename in its metadata
for document in documents:
document.metadata['filename'] = document.metadata['source'
이 코드는 PyPDFLoader 클래스를 사용하여 디렉터리에서 PDF 문서를 로드합니다. 그런 다음 DirectoryLoader 인스턴스를 생성하고, 문서를 로드한 뒤, 각 문서를 순회하면서 메타데이터에 filename 속성을 추가합니다. 이를 통해 각 문서가 해당 파일명으로 제대로 식별되고 관리되도록 합니다. 이 단계는 RAG 시스템의 지식 베이스를 형성하므로 매우 중요합니다. 이 예제에서는 PDF 문서를 사용하지만, 필요에 따라 다른 문서 유형에 맞게 조정할 수 있습니다.
테스트 세트 생성하기
이제 데이터를 로드했으므로, RAG 시스템을 효과적으로 평가하는 데 도움이 되는 다양한 테스트 질문 세트가 필요합니다. Ragas 프레임워크를 사용하여 합성 테스트 세트를 생성하겠습니다.
# Initialize OpenAI models for test set generation
generator_llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
critic_llm = ChatOpenAI(model="gpt-4")
embeddings = OpenAIEmbeddings()
# Create a TestsetGenerator
generator = TestsetGenerator.from_langchain(
generator_llm,
critic_llm,
embeddings
)
# Generate synthetic test set with 10 samples
testset = generator.generate_with_langchain_docs(documents, test_size=10, distributions={simple: 0.5, reasoning: 0.25, multi_context: 0.25})
# Convert test set to Pandas DataFrame
testset_df = testset.to_pandas()
print(testset_df)
이 코드는 두 개의 언어 모델(테스트 세트 생성을 위한 gpt-3.5-turbo-16k 및 평가를 위한 gpt-4)과 OpenAI의 임베딩 모델을 초기화합니다. 그런 다음 이러한 모델을 사용하여 TestsetGenerator 인스턴스를 생성합니다. 생성기는 로드된 PDF 문서에서 10개의 샘플로 구성된 합성 테스트 세트를 생성하며, 다양한 유형의 질문에 대해 지정된 분포를 적용합니다. 이 테스트 세트는 단순 쿼리, 추론 작업, 여러 컨텍스트가 필요한 질문을 처리하는 능력을 포함하여 RAG 시스템 성능의 다양한 측면을 평가하는 데 도움이 됩니다. 다음은 생성된 테스트 세트의 예입니다.
Fig 2- Ragas를 사용하여 생성된 테스트 세트
정답(ground truth)은 주어진 질문에 대한 실제 답변입니다. 이후 RAG 시스템의 출력이 이러한 정답에 얼마나 가까운지를 통해 시스템이 얼마나 잘 작동하는지 측정하는 데 사용할 것입니다. Shahul이 발표에서 지적했듯이, Ragas를 사용해 합성 테스트 세트를 생성한다고 해서 이를 무작정 사용한다는 의미는 아닙니다. 필요한 샘플을 필터링하여 사용해야 합니다.
테스트 세트가 준비되었으니, Milvus로 구동되는 간단한 RAG 시스템을 구축한 다음 위의 테스트 세트를 사용하여 평가해 보겠습니다.
Milvus 설정 및 문서 임베딩 삽입하기
이제 Milvus 벡터 데이터베이스를 설정하고 문서 임베딩을 삽입해 보겠습니다. Milvus가 설치되어 실행 중인지 확인하세요. 그렇지 않다면 이 Milvus 설치 종합 가이드를 따르세요.
# Connect to Milvus instance
milvus_client = MilvusClient(uri="http://localhost:19530")
# Define collection name
collection_name = "pdf_collection"
# Drop the collection if it already exists
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
# Create a new collection
milvus_client.create_collection(
collection_name=collection_name,
dimension=768, # Dimension of vectors
overwrite=True
)
# Initialize the embedding function
embedding_fn = model.DefaultEmbeddingFunction()
# Extract document texts and generate embeddings
expanded_docs = [doc.page_content for doc in documents]
expanded_vectors = embedding_fn.encode_documents(expanded_docs)
# Prepare data for insertion
expanded_data = [
{"id": i, "vector": expanded_vectors[i], "text": expanded_docs[i], "subject": "pdf_documents"}
for i in range(len(expanded_vectors))
]
# Insert expanded data into the collection
milvus_client.insert(data=expanded_data, collection_name=collection_name)
위 코드는 Milvus 인스턴스에 연결하고 문서 벡터를 저장하기 위해 pdf_collection이라는 컬렉션을 설정합니다. 컬렉션이 이미 존재하는 경우, 이를 삭제한 뒤 벡터 차원을 768로 하여 다시 생성합니다. 임베딩 함수를 초기화하고, 로드된 문서에서 텍스트를 추출한 다음 해당 임베딩을 생성합니다. 그런 다음 코드는 임베딩 및 관련 텍스트가 포함된 데이터를 준비하고 이 데이터를 Milvus 컬렉션에 삽입합니다. 이 단계는 Milvus가 진가를 발휘하는 부분으로, 저장된 데이터 임베딩에 대해 빠르고 효율적인 유사도 검색을 수행할 수 있게 해 주며, 이는 RAG 시스템의 검색 부분에 매우 중요합니다.
유사도 검색 수행하기
문서가 Milvus에 인덱싱되었으므로 이제 질문에 대한 관련 컨텍스트를 검색하기 위해 유사도 검색을 수행할 수 있습니다:
# 검색 매개변수 정의
search_params = {"metric_type": "COSINE", "params": {"nprobe": 20}} # 더 나은 recall을 위한 nprobe
# 검색 수행
query_vectors = embedding_fn.encode_queries(testset_df['question'].tolist())
results = milvus_client.search(
collection_name=collection_name,
data=query_vectors,
anns_field="vector", # 벡터 필드 이름 지정
search_params=search_params,
limit=3, # 검색할 상위 결과 수
output_fields=["id", "text"]
)
위 코드는 코사인 유사도와 더 나은 recall을 위한 nprobe 값 20을 사용하여 Milvus 컬렉션의 검색 매개변수를 설정합니다. 테스트 세트 질문을 쿼리 벡터로 인코딩하고 pdf_collection 컬렉션에서 검색을 수행하여 각 쿼리에 대해 가장 유사한 상위 세 개의 벡터를 검색합니다. 검색 결과에는 일치하는 문서의 IDs와 text가 포함됩니다.
대규모 언어 모델을 사용하여 답변 생성하기
관련 컨텍스트를 검색했으므로 이제 언어 모델을 사용하여 답변을 생성할 수 있습니다. 이 경우 GPT 3.5 turbo를 사용하겠습니다.
# OpenAI를 사용하여 답변을 생성하는 함수
def generate_answer(question, contexts):
context_text = " ".join(contexts)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": f"Context: {context_text}nnQuestion: {question}nAnswer:"}
]
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages,
max_tokens=100,
temperature=0.7,
)
return response.choices[0].message.content.strip()
# 컨텍스트를 추출하고 OpenAI를 사용하여 답변 생성
contexts = []
answers = []
for i, result in enumerate(results):
context = [match['entity']['text'] for match in result]
contexts.append(context)
question = testset_df['question'].iloc[i]
answer = generate_answer(question, context)
answers.append(answer)
이 코드는 주어진 질문과 컨텍스트를 기반으로 답변을 생성하기 위해 OpenAI의 gpt-3.5-turbo 모델을 사용하는 generate_answer 함수를 정의합니다. API 호출을 위한 메시지를 준비하고, 컨텍스트를 연결한 뒤 모델에 질의합니다. 그런 다음 생성된 답변을 추출하여 반환합니다. 이어지는 루프는 검색 결과를 반복하면서 generate_answer 함수를 사용하여 테스트 세트의 각 질문에 대한 답변을 생성하고, 컨텍스트와 답변을 리스트에 저장합니다. 답변을 생성하기 위해 루프에서 사용된 질문은 테스트 세트에서 생성된 것과 동일한 질문입니다.
이제 RAG 시스템이 생성한 답변과 정답 답변이 있으므로, RAG 시스템이 얼마나 잘 작동하는지 평가해 보겠습니다.
RAG 시스템 평가하기
마지막으로, 앞서 논의한 지표를 사용하여 RAG 시스템의 성능을 평가하겠습니다:
# 모든 리스트의 길이가 동일한지 확인
min_length = min(len(testset_df['question']), len(testset_df['ground_truth']), len(answers), len(contexts))
questions = testset_df['question'][:min_length]
ground_truths = testset_df['ground_truth'][:min_length]
answers = answers[:min_length]
contexts = contexts[:min_length]
# Create the dataset with correct column names
data = {
"question": questions,
"answer": answers,
"contexts": contexts,
"ground_truth": ground_truths
}
from datasets import Dataset
# Convert dict to dataset
dataset = Dataset.from_pandas(pd.DataFrame(data))
# Evaluate using RAGAS
metrics = evaluate(
dataset=dataset,
metrics=[
answer_correctness,
context_precision,
context_recall,
faithfulness,
answer_relevancy,
],
raise_exceptions=False #handle async issues
)
# Convert result to DataFrame for better readability
result_df = metrics.to_pandas()
print(result_df)
이 코드는 모든 관련 리스트를 동일한 최소 길이로 잘라 평가 프로세스의 일관성을 보장합니다. 이를 통해 평가 중 오류를 유발할 수 있는 길이 불일치를 방지합니다. 그런 다음 이렇게 잘라낸 리스트들로 딕셔너리를 만들고 이를 Pandas DataFrame으로 변환한 뒤, Hugging Face Dataset으로 변환합니다. 이 구조화된 데이터셋을 통해 생성된 답변을 ground truths와 비교하는 RAGAS 프레임워크를 사용하여 체계적인 평가를 수행할 수 있습니다. 평가 지표(답변 정확성, 컨텍스트 정밀도, 컨텍스트 재현율, 충실성, 답변 관련성)는 RAG 파이프라인의 성능을 평가합니다.
아래 평가 결과를 살펴보세요:
그림 3- Ragas RAG 시스템 평가 결과
우리 RAG 시스템의 컨텍스트 정밀도와 재현율은 훌륭하고 답변 관련성도 칭찬할 만합니다. 하지만 답변 정확성은 상당히 낮습니다.
뛰어난 컨텍스트 재현율과 정밀도 덕분에 검색 측면은 좋습니다. 따라서 생성 부분이 답변 정확성에 영향을 미치고 있을 수 있다고 결론 내릴 수 있습니다. 이를 개선하려면 GPT-4와 같은 더 강력한 답변 생성 모델을 사용할 수 있습니다.
결론
Retrieval-Augmented Generation (RAG) 시스템을 평가하고 개선하는 것은 AI 기반 정보 검색 영역에서 미묘하지만 필수적인 작업입니다. Jithin James와 Shahul Es가 보여준 것처럼 지표 중심 접근 방식을 활용하면 RAG 시스템을 체계적으로 개선하여 정확하고 관련성 있으며 신뢰할 수 있는 정보를 제공하도록 할 수 있습니다.
이 주제에 대한 자세한 내용은 YouTube에서 강연 다시보기를 시청하세요.
추가 자료
계속 읽기

VDBBench Adds Cost-Aware Benchmarking for Vector Databases
Compare Zilliz Cloud, Pinecone, and turbopuffer with VDBBench cost-aware vector database benchmarks across latency, freshness, multitenancy, and cold starts.

DeepSeek-OCR Explained: Optical Compression for Scalable Long-Context and RAG Systems
Discover how DeepSeek-OCR uses visual tokens and Contexts Optical Compression to boost long-context LLM efficiency and reshape RAG performance.

AI Integration in Video Surveillance Tools: Transforming the Industry with Vector Databases
Discover how AI and vector databases are revolutionizing video surveillance with real-time analysis, faster threat detection, and intelligent search capabilities for enhanced security.


