RAG란 무엇인가
RAG(Retrieval-Augmented Generation)는 LLM의 환각(hallucination) 문제를 해결하는 핵심 아키텍처다. 외부 지식 저장소에서 관련 문서를 검색한 뒤, 이를 컨텍스트로 활용해 답변을 생성한다.
하지만 단순히 RAG를 도입한다고 끝이 아니다. 검색 품질이 곧 답변 품질이며, 파이프라인의 각 단계를 최적화해야 실무에서 쓸 만한 시스템이 완성된다.
RAG 시스템의 성능은 가장 약한 고리에 의해 결정된다. 청킹, 임베딩, 검색, 리랭킹 중 하나라도 부실하면 전체 품질이 떨어진다.
RAG 진화 단계: Naive에서 Agentic까지
| 단계 | 특징 | 한계 |
|---|---|---|
| Naive RAG | 문서 분할 → 임베딩 → 벡터 검색 → 생성 | 단순 유사도 의존, 복잡한 질의 처리 불가 |
| Advanced RAG | 청킹 최적화 + 리랭킹 + 하이브리드 검색 | 파이프라인 고정, 동적 판단 불가 |
| Modular RAG | 모듈별 교체·조합 가능한 유연한 구조 | 모듈 간 조율에 설계 비용 발생 |
| Agentic RAG | LLM 에이전트가 검색 전략을 동적으로 결정 | 지연 시간 증가, 비용 상승 |
Naive RAG의 문제점
Naive RAG는 사용자 질의를 그대로 임베딩하여 벡터 검색만 수행한다. 다음과 같은 상황에서 실패한다.
- 멀티홉 질의: “A 회사의 CEO가 졸업한 대학의 설립 연도는?” → 두 단계 검색 필요
- 키워드 불일치: 의미는 같지만 표현이 다른 경우 검색 누락
- 컨텍스트 부족: 청킹 경계에서 정보가 잘리는 문제
청킹 전략: 검색 품질의 출발점
문서를 어떻게 나누느냐가 RAG 성능의 50% 이상을 좌우한다.
주요 청킹 방식 비교
| 방식 | 설명 | 적합한 경우 |
|---|---|---|
| 고정 크기 | 토큰/문자 수 기준 분할 | 빠른 프로토타이핑 |
| 재귀적 분할 | 구분자 우선순위(\n\n → \n → . → 공백)로 분할 | 범용적 사용 |
| 시맨틱 청킹 | 임베딩 유사도 변화 지점에서 분할 | 주제 전환이 잦은 문서 |
| 문서 구조 기반 | 제목·섹션·단락 기준 분할 | 구조화된 기술 문서 |
실전 시맨틱 청킹 구현
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
chunker = SemanticChunker(
embeddings,
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=90 # 유사도 급변 상위 10% 지점에서 분할
)
chunks = chunker.split_text(document_text)
실무 팁: 청크 크기는 512~1024 토큰이 일반적이며, 오버랩 10~15%를 주면 경계에서 정보가 잘리는 문제를 완화할 수 있다.
하이브리드 검색: 키워드 + 시맨틱의 결합
벡터 검색(시맨틱)만으로는 고유명사나 코드명 같은 정확한 키워드 매칭이 어렵다. BM25 같은 희소 검색과 결합하면 상호 보완이 가능하다.
검색 방식별 강점
| 검색 방식 | 강점 | 약점 |
|---|---|---|
| BM25 (희소) | 정확한 키워드 매칭, 고유명사 검색 | 동의어·유사 표현 처리 불가 |
| 벡터 검색 (밀집) | 의미적 유사도, 동의어 처리 | 정확한 키워드 매칭 약함 |
| 하이브리드 | 양쪽 장점 결합 | 가중치 튜닝 필요 |
하이브리드 검색의 최종 점수는 Reciprocal Rank Fusion(RRF)으로 결합한다.
- : 문서
- : 각 검색 방식의 결과 랭킹 집합
- : 해당 검색 방식에서 문서 의 순위
- : 스무딩 상수 (보통 60)
순위가 높을수록(숫자가 작을수록) 분모가 작아져 점수가 커지며, 여러 검색 방식에서 고르게 상위에 오른 문서가 최종 상위에 랭크된다.
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import Chroma
# BM25 검색기
bm25_retriever = BM25Retriever.from_documents(documents, k=10)
# 벡터 검색기
vectorstore = Chroma.from_documents(documents, embeddings)
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 10})
# 하이브리드 결합 (가중치: BM25 40%, 벡터 60%)
hybrid_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6]
)
리랭킹: 검색 결과의 정밀 필터링
1차 검색으로 후보 문서를 넓게 가져온 뒤, Cross-Encoder 기반 리랭커가 질의-문서 쌍의 관련성을 정밀하게 재평가한다.
리랭킹 파이프라인
- 1차 검색: 하이브리드 검색으로 상위 20~50개 후보 추출 (빠르지만 덜 정밀)
- 리랭킹: Cross-Encoder가 질의-문서 쌍을 직접 비교 (느리지만 정밀)
- 최종 선택: 상위 3~5개 문서만 LLM에 전달
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
# Cohere Reranker 설정
reranker = CohereRerank(
model="rerank-v3.5",
top_n=5 # 최종 5개 문서만 선택
)
compression_retriever = ContextualCompressionRetriever(
base_compressor=reranker,
base_retriever=hybrid_retriever # 앞서 구성한 하이브리드 검색기
)
# 사용
results = compression_retriever.invoke("RAG 파이프라인 최적화 방법")
리랭킹은 비용 대비 효과가 가장 높은 최적화다. 검색 단계를 변경하지 않고도 최종 답변 품질을 크게 향상시킨다.
Agentic RAG: 지능형 검색 전략
Agentic RAG는 LLM 에이전트가 검색 전략 자체를 동적으로 결정한다.
에이전트가 수행하는 판단
- 질의 분석: 검색이 필요한지, 어떤 소스를 탐색할지 판단
- 쿼리 변환: 원본 질의를 검색에 유리한 형태로 재작성
- 다단계 검색: 첫 검색 결과가 부족하면 쿼리를 수정하여 재검색
- 결과 검증: 검색된 문서가 질의에 충분한 답을 제공하는지 평가
from langgraph.graph import StateGraph, START, END
def route_query(state):
"""질의 유형에 따라 검색 전략 분기"""
query = state["query"]
# LLM이 질의를 분석하여 라우팅 결정
decision = llm.invoke(
f"다음 질의에 대해 적절한 검색 전략을 선택하세요: {query}"
f"\n선택지: [vector_search, hybrid_search, web_search, no_search]"
)
return decision.content
def rewrite_query(state):
"""검색 결과가 불충분할 때 쿼리 재작성"""
original = state["query"]
rewritten = llm.invoke(f"검색 성능 향상을 위해 쿼리를 재작성하세요: {original}")
state["query"] = rewritten.content
return state
# LangGraph로 에이전트 워크플로우 구성
graph = StateGraph(state_schema=RAGState)
graph.add_node("route", route_query)
graph.add_node("retrieve", retrieve_documents)
graph.add_node("rewrite", rewrite_query)
graph.add_node("generate", generate_answer)
최적화 체크리스트
실무에서 RAG 파이프라인을 구축할 때 다음 순서로 최적화를 진행하라.
- 청킹 전략 선택 — 문서 특성에 맞는 분할 방식 적용
- 임베딩 모델 선정 — 한국어라면 다국어 모델(
multilingual-e5-large등) 권장 - 하이브리드 검색 도입 — BM25 + 벡터 검색 결합
- 리랭킹 적용 — Cross-Encoder 또는 Cohere Rerank
- 쿼리 변환 추가 — HyDE, Step-back Prompting 등
- Agentic 전환 검토 — 복잡한 질의가 많은 경우에만 도입
마무리
RAG 파이프라인 최적화는 단순 벡터 검색을 넘어서는 과정이다. 핵심을 정리하면 다음과 같다.
- 청킹은 검색 품질의 기반이다. 시맨틱 청킹과 적절한 오버랩을 적용하라.
- 하이브리드 검색으로 키워드 매칭과 의미 검색의 장점을 모두 취하라.
- 리랭킹은 가장 비용 효율적인 성능 향상 수단이다. 반드시 도입하라.
- Agentic RAG는 복잡한 멀티홉 질의가 필요한 경우에 도입을 검토하라.
Naive RAG에서 시작해 단계적으로 Advanced → Agentic으로 진화시키는 것이 실무적으로 가장 안정적인 접근 방식이다. 각 단계에서 검색 정확도를 측정하고 병목을 찾아 해결하는 반복적 개선이 성공의 열쇠다.
이 글이 도움이 되셨나요?
Buy me a coffee
답글 남기기