Blog
ragllmvectordbembeddingailangchainretrieval

RAG(Retrieval-Augmented Generation) 완전 가이드 - 아키텍처부터 실전 구축까지

RAG의 핵심 아키텍처, 청킹 전략, 벡터 DB, 검색 기법, 고급 패턴(Self-RAG, Graph RAG, Agentic RAG), 평가 방법, 엔터프라이즈 구축 노하우를 체계적으로 정리합니다.

Data Dynamics2026년 4월 16일33 min read

RAG(Retrieval-Augmented Generation)는 LLM의 한계를 극복하기 위해 외부 지식을 검색하여 응답에 활용하는 기법입니다. 이 글에서는 RAG의 기본 개념부터 고급 기법, 엔터프라이즈 구축 실전까지 체계적으로 다룹니다.


1. RAG란 무엇인가

정의와 등장 배경

RAG(Retrieval-Augmented Generation)는 2020년 Meta(Facebook) AI Research의 Patrick Lewis 등이 제안한 기법으로, 정보 검색(Retrieval)과 텍스트 생성(Generation)을 결합한 아키텍처입니다.

핵심 아이디어는 단순합니다. LLM이 답변을 생성하기 전에, 먼저 관련 문서를 검색하여 컨텍스트로 제공하면 더 정확하고 최신의 답변을 생성할 수 있다는 것입니다.

[기존 LLM]
사용자 질문 → LLM → 응답 (학습 데이터에만 의존)

[RAG]
사용자 질문 → 관련 문서 검색 → 검색 결과 + 질문 → LLM → 응답 (외부 지식 기반)

LLM 단독 사용의 한계와 RAG의 필요성

LLM을 단독으로 사용할 때 다음과 같은 근본적인 한계가 있습니다.

한계설명RAG의 해결 방식
지식 단절 (Knowledge Cutoff)학습 시점 이후 정보를 알 수 없음최신 문서를 실시간 검색하여 제공
할루시네이션 (Hallucination)사실이 아닌 정보를 자신 있게 생성검색된 문서를 근거로 응답, 출처 명시
도메인 지식 부재기업 내부 데이터, 전문 분야 지식 부족사내 문서를 벡터 DB에 저장하여 검색
비용미세 조정(Fine-tuning)에 높은 비용문서 업데이트만으로 지식 갱신 가능

참고: RAG는 Fine-tuning의 대안이 아닌 보완적 기법입니다. Fine-tuning은 모델의 행동 방식(스타일, 형식)을 변경하는 데 적합하고, RAG는 최신 사실 정보를 제공하는 데 적합합니다. 두 기법을 함께 사용하면 최적의 결과를 얻을 수 있습니다.


2. RAG 아키텍처 상세

전체 파이프라인 구조

RAG 시스템은 크게 오프라인 인덱싱 파이프라인온라인 쿼리 파이프라인으로 구성됩니다.

[오프라인: 인덱싱 파이프라인]
원본 문서 → 문서 로딩 → 청킹(Chunking) → 임베딩 → 벡터 DB 저장

[온라인: 쿼리 파이프라인]
사용자 질의 → 질의 임베딩 → 벡터 검색 → 리랭킹 → 컨텍스트 구성 → LLM 생성 → 응답

각 단계별 역할:

단계역할주요 도구
문서 로딩다양한 형식의 문서를 텍스트로 변환LangChain Loaders, Unstructured
청킹문서를 적절한 크기의 조각으로 분할RecursiveCharacterTextSplitter
임베딩텍스트를 고차원 벡터로 변환OpenAI, Cohere, BGE, E5
벡터 저장임베딩 벡터를 인덱싱하여 저장Chroma, Pinecone, Milvus
검색유사한 문서 청크를 빠르게 검색ANN (Approximate Nearest Neighbor)
리랭킹검색 결과를 관련도 순으로 재정렬Cross-Encoder, Cohere Rerank
생성검색 결과를 바탕으로 응답 생성GPT-4, Claude, LLaMA

Naive RAG vs Advanced RAG vs Modular RAG

RAG는 발전 과정에 따라 세 가지 세대로 구분됩니다.

Naive RAG (1세대)

가장 기본적인 RAG 구현으로, 단순한 검색-생성 파이프라인입니다.

질의 → 임베딩 → Top-K 검색 → 프롬프트에 삽입 → LLM 생성
  • 장점: 구현이 간단
  • 한계: 검색 품질에 전적으로 의존, 노이즈 문서 포함 가능

Advanced RAG (2세대)

검색 전/후에 최적화 단계를 추가한 개선된 파이프라인입니다.

질의 → [질의 변환] → 임베딩 → [하이브리드 검색] → [리랭킹] → [컨텍스트 압축] → LLM 생성
  • 질의 변환: 질의를 재구성하여 검색 품질 향상
  • 하이브리드 검색: 벡터 + 키워드 검색 결합
  • 리랭킹: 검색 결과의 관련도 재평가
  • 컨텍스트 압축: 불필요한 정보 제거

Modular RAG (3세대)

각 구성 요소를 독립 모듈로 분리하여 유연하게 조합하는 아키텍처입니다.

질의 분석 → 라우팅 → [검색 모듈 선택] → [후처리 모듈 조합] → 생성 → 검증
                          ├─ 벡터 검색
                          ├─ 그래프 검색
                          ├─ SQL 검색
                          └─ 웹 검색
  • 라우팅: 질의 유형에 따라 적절한 검색 전략 선택
  • 모듈 교체: 각 단계의 컴포넌트를 독립적으로 교체 가능
  • 피드백 루프: 생성 결과를 평가하여 검색 재시도

3. 데이터 준비: 문서 로딩과 청킹

문서 로딩

RAG의 첫 단계는 다양한 형식의 원본 문서를 텍스트로 변환하는 것입니다.

문서 형식로더특이사항
PDFPyPDFLoader, Unstructured표, 이미지 내 텍스트 추출 주의
HTMLBeautifulSoupLoader태그 제거, 본문 추출
MarkdownMarkdownLoader헤딩 기반 구조 보존
Word/PPTUnstructured서식 정보 활용 가능
DB (SQL)SQLDatabaseLoader쿼리 결과를 문서화
Confluence/Notion전용 API Loader페이지 계층 구조 반영
# 다양한 문서 로딩 예시
from langchain_community.document_loaders import (
    PyPDFLoader,
    WebBaseLoader,
    UnstructuredMarkdownLoader,
    CSVLoader
)
 
# PDF 로딩
pdf_loader = PyPDFLoader("report.pdf")
pdf_docs = pdf_loader.load()
 
# 웹 페이지 로딩
web_loader = WebBaseLoader("https://docs.example.com/guide")
web_docs = web_loader.load()
 
# 마크다운 로딩
md_loader = UnstructuredMarkdownLoader("README.md")
md_docs = md_loader.load()

청킹 전략

청킹(Chunking)은 RAG 성능에 가장 큰 영향을 미치는 단계 중 하나입니다. 청크가 너무 크면 노이즈가 포함되고, 너무 작으면 문맥이 손실됩니다.

주요 청킹 전략:

전략설명적합 대상
고정 크기 (Fixed Size)일정 문자/토큰 수로 분할구조가 없는 텍스트
재귀적 분할 (Recursive)단락 → 문장 → 단어 순서로 분할 시도범용 (가장 널리 사용)
의미 기반 (Semantic)임베딩 유사도 변화 지점에서 분할주제 전환이 잦은 문서
문서 구조 기반헤딩, 섹션 등 구조를 활용기술 문서, 매뉴얼
Agentic ChunkingLLM이 직접 청킹 수행복잡한 문서
from langchain.text_splitter import RecursiveCharacterTextSplitter
 
# 재귀적 분할 (가장 많이 사용)
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,        # 청크 최대 크기 (문자 수)
    chunk_overlap=50,      # 청크 간 겹침 (문맥 유지)
    separators=["\n\n", "\n", ". ", " ", ""],  # 분할 우선순위
    length_function=len
)
 
chunks = splitter.split_documents(documents)
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
 
# 의미 기반 분할
semantic_splitter = SemanticChunker(
    OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",
    breakpoint_threshold_amount=95  # 유사도 변화 상위 5%에서 분할
)
 
semantic_chunks = semantic_splitter.split_documents(documents)

참고: 청크 크기의 일반적인 가이드라인은 2001000 토큰입니다. 작은 청크(200400)는 정밀한 검색에, 큰 청크(6001000)는 풍부한 문맥 제공에 유리합니다. overlap은 chunk_size의 1020%가 적절합니다.

메타데이터 관리

청크에 메타데이터를 첨부하면 검색 시 필터링과 가중치 조정에 활용할 수 있습니다.

# 메타데이터가 포함된 청크 예시
{
    "content": "분기별 매출은 전년 대비 15% 증가했습니다...",
    "metadata": {
        "source": "2025_Q4_report.pdf",
        "page": 12,
        "department": "finance",
        "date": "2025-12-31",
        "doc_type": "quarterly_report",
        "access_level": "internal"
    }
}

활용 사례:

  • 날짜 필터: "2025년 4분기 매출" → date 필드로 범위 검색
  • 부서 필터: "마케팅팀 관련 문서" → department == "marketing" 필터
  • 접근 제어: 사용자 권한에 따라 access_level 필터링

4. 임베딩과 벡터 데이터베이스

임베딩 모델 선택

임베딩 모델은 텍스트를 고차원 벡터로 변환하여 의미적 유사도를 계산할 수 있게 합니다. 모델 선택은 RAG 성능에 직접적인 영향을 미칩니다.

모델차원다국어특징
OpenAI text-embedding-3-large3072O높은 성능, API 비용 발생
OpenAI text-embedding-3-small1536O비용 대비 우수한 성능
Cohere embed-v31024O검색 특화, 다국어 강점
BGE-M3 (BAAI)1024O오픈소스 최고 수준, 다국어
E5-Mistral-7B4096O오픈소스, 긴 문서에 강점
multilingual-e5-large1024O다국어 특화, 경량
# OpenAI 임베딩
from langchain_openai import OpenAIEmbeddings
 
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector = embeddings.embed_query("RAG는 검색과 생성을 결합한 기법입니다")
# 결과: 1536차원 벡터
 
# 오픈소스 임베딩 (Hugging Face)
from langchain_huggingface import HuggingFaceEmbeddings
 
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-m3",
    model_kwargs={"device": "cuda"},
    encode_kwargs={"normalize_embeddings": True}
)

참고: 한국어 RAG를 구축할 때는 다국어 모델(BGE-M3, multilingual-e5 등) 사용을 권장합니다. 영어 전용 모델은 한국어 의미를 정확히 포착하지 못할 수 있습니다.

주요 벡터 DB 비교

벡터 DB유형확장성하이브리드 검색메타데이터 필터적합 환경
Chroma임베디드소규모XO프로토타입, PoC
Pinecone관리형 SaaS대규모OO운영 부담 최소화
Weaviate자체 호스팅/클라우드대규모OO하이브리드 검색 중심
Milvus자체 호스팅초대규모OO엔터프라이즈 대용량
Qdrant자체 호스팅/클라우드대규모OO고성능 필터링
pgvectorPostgreSQL 확장중규모XO (SQL)기존 PostgreSQL 활용
# Chroma를 이용한 벡터 저장 및 검색
from langchain_chroma import Chroma
 
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    collection_name="company_docs",
    persist_directory="./chroma_db"
)
 
# 유사도 검색
results = vectorstore.similarity_search_with_score(
    query="데이터 보존 정책은?",
    k=5
)
 
for doc, score in results:
    print(f"[점수: {score:.4f}] {doc.page_content[:100]}...")

인덱싱 전략

벡터 검색의 속도와 정확도는 인덱싱 알고리즘에 크게 의존합니다.

알고리즘원리속도정확도메모리
Flat (Brute Force)모든 벡터와 비교느림100%낮음
HNSW계층적 그래프 탐색빠름매우 높음높음
IVF (Inverted File)클러스터 기반 검색빠름높음중간
PQ (Product Quantization)벡터 압축 후 검색매우 빠름중간매우 낮음
HNSW + PQHNSW와 PQ 결합빠름높음중간
  • 소규모 (< 10만 벡터): Flat 또는 HNSW
  • 중규모 (10만 ~ 1,000만): HNSW
  • 대규모 (> 1,000만): IVF + PQ 또는 HNSW + PQ

5. 검색 전략

벡터 유사도 검색

벡터 검색은 질의 벡터와 저장된 문서 벡터 간의 유사도를 계산하여 가장 관련성 높은 문서를 찾습니다.

주요 유사도 메트릭:

메트릭수식특징
Cosine Similaritycos(θ) = (A·B) / (‖A‖·‖B‖)방향 기반, 가장 널리 사용
Euclidean Distance (L2)d = √Σ(a_i - b_i)²거리 기반, 정규화 필요
Inner Product (Dot Product)s = Σ(a_i × b_i)빠른 계산, 정규화된 벡터 시 Cosine과 동일

참고: 대부분의 임베딩 모델은 정규화된 벡터를 출력하므로, Cosine Similarity와 Inner Product의 결과가 동일합니다. 성능이 중요한 경우 연산이 간단한 Inner Product를 사용하세요.

키워드 검색 (BM25)

BM25는 전통적인 정보 검색 알고리즘으로, 단어의 빈도(TF)와 희소성(IDF)을 고려합니다.

from langchain_community.retrievers import BM25Retriever
 
# BM25 검색기 생성
bm25_retriever = BM25Retriever.from_documents(
    documents=chunks,
    k=5
)
 
# 키워드 검색
results = bm25_retriever.invoke("NiFi LDAP 인증 설정 방법")

벡터 검색 vs BM25 비교:

상황벡터 검색BM25
"NiFi 인증 설정 방법"의미적으로 유사한 문서 검색 (좋음)"NiFi", "인증" 키워드 매칭 (좋음)
"보안 접근 제어""인증", "권한 관리" 등 유사 개념 문서도 검색 (좋음)정확한 키워드가 없으면 누락 (약함)
"error code 0x8007"의미 유사도 낮아 부정확 (약함)정확한 코드 매칭 (좋음)

하이브리드 검색

하이브리드 검색은 벡터 검색과 키워드 검색의 장점을 결합합니다. 두 검색 결과를 결합하기 위해 Reciprocal Rank Fusion (RRF) 또는 가중 점수 방식을 사용합니다.

from langchain.retrievers import EnsembleRetriever
 
# 벡터 검색기
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
 
# BM25 검색기
bm25_retriever = BM25Retriever.from_documents(chunks, k=5)
 
# 하이브리드 검색 (앙상블)
hybrid_retriever = EnsembleRetriever(
    retrievers=[vector_retriever, bm25_retriever],
    weights=[0.6, 0.4]  # 벡터 검색 60%, BM25 40%
)
 
results = hybrid_retriever.invoke("NiFi 클러스터 TLS 인증서 설정")

RRF (Reciprocal Rank Fusion) 공식:

RRF_score(d) = Σ 1 / (k + rank_i(d))

- d: 문서
- k: 상수 (보통 60)
- rank_i(d): i번째 검색기에서의 순위

리랭킹

검색된 문서를 더 정밀하게 관련도 순으로 재정렬하는 단계입니다. 초기 검색(bi-encoder)이 속도를 위해 정확도를 일부 희생한다면, 리랭킹(cross-encoder)은 소수의 후보에 대해 정밀한 관련도를 평가합니다.

from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
 
# Cohere Reranker
reranker = CohereRerank(
    model="rerank-v3.5",
    top_n=3  # 상위 3개만 반환
)
 
# 리랭킹 적용 검색기
compression_retriever = ContextualCompressionRetriever(
    base_compressor=reranker,
    base_retriever=hybrid_retriever  # 하이브리드 검색 결과를 리랭킹
)
 
results = compression_retriever.invoke("Kudu 파티셔닝 전략")

리랭킹의 효과:

단계처리 대상속도정확도
초기 검색 (Bi-Encoder)전체 문서 (수만~수백만)빠름중간
리랭킹 (Cross-Encoder)후보 문서 (10~50개)느림높음

6. 프롬프트 구성과 응답 생성

컨텍스트 주입 방식

검색된 문서를 LLM 프롬프트에 삽입하는 방법입니다.

Stuff 방식 (가장 기본): 모든 검색 결과를 하나의 프롬프트에 삽입

컨텍스트:
[문서 1 내용]
[문서 2 내용]
[문서 3 내용]

위 컨텍스트를 바탕으로 다음 질문에 답하세요:
{질문}

Map-Reduce 방식: 각 문서에 대해 개별 요약 후 통합

[문서 1] → LLM → 요약 1 ─┐
[문서 2] → LLM → 요약 2 ─┼→ 통합 LLM → 최종 응답
[문서 3] → LLM → 요약 3 ─┘

Map-Rerank 방식: 각 문서로 응답 생성 후 점수 기반 선택

[문서 1] → LLM → 응답 1 (점수: 0.9) ← 선택
[문서 2] → LLM → 응답 2 (점수: 0.3)
[문서 3] → LLM → 응답 3 (점수: 0.7)

프롬프트 템플릿 설계

효과적인 RAG 프롬프트 설계를 위한 원칙:

from langchain_core.prompts import ChatPromptTemplate
 
rag_prompt = ChatPromptTemplate.from_template("""
당신은 Data Dynamics의 기술 지원 전문가입니다.
아래 제공된 컨텍스트 정보만을 사용하여 질문에 답변하세요.
 
## 규칙
1. 컨텍스트에 없는 정보는 "제공된 문서에서 해당 정보를 찾을 수 없습니다"라고 답하세요.
2. 답변에 사용한 문서의 출처를 명시하세요.
3. 기술적인 내용은 코드 예시와 함께 설명하세요.
 
## 컨텍스트
{context}
 
## 질문
{question}
 
## 답변
""")

주요 설계 원칙:

  • 역할 명시: 모델의 전문 분야 지정
  • 컨텍스트 기반 응답 강제: 할루시네이션 방지
  • 모르면 모른다고 답하도록 지시: 정직한 응답 유도
  • 출처 인용 요구: 응답의 신뢰도 향상

출처 인용(Citation) 처리

RAG 응답에 출처를 포함하면 사용자가 정보를 검증할 수 있습니다.

# 출처가 포함된 RAG 체인 구현
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
 
def format_docs_with_sources(docs):
    formatted = []
    for i, doc in enumerate(docs):
        source = doc.metadata.get("source", "Unknown")
        page = doc.metadata.get("page", "")
        ref = f"[{i+1}] {source}" + (f" (p.{page})" if page else "")
        formatted.append(f"{ref}\n{doc.page_content}")
    return "\n\n---\n\n".join(formatted)
 
rag_chain = (
    {"context": retriever | format_docs_with_sources,
     "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)
 
answer = rag_chain.invoke("Kudu의 파티셔닝 제한사항은?")

7. 고급 RAG 기법

Query Transformation

질의를 변환하여 검색 품질을 향상시키는 기법입니다.

HyDE (Hypothetical Document Embedding)

질의에 대한 가상의 답변을 생성하고, 그 답변의 임베딩으로 검색하는 방식입니다.

원래 질의: "Kudu의 파티셔닝 방식은?"
    ↓ LLM이 가상 답변 생성
가상 답변: "Apache Kudu는 Hash Partitioning과 Range Partitioning을
           지원합니다. Hash Partitioning은 데이터를 균등 분배하고..."
    ↓ 가상 답변을 임베딩하여 검색
검색 결과: 실제 Kudu 파티셔닝 관련 문서

Multi-Query

하나의 질의를 여러 관점으로 재작성하여 검색 커버리지를 확대합니다.

from langchain.retrievers.multi_query import MultiQueryRetriever
 
multi_retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(),
    llm=llm
)
 
# 원래 질의: "Kudu 성능 최적화 방법"
# 자동 생성되는 쿼리들:
# 1. "Apache Kudu 테이블 스캔 성능을 높이는 방법"
# 2. "Kudu 파티셔닝으로 쿼리 속도를 개선하는 전략"
# 3. "Kudu 클러스터 하드웨어 및 설정 튜닝 가이드"
results = multi_retriever.invoke("Kudu 성능 최적화 방법")

Step-back Prompting

구체적인 질의를 더 일반적인 질의로 변환하여 배경 지식을 먼저 검색합니다.

원래 질의: "NiFi 1.23에서 LDAP 인증 시 TLS 에러가 발생합니다"
    ↓ Step-back
변환 질의: "Apache NiFi의 LDAP 인증 및 TLS 설정 구조는?"
    ↓ 배경 지식 검색 후 원래 질의와 결합

Self-RAG / Corrective RAG (CRAG)

Self-RAG

LLM이 스스로 검색 필요 여부를 판단하고, 검색된 문서의 관련성을 평가하며, 생성한 응답을 검증하는 기법입니다.

질의 수신
  ↓
[검색 필요한가?] → No → 직접 응답
  ↓ Yes
[문서 검색]
  ↓
[검색된 문서가 관련 있는가?] → No → 다른 전략으로 재검색
  ↓ Yes
[응답 생성]
  ↓
[응답이 문서에 근거하는가?] → No → 재생성
  ↓ Yes
[응답이 질문에 유용한가?] → No → 재생성
  ↓ Yes
최종 응답 출력

Corrective RAG (CRAG)

검색 결과의 품질을 평가하여, 부정확하면 웹 검색 등 대체 소스로 보완합니다.

[문서 검색] → [관련성 평가]
                ├─ Correct → 지식 정제 → 생성
                ├─ Ambiguous → 웹 검색으로 보완 → 생성
                └─ Incorrect → 웹 검색으로 대체 → 생성

Graph RAG

지식 그래프를 활용하여 엔티티 간의 관계를 기반으로 검색하는 기법입니다. 단순 텍스트 유사도로는 포착하기 어려운 관계 기반 질의에 효과적입니다.

[일반 RAG]
"이 프로젝트의 데이터 아키텍트는 누구인가?" → 텍스트 유사도 검색 → 정확한 결과 어려움

[Graph RAG]
"이 프로젝트의 데이터 아키텍트는 누구인가?"
  → 지식 그래프에서 "프로젝트" 노드 탐색
  → "역할: 데이터 아키텍트" 관계 추적
  → 연결된 "사람" 노드 반환

활용 사례:

  • 조직 구조 기반 질의 ("A 부서의 팀장은?")
  • 인과 관계 추적 ("이 장애의 근본 원인은?")
  • 다단계 추론 ("A 제품을 구매한 고객이 함께 구매한 제품은?")

Agentic RAG

LLM 에이전트가 도구(Tool)로서 검색을 수행하며, 복잡한 질의에 대해 계획-실행-반성 루프를 통해 답변을 구축합니다.

[사용자 질의: "Q3 매출이 Q2보다 얼마나 증가했고, 주요 원인은?"]

에이전트 계획:
1. Q2 매출 데이터 검색 → tool: vector_search("Q2 매출")
2. Q3 매출 데이터 검색 → tool: vector_search("Q3 매출")
3. 매출 증감 비교 → tool: calculator(Q3 - Q2)
4. 증가 원인 분석 → tool: vector_search("Q3 매출 증가 원인")
5. 종합 보고서 생성

에이전트 실행: 각 단계 수행 → 중간 결과 반성 → 필요 시 재검색
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.tools.retriever import create_retriever_tool
 
# 검색 도구 생성
search_tool = create_retriever_tool(
    retriever=retriever,
    name="company_docs_search",
    description="회사 내부 문서에서 정보를 검색합니다. 정책, 기술 가이드, 보고서 등을 찾을 때 사용하세요."
)
 
# 에이전트 생성
agent = create_tool_calling_agent(llm, [search_tool], prompt)
agent_executor = AgentExecutor(agent=agent, tools=[search_tool], verbose=True)
 
result = agent_executor.invoke({
    "input": "Q3 매출이 Q2 대비 얼마나 증가했고, 주요 원인은?"
})

8. RAG 평가와 최적화

평가 지표

RAG 시스템의 품질은 검색 품질생성 품질 두 축으로 평가합니다.

검색 품질 지표:

지표설명
Recall@K상위 K개 검색 결과에 정답 문서가 포함된 비율
Precision@K상위 K개 검색 결과 중 관련 문서의 비율
MRR (Mean Reciprocal Rank)첫 번째 정답 문서의 순위 역수 평균
NDCG순위를 고려한 관련도 점수

생성 품질 지표:

지표설명
Faithfulness응답이 검색된 문서에 근거하는 정도
Answer Relevancy응답이 질문에 적절한 정도
Context Relevancy검색된 컨텍스트가 질문에 관련된 정도
Context Utilization검색된 컨텍스트를 실제로 활용한 정도

RAGAS 프레임워크

RAGAS(Retrieval Augmented Generation Assessment)는 RAG 시스템을 자동 평가하는 오픈소스 프레임워크입니다.

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall
)
from datasets import Dataset
 
# 평가 데이터 구성
eval_data = {
    "question": ["Kudu의 파티셔닝 방식은?"],
    "answer": ["Kudu는 Hash와 Range 파티셔닝을 지원합니다..."],
    "contexts": [["Kudu는 Hash Partitioning과 Range Partitioning..."]],
    "ground_truth": ["Kudu는 Hash, Range 파티셔닝을 지원하며..."]
}
 
dataset = Dataset.from_dict(eval_data)
 
# RAGAS 평가 실행
results = evaluate(
    dataset,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
 
print(results)
# {'faithfulness': 0.92, 'answer_relevancy': 0.88,
#  'context_precision': 0.85, 'context_recall': 0.90}

성능 튜닝 포인트

RAG 성능이 기대에 미치지 못할 때 점검할 핵심 포인트:

문제증상튜닝 방향
검색 누락관련 문서를 못 찾음청킹 크기 조정, 하이브리드 검색 적용, K값 증가
노이즈 문서무관한 문서가 포함됨리랭킹 추가, 메타데이터 필터링, 임계값 설정
할루시네이션검색 결과와 무관한 응답프롬프트 개선, Temperature 낮추기, 출처 인용 강제
불완전한 응답일부 정보만 포함청크 크기 증가, Multi-Query 적용, K값 증가
느린 응답지연시간이 김인덱스 최적화, 캐싱, 스트리밍 적용
임베딩 품질의미 유사도 부정확임베딩 모델 변경, 도메인 미세 조정

튜닝 우선순위 (권장):

  1. 청킹 전략 최적화 (영향도 최대)
  2. 하이브리드 검색 적용
  3. 리랭킹 추가
  4. 임베딩 모델 변경
  5. 프롬프트 개선
  6. Query Transformation 적용

9. 엔터프라이즈 RAG 구축 실전

보안과 접근 제어

기업 환경에서는 문서별 접근 권한을 RAG 시스템에 반영해야 합니다.

# 접근 제어가 적용된 검색 예시
def secure_search(query: str, user_role: str, department: str):
    # 사용자 권한에 따른 메타데이터 필터 구성
    filter_conditions = {
        "access_level": {"$in": get_allowed_levels(user_role)},
        "department": {"$in": get_allowed_departments(user_role, department)}
    }
 
    results = vectorstore.similarity_search(
        query=query,
        k=5,
        filter=filter_conditions
    )
    return results
 
# 일반 직원: 공개 문서만 검색
results = secure_search("인사 정책", user_role="employee", department="engineering")
 
# 관리자: 내부 문서까지 검색
results = secure_search("인사 정책", user_role="manager", department="hr")

주요 보안 고려사항:

  • 문서 수준 ACL: 문서별 접근 권한을 메타데이터로 관리
  • 행 수준 보안: 검색 결과에서 권한 없는 문서 필터링
  • 프롬프트 인젝션 방지: 사용자 입력 검증 및 새니타이징
  • 데이터 암호화: 벡터 DB 저장 시 암호화 적용
  • 감사 로그: 검색 질의 및 접근 이력 기록

멀티테넌트 아키텍처

여러 팀이나 고객이 하나의 RAG 시스템을 공유할 때의 아키텍처입니다.

[멀티테넌트 RAG 아키텍처]

테넌트 A ─┐                    ┌─ 컬렉션 A (벡터 DB)
테넌트 B ─┼→ API Gateway → ─┼─ 컬렉션 B (벡터 DB)
테넌트 C ─┘   (인증/라우팅)    └─ 컬렉션 C (벡터 DB)
                                        ↓
                                 공유 LLM 엔드포인트

격리 전략:

방식설명장점단점
컬렉션 격리테넌트별 별도 컬렉션완전한 데이터 격리관리 오버헤드
네임스페이스 격리동일 컬렉션 내 네임스페이스 분리효율적 자원 활용소프트 격리
메타데이터 필터링테넌트 ID를 메타데이터로 필터구현 간단대규모 시 성능 저하

운영 모니터링과 피드백 루프

프로덕션 RAG 시스템의 지속적인 품질 관리를 위한 모니터링 체계입니다.

핵심 모니터링 지표:

카테고리지표목표
성능응답 지연시간 (P50/P95/P99)P95 < 3초
품질사용자 피드백 (좋아요/싫어요)긍정 > 80%
검색검색 결과 없음 비율< 5%
비용일일 토큰 사용량예산 범위 내
안정성에러율< 0.1%

피드백 루프 구축:

[사용자 질의] → [RAG 응답] → [사용자 피드백]
                                    ↓
                            [피드백 분석]
                            ├─ 부정 피드백 → 실패 사례 수집 → 개선
                            │               ├─ 검색 누락 → 문서 추가/청킹 조정
                            │               ├─ 잘못된 응답 → 프롬프트 개선
                            │               └─ 느린 응답 → 인프라 최적화
                            └─ 긍정 피드백 → 성공 패턴 분석 → 유지/확대

참고: RAG 시스템은 "한 번 구축하고 끝"이 아닙니다. 문서가 추가/변경되고 사용자 질의 패턴이 변화하므로, 지속적인 모니터링과 개선이 필수입니다.


References

  • Lewis, P. et al. (2020). "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks." NeurIPS
  • Gao, Y. et al. (2024). "Retrieval-Augmented Generation for Large Language Models: A Survey." arXiv
  • Asai, A. et al. (2023). "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection." ICLR
  • Yan, S. et al. (2024). "Corrective Retrieval Augmented Generation." arXiv
  • Edge, D. et al. (2024). "From Local to Global: A Graph RAG Approach to Query-Focused Summarization." arXiv
  • Es, S. et al. (2024). "RAGAS: Automated Evaluation of Retrieval Augmented Generation." EACL
  • LangChain Documentation — https://python.langchain.com/docs/
  • LlamaIndex Documentation — https://docs.llamaindex.ai/

— Data Dynamics 엔지니어링 팀