RAG가 필요한 이유
LLM은 학습 데이터 이후의 정보를 모릅니다. 회사 내부 문서, 최신 뉴스, 개인 데이터에 대한 질문에 답하려면 외부 지식을 연결해야 합니다. RAG는 검색(Retrieval)과 생성(Generation)을 결합해 이 문제를 해결합니다.
flowchart LR Q[사용자 질문] --> E[임베딩 변환] E --> VS[(벡터 DB)] VS --> |상위 k개 청크| R[관련 문서] R --> P[프롬프트 구성] Q --> P P --> LLM[LLM 생성] LLM --> A[최종 답변]
핵심 컴포넌트
1. 문서 청킹 (Chunking)
청킹 방법이 RAG 품질의 50%를 결정합니다:
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 기본 청킹 (비추천)
simple_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
# 의미 단위 청킹 (권장)
semantic_splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100,
separators=["
", "
", ".", "!", "?", " "],
length_function=len,
)
# 문서 처리
docs = semantic_splitter.split_documents(raw_docs)
청킹 전략 비교:
| 전략 | 청크 크기 | 적합한 경우 |
|---|---|---|
| Fixed size | 500-1000자 | 일반 텍스트 |
| Semantic | 가변 | 논문, 기술 문서 |
| Sentence | 1-3문장 | QA 시스템 |
| Parent-Child | 중첩 | 정밀 검색 |
벡터 DB 선택
# Chroma (로컬 개발)
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
persist_directory="./chroma_db"
)
# pgvector (프로덕션 PostgreSQL)
from langchain_postgres import PGVector
vectorstore = PGVector.from_documents(
documents=docs,
embedding=embeddings,
connection="postgresql://user:pass@host/db",
collection_name="my_docs",
)
# 검색
results = vectorstore.similarity_search_with_score(
query="RAG 청킹 방법",
k=5
)
주요 벡터 DB 비교:
quadrantChart title 벡터 DB 비교 (확장성 vs 관리 용이성) x-axis 관리 어려움 --> 관리 쉬움 y-axis 소규모 --> 대규모 quadrant-1 엔터프라이즈 quadrant-2 확장성 우선 quadrant-3 프로토타입 quadrant-4 균형 Pinecone: [0.85, 0.75] Weaviate: [0.40, 0.80] Chroma: [0.90, 0.30] pgvector: [0.60, 0.60] Qdrant: [0.55, 0.70]
고급 검색 전략
하이브리드 검색 (BM25 + 벡터)
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
# BM25 (키워드 검색)
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 3
# 벡터 검색
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 앙상블 (0.5:0.5 가중치)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.5, 0.5]
)
리랭킹 (Cohere Rerank)
from langchain.retrievers.contextual_compression import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
compressor = CohereRerank(model="rerank-multilingual-v3.0", top_n=3)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=ensemble_retriever
)
완성된 RAG 파이프라인
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_template('''
다음 컨텍스트를 기반으로 질문에 답하세요.
컨텍스트에 없는 내용은 "모르겠습니다"라고 답하세요.
컨텍스트:
{context}
질문: {question}
답변:
''')
def format_docs(docs):
return "
".join(doc.page_content for doc in docs)
rag_chain = (
{"context": compression_retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 사용
response = rag_chain.invoke("회사 휴가 정책이 어떻게 되나요?")
print(response)
RAG 평가 지표
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_recall,
context_precision,
)
result = evaluate(
dataset=eval_dataset,
metrics=[
context_precision, # 검색된 컨텍스트 정밀도
context_recall, # 관련 정보 재현율
faithfulness, # 답변의 사실 충실도
answer_relevancy, # 답변 관련성
],
)
print(result.to_pandas())
지표 해석:
- Faithfulness > 0.8: 할루시네이션 낮음
- Context Precision > 0.7: 검색 품질 양호
- Answer Relevancy > 0.8: 질문에 잘 답함
프로덕션 체크리스트
- 청크 크기 실험 (500/800/1200자 비교)
- 임베딩 모델 선택 (OpenAI vs BGE vs E5)
- 하이브리드 검색 + 리랭킹 적용
- RAGAS로 자동 평가 파이프라인 구축
- 답변 캐싱 (동일 질문 반복 비용 절감)
- Guardrails 적용 (악의적 쿼리 필터링)
LangChain 공식 문서와 RAGAS GitHub를 함께 참고하세요. 청킹 전략 실험이 가장 큰 품질 향상을 가져옵니다.





