▶️ RAG(검색 증강 생성)란 무엇인가 (출처: IBM Technology)
RAG란 무엇인가: LLM의 지식 한계를 극복하는 방법
GPT-4나 Claude 같은 대형 언어 모델(LLM)은 학습 데이터의 범위 안에서만 알고 있다. 2024년 4월에 학습이 끝난 모델은 그 이후 세상에서 일어난 일을 모른다. 당신 회사의 내부 문서, 최신 제품 매뉴얼, 어제 작성된 보고서 — 이런 정보는 LLM이 절대 알 수 없다.
이 문제를 해결하는 가장 실용적인 방법이 **RAG(Retrieval-Augmented Generation, 검색 증강 생성)**다.
RAG의 아이디어는 단순하다: LLM에게 질문할 때, 관련 문서를 먼저 검색해서 함께 전달하면 어떨까? LLM은 학습된 지식 대신 제공된 문서를 참조해 답변을 생성한다. 마치 오픈북 시험처럼.
[기존 LLM]
사용자 질문 → LLM(내부 기억만) → 답변
[RAG 방식]
사용자 질문 → 벡터 검색 → 관련 문서 추출 → LLM(질문 + 문서) → 정확한 답변
RAG가 필요한 대표적 시나리오:
- 기업 내부 문서 Q&A (사규, 제품 스펙, 계약서 검색)
- 최신 뉴스/데이터 기반 분석 시스템
- 고객 지원 챗봇 (제품 매뉴얼, FAQ 기반)
- 법률/의학 문서 검색 및 요약
- 개인화 지식 베이스 (개인 노트, 논문 컬렉션)
"RAG는 LLM에게 도서관 사서의 역할을 부여하는 것과 같다. 모든 것을 외우는 대신, 필요할 때 정확한 자료를 찾아오는 능력을 준다." — Jerry Liu, LlamaIndex 창시자, AI Engineer Summit 2024
RAG(검색 증강 생성)는 LLM이 학습 데이터 밖의 정보(사내 문서, 최신 데이터 등)를 활용할 수 있도록 질의 시 관련 문서를 검색해 함께 전달하는 아키텍처다. 문서 로딩 → 청킹 → 임베딩 → 벡터 저장 → 검색 → 생성의 7단계 파이프라인으로 구성되며, 한국어 환경에서는 BGE-M3 임베딩 모델이 압도적으로 권장된다. 기본 구축 비용은 거의 무료에 가깝고 월 운영 비용도 약 24만 원 수준이므로, 실전 도입 장벽이 매우 낮다.
기본 RAG 아키텍처: 단계별 완전 이해
RAG는 크게 **인덱싱(Indexing)**과 검색 및 생성(Retrieval & Generation) 두 단계로 나뉜다.
아키텍처 단계별 설명
| 단계 | 처리 내용 | 사용 도구 | 주요 고려사항 |
|---|---|---|---|
| 1. Document Loading | PDF, Word, 웹페이지 등 원본 문서 수집 | LangChain Loaders, PyPDF2, Unstructured | 문서 형식별 파서, 인코딩 문제 |
| 2. Chunking | 긴 문서를 검색 가능한 작은 단위로 분할 | RecursiveCharacterTextSplitter | 청크 크기, 오버랩 전략 |
| 3. Embedding | 텍스트를 숫자 벡터로 변환 | OpenAI, BGE-M3, HuggingFace | 모델 선택, 한국어 지원 여부 |
| 4. Vector Store | 벡터를 인덱싱하고 저장 | Pinecone, Qdrant, pgvector | 비용, 속도, 확장성 |
| 5. Retrieval | 질문과 유사한 청크를 벡터 검색으로 추출 | Similarity Search, MMR | Top-K 설정, 다양성 확보 |
| 6. Augmentation | 검색된 청크를 프롬프트에 삽입 | PromptTemplate | 컨텍스트 윈도우 관리 |
| 7. Generation | LLM이 증강된 프롬프트로 최종 답변 생성 | GPT-4o, Claude, Llama | 모델 선택, 온도 설정 |
핵심 구성 요소 선택 가이드
임베딩 모델 비교표
임베딩 모델은 RAG 시스템의 "감각 기관"이다. 텍스트의 의미를 벡터 공간에 얼마나 정확히 표현하느냐가 검색 품질의 기반이 된다.
| 모델 | 제공사 | 벡터 차원 | MTEB 점수 | 한국어 성능 | 비용 (1M 토큰) | 최적 사용 케이스 |
|---|---|---|---|---|---|---|
| text-embedding-3-large | OpenAI | 3,072 | 64.6 | 우수 | $0.13 | 일반 목적, 영어 중심 |
| text-embedding-3-small | OpenAI | 1,536 | 62.3 | 보통 | $0.02 | 비용 절감, 빠른 속도 |
| text-embedding-ada-002 | OpenAI | 1,536 | 61.0 | 보통 | $0.10 | 레거시 시스템 유지 |
| BGE-M3 | BAAI | 1,024 | 64.5 | 매우 우수 | 무료 (로컬) | 한국어 RAG 최우선 추천 |
| KoSimCSE-roberta | SKT | 768 | 55.2 | 우수 | 무료 (로컬) | 한국어 전용, 경량 |
| multilingual-e5-large | Microsoft | 1,024 | 63.7 | 우수 | 무료 (로컬) | 다국어 문서 처리 |
| Cohere embed-v3 | Cohere | 1,024 | 64.0 | 보통 | $0.10 | 검색 최적화 |
출처: MTEB Leaderboard (Hugging Face, 2025년 기준), 한국어 성능은 KorSTS, KorNLI 기준
한국어 RAG에서 BGE-M3가 압도적으로 추천되는 이유:
- MIRACL(다국어 검색 벤치마크) 한국어 부문 1위
- 최대 8,192 토큰의 긴 문서 처리 가능 (기존 모델 대비 4-8배)
- Dense + Sparse + ColBERT 세 가지 검색 방식을 단일 모델로 지원
- HuggingFace에서 무료로 사용 가능
(Dense / Sparse / ColBERT)
벡터 데이터베이스 선택 가이드
벡터 DB는 RAG 시스템의 "기억 장치"다. 수백만 개의 벡터를 빠르게 저장하고 유사도 검색을 수행한다.
| DB | 타입 | 호스팅 | 무료 티어 | 성능 (1M 벡터 검색) | 월 비용 (1M 벡터) | 특징 |
|---|---|---|---|---|---|---|
| Pinecone | 전용 벡터 DB | 클라우드 | 100MB | ~20ms | $70+ | 관리형, 안정적 |
| Weaviate | 전용 벡터 DB | 클라우드/셀프 | 있음 | ~25ms | $25+ | GraphQL API, 객체 저장 |
| Qdrant | 전용 벡터 DB | 클라우드/셀프 | 있음 | ~15ms | $20+ | 빠름, Rust 기반 |
| Chroma | 경량 벡터 DB | 셀프호스트 | 전체 무료 | ~50ms (소규모) | 무료 | 프로토타입 최적, 간단한 설정 |
| pgvector | PostgreSQL 확장 | 셀프호스트 | 전체 무료 | ~100ms | 서버 비용만 | 기존 PG 통합, SQL 지원 |
| Milvus | 전용 벡터 DB | 클라우드/셀프 | 있음 | ~10ms | $30+ | 대규모 엔터프라이즈 |
| Redis Vector | 인메모리 DB | 클라우드/셀프 | 제한적 | ~5ms | 별도 | 초저지연 필요 시 |
출처: ANN Benchmarks (ann-benchmarks.com), 각 사 공식 pricing 페이지 (2026년 5월 기준)
선택 가이드:
- 프로토타입/개발: Chroma (설치 2분, 코드 5줄)
- 소규모 프로덕션 (10만 문서 이하): Qdrant 클라우드 또는 pgvector
- 중대규모 프로덕션 (수백만 문서): Pinecone 또는 Weaviate
- 기존 PostgreSQL 인프라 보유: pgvector (인프라 추가 불필요)
- 한국 기업 규정 준수 (데이터 국내 보관): Qdrant 셀프호스트 또는 pgvector
실전 코드: LangChain으로 완성된 RAG 파이프라인
아래는 한국어 PDF 문서를 처리하는 실제 동작하는 RAG 파이프라인이다. BGE-M3 임베딩과 Chroma 벡터 DB를 사용한다.
# requirements:
# pip install langchain langchain-openai langchain-community chromadb
# pip install FlagEmbedding sentence-transformers pypdf
import os
from pathlib import Path
from typing import List
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# =========================================================
# 1단계: 임베딩 모델 초기화 (BGE-M3, 한국어 최적화)
# =========================================================
def init_embedding_model():
"""BGE-M3 임베딩 모델 초기화 — 한국어 RAG에 가장 적합"""
return HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-m3",
model_kwargs={"device": "cpu"}, # GPU 사용 시 "cuda"
encode_kwargs={
"normalize_embeddings": True, # 코사인 유사도 최적화
"batch_size": 64
}
)
# =========================================================
# 2단계: 문서 로딩 및 청킹
# =========================================================
def load_and_split_documents(docs_dir: str) -> List:
"""PDF 디렉토리에서 문서를 로드하고 청크로 분할"""
# 디렉토리 내 모든 PDF 로드
loader = DirectoryLoader(
docs_dir,
glob="**/*.pdf",
loader_cls=PyPDFLoader
)
documents = loader.load()
print(f"[로드 완료] {len(documents)}개 페이지 로드됨")
# 한국어에 맞게 조정된 청킹 전략
# - chunk_size=512: BGE-M3의 최적 입력 크기
# - chunk_overlap=50: 문맥 연속성 유지
# - separators: 한국어 문장 구분자 우선
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50,
length_function=len,
separators=["\n\n", "\n", "。", ".", "!", "?", "!", "?", " ", ""]
)
chunks = splitter.split_documents(documents)
print(f"[청킹 완료] {len(chunks)}개 청크 생성됨 (평균 {sum(len(c.page_content) for c in chunks)//len(chunks)}자)")
return chunks
# =========================================================
# 3단계: 벡터 스토어 생성 또는 로드
# =========================================================
def get_or_create_vectorstore(chunks: List, embedding_model, persist_dir: str):
"""벡터 스토어가 이미 존재하면 로드, 없으면 생성"""
if Path(persist_dir).exists() and any(Path(persist_dir).iterdir()):
print("[캐시 로드] 기존 벡터 스토어 로드 중...")
return Chroma(
persist_directory=persist_dir,
embedding_function=embedding_model
)
print("[인덱싱] 벡터 스토어 생성 중 (최초 실행 시 몇 분 소요)...")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embedding_model,
persist_directory=persist_dir
)
vectorstore.persist()
print(f"[완료] {len(chunks)}개 청크 인덱싱 완료")
return vectorstore
# =========================================================
# 4단계: 한국어 최적화 프롬프트 템플릿
# =========================================================
KOREAN_RAG_PROMPT = PromptTemplate(
input_variables=["context", "question"],
template="""당신은 주어진 문서를 기반으로 정확하게 답변하는 AI 어시스턴트입니다.
아래 참고 문서를 바탕으로 질문에 답변해주세요.
문서에 없는 내용은 "제공된 문서에는 해당 정보가 없습니다"라고 말씀해주세요.
답변은 한국어로 작성하고, 근거가 된 문서 내용을 간략히 인용해주세요.
[참고 문서]
{context}
[질문]
{question}
[답변]"""
)
# =========================================================
# 5단계: RAG 체인 구성 및 실행
# =========================================================
def create_rag_chain(vectorstore, llm_model: str = "gpt-4o"):
"""RetrievalQA 체인 생성"""
llm = ChatOpenAI(
model=llm_model,
temperature=0, # RAG는 창의성보다 정확성이 중요
streaming=True
)
retriever = vectorstore.as_retriever(
search_type="mmr", # Maximum Marginal Relevance: 다양한 결과 반환
search_kwargs={
"k": 5, # 상위 5개 청크 검색
"fetch_k": 20, # MMR 계산을 위해 20개 후보 추출
"lambda_mult": 0.7 # 관련성(0.7) vs 다양성(0.3) 비율
}
)
return RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 소규모: stuff / 대규모: map_reduce
retriever=retriever,
chain_type_kwargs={"prompt": KOREAN_RAG_PROMPT},
return_source_documents=True # 출처 문서도 함께 반환
)
# =========================================================
# 메인 실행
# =========================================================
if __name__ == "__main__":
# 설정
DOCS_DIR = "./documents" # PDF 파일 디렉토리
VECTOR_STORE_DIR = "./vectorstore" # 벡터 DB 저장 경로
# 파이프라인 초기화
embedding_model = init_embedding_model()
chunks = load_and_split_documents(DOCS_DIR)
vectorstore = get_or_create_vectorstore(chunks, embedding_model, VECTOR_STORE_DIR)
rag_chain = create_rag_chain(vectorstore)
# 질의응답
query = "회사의 연차 휴가 정책은 어떻게 되나요?"
result = rag_chain({"query": query})
print(f"\n[질문] {query}")
print(f"[답변] {result['result']}")
print(f"\n[출처 문서]")
for i, doc in enumerate(result["source_documents"], 1):
print(f" {i}. {doc.metadata.get('source', '출처 없음')} (p.{doc.metadata.get('page', '?')})")
print(f" → {doc.page_content[:100]}...")
위 코드를 그대로 복사해
./documents 폴더에 PDF 한 장만 넣고 실행하면 바로 동작한다. Chroma는 별도 서버 없이 로컬 파일로 저장되고, BGE-M3는 최초 실행 시 자동 다운로드된다. OpenAI API 키만 있으면 추가 설정 없이 한국어 RAG를 체험할 수 있다.고급 RAG 기법: 기본을 넘어선 성능 최적화
기본 RAG 파이프라인으로는 한계가 있다. 실제 프로덕션에서는 검색 품질을 높이기 위한 고급 기법이 필수다.
기법 1: Hybrid Search (하이브리드 검색)
벡터 검색만으로는 고유명사, 제품 코드, 날짜 등 정확한 키워드 매칭에서 실패한다. BM25 키워드 검색과 벡터 검색을 결합한 하이브리드 검색이 해결책이다.
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
def create_hybrid_retriever(chunks: List, vectorstore):
"""BM25 + 벡터 검색 앙상블 리트리버"""
# BM25 키워드 리트리버
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 5
# 벡터 리트리버
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# 앙상블: BM25 40% + 벡터 60% 가중치
return EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6]
)
기법 2: Re-ranking (재순위 매기기)
검색된 결과를 더 정교한 모델로 재평가해 가장 관련 높은 문서를 선별한다.
from langchain.retrievers.document_compressors import CohereRerank
from langchain.retrievers import ContextualCompressionRetriever
def create_reranking_retriever(base_retriever):
"""Cohere Reranker로 검색 결과 재순위화"""
compressor = CohereRerank(
model="rerank-multilingual-v3.0", # 한국어 지원
top_n=3 # 재순위 후 상위 3개만 사용
)
return ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=base_retriever
)
기법 3: HyDE (Hypothetical Document Embeddings)
질문 자체보다 "이 질문에 답하는 가상의 문서"를 임베딩해 검색하면 더 높은 품질의 결과를 얻는다.
from langchain.chains import HypotheticalDocumentEmbedder
def create_hyde_retriever(vectorstore, llm):
"""HyDE: 가상 답변 문서로 검색 품질 향상"""
# LLM이 먼저 가상의 답변을 생성 → 그 답변으로 검색
hyde_embeddings = HypotheticalDocumentEmbedder.from_llm(
llm=llm,
base_embeddings=vectorstore.embeddings,
custom_prompt="""다음 질문에 대한 답변이 담긴 짧은 문서를 한국어로 작성하세요.
질문: {question}
가상 답변 문서:"""
)
return vectorstore.as_retriever(
search_kwargs={"k": 5},
embedding=hyde_embeddings
)
기법 4: Parent-Child Chunking (부모-자식 청킹)
작은 청크로 검색하지만, 실제 컨텍스트는 더 큰 부모 청크를 사용해 문맥을 보존한다.
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
def create_parent_child_retriever(documents, vectorstore):
"""소단위로 검색, 대단위로 컨텍스트 제공"""
store = InMemoryStore() # 프로덕션에서는 Redis 등 영구 저장소 사용
# 부모 청크: 1024토큰 (컨텍스트용)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1024)
# 자식 청크: 256토큰 (검색 정밀도용)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=256)
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter
)
retriever.add_documents(documents)
return retriever
고급 기법별 성능 향상 비교
| 기법 | 구현 난이도 | 검색 품질 향상 | 지연 시간 증가 | 비용 증가 |
|---|---|---|---|---|
| 기본 Vector Search | 낮음 (기준) | 기준 | 기준 (~50ms) | 기준 |
| Hybrid Search (BM25+벡터) | 낮음 | +15-20% | +10ms | 없음 |
| Re-ranking (Cohere) | 중간 | +20-30% | +200-500ms | $2/1K queries |
| HyDE | 중간 | +10-25% | +1-2초 (LLM 호출) | LLM 비용 추가 |
| Parent-Child Chunking | 중간 | +15-25% | +5ms | 저장 비용 증가 |
| 모두 조합 (Production) | 높음 | +40-60% | +1-3초 | 중간 |
출처: RAG 평가 프레임워크 RAGAS 내부 연구, LangChain 블로그 (2024-2025년)
기본 벡터 검색으로 먼저 프로토타입을 완성한 뒤, RAGAS로 성능을 측정하고 가장 낮은 지표에 맞는 기법을 추가하는 것이 효율적이다. 구현 난이도 대비 효과가 가장 높은 순서는 Hybrid Search → Re-ranking → Parent-Child Chunking → HyDE다. 처음부터 모든 기법을 적용하면 디버깅이 어렵고 비용 대비 효과를 측정하기 힘들다.
성능 평가: RAGAS로 RAG 시스템 측정하기
RAG 시스템을 구축했다면 얼마나 잘 동작하는지 정량적으로 측정해야 한다. **RAGAS(Retrieval-Augmented Generation Assessment)**가 업계 표준으로 자리잡았다.
4가지 핵심 평가 지표
| 지표 | 측정 내용 | 이상적 점수 | 낮을 때 원인 |
|---|---|---|---|
| Faithfulness | 답변이 검색된 문서에 근거하는가 (환각 측정) | 0.9 이상 | 검색된 문서와 무관한 답변 생성 |
| Answer Relevancy | 답변이 질문에 얼마나 관련이 있는가 | 0.85 이상 | 엉뚱한 문서 검색, 프롬프트 문제 |
| Context Recall | 필요한 정보가 검색 결과에 포함됐는가 | 0.8 이상 | 청킹 전략 문제, 임베딩 품질 |
| Context Precision | 검색된 문서 중 실제로 필요한 비율 | 0.8 이상 | Top-K 설정, 검색 알고리즘 |
# pip install ragas
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_recall, context_precision
from datasets import Dataset
# 평가용 데이터셋 준비
eval_data = {
"question": ["회사 휴가 정책은?", "원격근무 신청 방법은?"],
"answer": [rag_answer_1, rag_answer_2], # RAG 생성 답변
"contexts": [retrieved_docs_1, retrieved_docs_2], # 검색된 문서 목록
"ground_truth": ["정답 1...", "정답 2..."] # 실제 정답 (선택사항)
}
dataset = Dataset.from_dict(eval_data)
# RAGAS 평가 실행
results = evaluate(
dataset,
metrics=[faithfulness, answer_relevancy, context_recall, context_precision],
llm=ChatOpenAI(model="gpt-4o"),
embeddings=init_embedding_model()
)
print(results.to_pandas())
한국어 RAG 특화 팁
한국어는 영어와 언어 구조가 다르기 때문에 특별한 주의가 필요하다.
1. 형태소 분석을 활용한 BM25 최적화
한국어 BM25 검색에서 형태소 분석 없이 단순 공백 분리를 사용하면 성능이 크게 떨어진다.
# pip install konlpy
from konlpy.tag import Okt
class KoreanBM25Retriever:
"""형태소 분석 기반 한국어 BM25 리트리버"""
def __init__(self):
self.okt = Okt()
def tokenize(self, text: str) -> List[str]:
"""한국어 형태소 분석 + 불용어 제거"""
STOPWORDS = {"은", "는", "이", "가", "을", "를", "의", "에", "서", "와", "과"}
morphs = self.okt.morphs(text, stem=True) # 어간 추출
return [m for m in morphs if m not in STOPWORDS and len(m) > 1]
def tokenize_batch(self, texts: List[str]) -> List[List[str]]:
return [self.tokenize(text) for text in texts]
2. 한국어 청킹 전략
한국어는 문장 경계가 영어와 다르다. 단락(문단) 기준 청킹이 효과적이다.
# 한국어 문서에 최적화된 청킹 설정
korean_splitter = RecursiveCharacterTextSplitter(
# 한국어 문장 평균 30-50자 → 512자 = 약 10-17문장
chunk_size=512,
chunk_overlap=64,
# 한국어 구분자 우선순위
separators=[
"\n\n", # 단락 구분 (최우선)
"\n", # 줄 바꿈
"다.\n", "요.\n", "죠.\n", # 한국어 문장 종결어미
"다. ", "요. ", "죠. ",
". ", "! ", "? ",
" ", ""
],
length_function=lambda x: len(x) # 바이트가 아닌 문자 수 기준
)
3. 추천 한국어 임베딩 모델 비교
| 모델 | KorSTS | KorNLI | KorQuAD | 속도 | 크기 |
|---|---|---|---|---|---|
| BGE-M3 | 89.2 | 87.8 | 88.5 | 중간 | 2.2GB |
| KoSimCSE-RoBERTa | 83.4 | 79.3 | 76.2 | 빠름 | 443MB |
| multilingual-e5-large | 86.1 | 83.5 | 81.7 | 중간 | 1.3GB |
| KLUE-RoBERTa-large | 78.2 | 81.0 | 79.5 | 빠름 | 349MB |
| text-embedding-3-large (OpenAI) | 84.7 | 82.1 | 78.3 | 빠름 (API) | API |
출처: KLUE Benchmark, KorNLU 공식 리더보드 (2025년 기준)
실제 프로덕션 사례
사례 1: 국내 대형 로펌 — 판례 검색 시스템
배경: 국내 10대 로펌 중 한 곳이 20년치 판례 약 50만 건을 RAG로 검색하는 시스템을 구축했다.
구성: BGE-M3 임베딩 + Weaviate(셀프호스트) + Cohere Reranker + GPT-4o
결과:
- 변호사 검색 시간: 4-6시간 → 평균 15분으로 단축 (95% 감소)
- 관련 판례 누락률: 기존 키워드 검색 대비 35% 감소
- 변호사 1인당 하루 처리 가능 건수: 2.3배 증가
핵심 설계 결정: 판례 원문을 그대로 청킹하지 않고, 판결 요지/판시사항/판결이유를 별도 필드로 구조화해 메타데이터 필터링에 활용했다.
사례 2: 이커머스 기업 — 상품 Q&A 챗봇
배경: 월간 상품 문의 30만 건을 처리하는 대형 쇼핑몰
구성: text-embedding-3-small + Pinecone + GPT-4o mini
결과:
- CS 처리 비용: 월 4,200만원 → 900만원 (78.6% 절감)
- 고객 만족도(CSAT): 3.2점 → 4.1점 (5점 만점)
- 자동 처리 비율: 82% (나머지 18%는 인간 상담사 연결)
비용 계산 가이드: 1만 문서 RAG 시스템 월 비용
현실적인 시나리오로 비용을 계산해보자. 평균 5페이지 PDF 문서 1만 개를 RAG로 구축한다고 가정한다.
초기 구축 비용 (1회성)
| 항목 | 계산식 | 비용 |
|---|---|---|
| 임베딩 생성 | 1만 문서 × 5페이지 × 500토큰/청크 × 5청크/페이지 = 1.25억 토큰 | BGE-M3 무료 / OpenAI: $2.50 |
| 벡터 DB 초기 인덱싱 | 125만 벡터 (1,024차원) | Chroma: 무료 / Pinecone: ~$3 |
| 컴퓨팅 (임베딩 서버) | GPU t4 × 3시간 | GCP: ~$1.80 |
| 초기 구축 총 비용 | 약 $5-10 (로컬 GPU 사용 시 거의 무료) |
월간 운영 비용 (일일 1,000 쿼리 기준)
| 항목 | 계산식 | 월 비용 |
|---|---|---|
| 벡터 DB | Qdrant 클라우드 1GB 플랜 | $20 |
| LLM API (GPT-4o) | 1,000쿼리 × 30일 × (2K 입력 + 500 출력) 토큰 | $75 |
| Re-ranking (Cohere) | 1,000쿼리 × 30일 × $0.002/쿼리 | $60 |
| 임베딩 (쿼리) | 1,000쿼리 × 30일 × 200토큰 × $0.02/M | $0.12 |
| 서버 (임베딩 + 앱) | 2 vCPU, 4GB RAM | $20 |
| 월 운영 총 비용 | 약 $175 (~241,000원) |
"RAG 시스템의 비용 최적화 핵심은 LLM API 비용이다. GPT-4o 대신 GPT-4o mini를 사용하면 LLM 비용이 약 90% 절감된다. 다만 답변 품질이 다소 낮아지므로 사용 케이스에 따라 판단해야 한다."
비용 절감 옵션 비교
| 최적화 방법 | 월 비용 | 절감율 | 품질 영향 |
|---|---|---|---|
| 기본 설정 (위 기준) | $175 | 기준 | 기준 |
| GPT-4o → GPT-4o mini | $112 | 36% | 품질 소폭 하락 |
| Cohere → 셀프호스팅 re-ranker | $115 | 34% | 유사한 품질 |
| 벡터 DB → pgvector (셀프호스트) | $135 | 23% | 동일 |
| BGE-M3 로컬 + pgvector + Llama 3.1 | $25 | 86% | 한국어 품질 주의 |
BGE-M3 + pgvector + Llama 3.1 조합으로 월 $25까지 비용을 낮출 수 있지만, 한국어 생성 품질이 GPT-4o 대비 크게 낮을 수 있다. 특히 Llama 3.1의 한국어 명령 이행률과 문장 자연스러움이 GPT-4o mini보다도 떨어지는 경우가 많다. 프로덕션 전환 전에 반드시 실제 사용 시나리오로 품질 평가(RAGAS Faithfulness 0.8 이상 기준)를 통과했는지 검증하라.
개발자 액션 아이템: 오늘부터 시작하는 RAG
- 30분 프로토타입: Chroma + OpenAI 임베딩으로 PDF 하나 RAG 구축 (LangChain 빠른 시작 가이드)
- 한국어 최적화: BGE-M3 모델로 전환하고 KorSTS 점수로 품질 검증
- 평가 파이프라인 구축: RAGAS를 프로젝트에 추가하고 Faithfulness 0.85 이상을 목표로 설정
- Hybrid Search 도입: BM25 + 벡터 검색 앙상블로 검색 품질 즉시 향상
- 비용 모니터링: LangSmith 또는 Langfuse로 토큰 사용량과 비용을 실시간 추적
- 점진적 고도화: 프로덕션 안정화 후 Re-ranking → HyDE → Parent-Child 순으로 고급 기법 적용
- RAG는 LLM의 지식 한계를 극복하는 가장 실용적인 방법으로, 문서 로딩 → 청킹 → 임베딩 → 벡터 저장 → 검색 → 생성의 7단계 파이프라인으로 구성된다.
- 한국어 RAG에는 BGE-M3 임베딩 모델이 최우선 추천이며, 벡터 DB는 프로토타입엔 Chroma, 프로덕션엔 Qdrant 또는 pgvector가 적합하다.
- 검색 품질을 높이려면 Hybrid Search(BM25+벡터) → Re-ranking → Parent-Child Chunking → HyDE 순으로 단계적으로 고급 기법을 도입하고, RAGAS로 각 단계의 효과를 정량 측정해야 한다.
- 1만 문서 기준 초기 구축 비용은 사실상 무료에 가깝고 월 운영 비용은 약 $175(24만 원)이며, GPT-4o mini 전환만으로 36%를 절감할 수 있다.
관련 자료 · 공식 출처 · 사용 안내
— IBM — RAG 개념 정리
— LangChain — RAG 구축 튜토리얼 (실습)
— 원논문: Retrieval-Augmented Generation (Lewis et al., arXiv)
— LlamaIndex 공식 문서 (RAG 프레임워크)