RAG 파이프라인 최적화 완전 가이드: Naive RAG부터 Agentic RAG까지

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)으로 결합한다.

RRF(d)=rR1k+r(d)RRF(d) = \sum_{r \in R} \frac{1}{k + r(d)}

  • dd: 문서
  • RR: 각 검색 방식의 결과 랭킹 집합
  • r(d)r(d): 해당 검색 방식에서 문서 dd의 순위
  • kk: 스무딩 상수 (보통 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. 1차 검색: 하이브리드 검색으로 상위 20~50개 후보 추출 (빠르지만 덜 정밀)
  2. 리랭킹: Cross-Encoder가 질의-문서 쌍을 직접 비교 (느리지만 정밀)
  3. 최종 선택: 상위 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 파이프라인을 구축할 때 다음 순서로 최적화를 진행하라.

  1. 청킹 전략 선택 — 문서 특성에 맞는 분할 방식 적용
  2. 임베딩 모델 선정 — 한국어라면 다국어 모델(multilingual-e5-large 등) 권장
  3. 하이브리드 검색 도입 — BM25 + 벡터 검색 결합
  4. 리랭킹 적용 — Cross-Encoder 또는 Cohere Rerank
  5. 쿼리 변환 추가 — HyDE, Step-back Prompting 등
  6. Agentic 전환 검토 — 복잡한 질의가 많은 경우에만 도입

마무리

RAG 파이프라인 최적화는 단순 벡터 검색을 넘어서는 과정이다. 핵심을 정리하면 다음과 같다.

  • 청킹은 검색 품질의 기반이다. 시맨틱 청킹과 적절한 오버랩을 적용하라.
  • 하이브리드 검색으로 키워드 매칭과 의미 검색의 장점을 모두 취하라.
  • 리랭킹은 가장 비용 효율적인 성능 향상 수단이다. 반드시 도입하라.
  • Agentic RAG는 복잡한 멀티홉 질의가 필요한 경우에 도입을 검토하라.

Naive RAG에서 시작해 단계적으로 Advanced → Agentic으로 진화시키는 것이 실무적으로 가장 안정적인 접근 방식이다. 각 단계에서 검색 정확도를 측정하고 병목을 찾아 해결하는 반복적 개선이 성공의 열쇠다.

이 글이 도움이 되셨나요?

Buy me a coffee

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

TODAY 21 | TOTAL 220