LangChain RAG (Retrieval-Augmented Generation) 가이드

CHZEE93·2026년 2월 18일

AI개발

목록 보기
7/11

RAG란?

RAG (Retrieval-Augmented Generation)는 외부 지식 베이스에서 관련 정보를 검색(Retrieve)하여, 그 정보를 바탕으로 LLM이 답변을 생성(Generate)하는 기술입니다.

RAG의 필요성

LLM은 학습 데이터에 기반하여 답변하기 때문에:

  • ❌ 학습 이후의 최신 정보를 모름
  • ❌ 특정 도메인의 전문 지식이 부족할 수 있음
  • ❌ 환각(Hallucination) 문제 발생 가능

RAG를 사용하면:

  • ✅ 실시간 정보 활용 가능
  • ✅ 특정 문서/데이터베이스 기반 답변
  • ✅ 환각 문제 완화 (근거 있는 답변)

RAG 구축 순서

RAG 시스템은 4단계로 구축됩니다:

Load → Split → Embed → Store → Retrieve

1. Load (문서 로드)

  • 문서를 Document 객체로 전환
  • PDF, TXT, CSV, 웹페이지 등 다양한 소스 지원

2. Split (문서 분할)

  • Document 객체를 작은 Chunk로 전환
  • 검색 효율성과 정확도 향상

3. Embed (임베딩)

  • 작은 말뭉치를 고정된 길이의 숫자(벡터)로 변환
  • Vectorizing 과정

4. Store (저장)

  • 임베딩한 결과를 벡터 데이터베이스에 저장
  • 빠른 검색을 위한 인덱싱

5. Retrieve (검색)

  • 질문과 유사한 문서를 찾아서 반환
  • 유사도 측정 방법 활용

1. 환경 설정

!pip install -q -U google-genai
!pip install -U langchain-google-genai
!pip install -qU langchain-community pdfplumber

2. Load - 문서 로드

2.1 PDFPlumberLoader 사용

from langchain_community.document_loaders import PDFPlumberLoader

loader = PDFPlumberLoader("./ARK_INNOVATION_ETF_ARKK_HOLDINGS.pdf")
docs = loader.load()

print(f"총 페이지 수: {len(docs)}")

Document 객체 구조 확인:

import pprint

# 메타데이터 확인
pprint.pp(docs[0].metadata)

# 내용 일부 확인
print(docs[0].page_content[:500])

Document 객체는 다음 두 가지로 구성됩니다:

  • page_content: 실제 텍스트 내용
  • metadata: 페이지 번호, 출처 등 메타 정보

2.2 PyMuPDFLoader 사용 (대안)

!pip install -qU pymupdf

from langchain_community.document_loaders import PyMuPDFLoader

loader = PyMuPDFLoader("./ARK_INNOVATION_ETF_ARKK_HOLDINGS.pdf")
docs = loader.load()

포인트: 다양한 Loader가 있으며, 문서 형식과 요구사항에 따라 선택할 수 있습니다.

참고 문서: LangChain Document Loaders


3. Split - 문서 분할

문서를 작은 단위(Chunk)로 나누는 단계입니다.

왜 Split이 필요한가?

  • 전체 문서는 너무 길어서 검색 효율이 떨어짐
  • 관련 없는 내용이 섞여 있으면 정확도 하락
  • LLM의 입력 토큰 제한 회피

RecursiveCharacterTextSplitter

from langchain_text_splitters import RecursiveCharacterTextSplitter

sample_text = """
대규모 언어 모델(LLM)은 방대한 텍스트 데이터를 학습해 인간과 유사한 방식으로 언어를 이해하고 생성할 수 있는 인공지능 시스템이다. 이러한 모델은 단어와 문장 사이의 확률적 관계를 학습함으로써 질문에 답하거나 글을 작성하고, 요약·번역·코드 생성 같은 다양한 작업을 수행할 수 있다. 최근에는 모델 규모와 학습 데이터가 급격히 증가하면서 성능 또한 빠르게 향상되고 있다.

LLM의 핵심 원리는 딥러닝 구조 중 하나인 트랜스포머 아키텍처에 기반한다. 이 구조는 문맥을 동시에 고려하는 어텐션 메커니즘을 사용하여 긴 문장이나 복잡한 문단도 효과적으로 처리한다. 그 결과, 단순한 문장 완성을 넘어 논리적 추론이나 창의적 글쓰기처럼 고차원적인 언어 작업까지 가능해졌다.

하지만 LLM은 여전히 한계도 지니고 있다. 학습 데이터에 포함된 편향을 반영할 수 있으며, 실제 사실과 다른 내용을 그럴듯하게 생성하는 환각(hallucination) 문제가 발생하기도 한다. 따라서 LLM을 실무나 연구에 활용할 때는 결과를 검증하고 적절한 안전 장치를 마련하는 것이 중요하다.
"""

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=100,        # 각 청크의 최대 크기
    chunk_overlap=0        # 청크 간 겹치는 부분
)

recursive_text = text_splitter.split_text(sample_text)

print(f"총 chunk: {len(recursive_text)}\n")
for i, doc in enumerate(recursive_text):
    print(f"chunk {i+1}")
    print(doc)
    print()

파라미터 설명:

  • chunk_size: 각 청크의 최대 문자 수
  • chunk_overlap: 청크 간 겹치는 문자 수 (문맥 유지에 도움)

4. Embed - 임베딩

텍스트를 고정된 길이의 숫자 벡터로 변환하는 단계입니다.

임베딩이란?

"Tesla 투자 비중" → [0.234, -0.156, 0.892, ..., 0.445] (예: 768차원 벡터)

의미가 유사한 텍스트는 벡터 공간에서 가까이 위치합니다.

Google Generative AI Embeddings 사용

import os

os.environ["GOOGLE_API_KEY"] = "YOUR_API_KEY_HERE"

!pip install -U langchain-google-genai
!pip install -U langchain_classic

from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_classic.embeddings import CacheBackedEmbeddings
from langchain_classic.storage import LocalFileStore

# 기본 임베딩 모델
underlying_embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")

# 캐시 저장소 (중복 임베딩 방지)
store = LocalFileStore("./cache/")

# 캐시 적용
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings,
    store,
    namespace=underlying_embeddings.model
)

포인트: CacheBackedEmbeddings를 사용하면 동일한 텍스트를 반복해서 임베딩하지 않아 비용과 시간을 절약할 수 있습니다.


5. Store - 벡터 저장소

임베딩된 벡터를 저장하고 검색하는 단계입니다.

5.1 InMemoryVectorStore (메모리 기반)

from langchain_core.vectorstores import InMemoryVectorStore

vectorstore = InMemoryVectorStore.from_documents(
    docs,
    cached_embeddings
)

# 유사도 검색
query = "Tesla 투자 비중이 얼마나 되나요?"
results = vectorstore.similarity_search(query)
print(results)

특징:

  • 메모리에만 저장 (프로그램 종료 시 소멸)
  • 빠르지만 영구 저장 불가
  • 프로토타이핑과 테스트에 적합

5.2 Chroma (영구 저장)

from langchain_chroma import Chroma

db = Chroma(
    collection_name="langchain",
    embedding_function=cached_embeddings,
    persist_directory="./chroma_db",  # 디스크에 저장
)

# Retriever 생성
retriever = db.as_retriever(
    search_type="similarity_score_threshold",  # 또는 "mmr"
    search_kwargs={"score_threshold": 0.4}     # 또는 {"k": 3}
)

# 검색 실행
results = retriever.invoke("ARKK의 Tesla 투자 비중은?")
print(results)

search_type 옵션:

  • similarity: 단순 유사도 기반 (기본값)
  • similarity_score_threshold: 임계값 이상만 반환
  • mmr (Maximal Marginal Relevance): 다양성을 고려한 검색

search_kwargs 옵션:

  • k: 반환할 문서 개수 (예: {"k": 3})
  • score_threshold: 최소 유사도 점수 (예: {"score_threshold": 0.4})

6. Retrieve - 유사도 측정

검색 시 사용되는 유사도 측정 방법입니다.

(1) 코사인 유사도 (Cosine Similarity)

벡터 간의 각도를 측정합니다.

코사인 유사도 = cos(θ) = (A · B) / (||A|| ||B||)
  • 범위: -1 ~ 1 (1에 가까울수록 유사)
  • 벡터의 크기와 무관하게 방향만 비교
  • 가장 많이 사용됨

(2) 유클리드 거리 (Euclidean Distance)

벡터 간의 직선 거리를 측정합니다.

거리 = √[(x₁-x₂)² + (y₁-y₂)² + ...]
  • 거리가 작을수록 유사
  • 벡터의 크기에 영향을 받음

(3) 내적 (Dot Product)

벡터의 을 측정합니다.

내적 = A · B = a₁b₁ + a₂b₂ + ...
  • 값이 클수록 유사
  • 정규화되지 않은 벡터에서는 부정확할 수 있음

7. Agentic RAG

Agentic RAG는 Agent가 필요에 따라 RAG 검색을 동적으로 수행하는 방식입니다.

전통적 RAG vs Agentic RAG

구분전통적 RAGAgentic RAG
검색 시점항상 검색 수행필요할 때만 검색
판단 주체사전 정의된 규칙Agent가 자율 판단
유연성낮음높음

Agentic RAG 예시

from langchain.tools import tool

@tool
def search_documents(query: str) -> str:
    """문서 검색 도구"""
    results = retriever.invoke(query)
    return "\n".join([doc.page_content for doc in results])

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model

model = init_chat_model("gemini-2.5-flash", model_provider="google_genai")

agent = create_agent(
    model,
    tools=[search_documents],
    system_prompt="필요할 때만 문서를 검색하세요."
)

# Agent가 판단하여 검색 여부 결정
response = agent.invoke(
    {"messages": [{"role": "user", "content": "ARKK의 Tesla 비중은?"}]}
)

장점:

  • Agent가 질문을 분석하고 필요할 때만 검색
  • 여러 번 검색하거나, 다른 Tool과 조합 가능
  • 복잡한 질문에 대해 단계적으로 해결

8. Hybrid RAG

Hybrid RAG는 여러 검색 방법을 결합하여 더 정확한 검색을 수행합니다.

결합 방식

  1. Keyword Search (키워드 검색) + Vector Search (벡터 검색)

    • 키워드: 정확한 단어 매칭 (BM25 알고리즘)
    • 벡터: 의미적 유사도 매칭
    • 두 결과를 가중치 합산
  2. Dense Retrieval + Sparse Retrieval

    • Dense: 임베딩 벡터 기반
    • Sparse: TF-IDF 기반

Hybrid RAG 예시

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# BM25 (키워드 검색)
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 3

# Vector Search (의미 검색)
vector_retriever = db.as_retriever(search_kwargs={"k": 3})

# 하이브리드: 두 검색 결과 결합
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.5, 0.5]  # 각각 50% 가중치
)

results = ensemble_retriever.invoke("ARKK의 Tesla 투자 비중은?")

장점:

  • 정확한 키워드와 의미적 유사도를 모두 활용
  • 단일 검색보다 높은 정확도
  • 다양한 질문 유형에 대응 가능

RAG 파이프라인 전체 코드

# 1. Load
from langchain_community.document_loaders import PDFPlumberLoader
loader = PDFPlumberLoader("./document.pdf")
docs = loader.load()

# 2. Split
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
splits = text_splitter.split_documents(docs)

# 3. Embed
from langchain_google_genai import GoogleGenerativeAIEmbeddings
embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")

# 4. Store
from langchain_chroma import Chroma
vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

# 5. Retrieve
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# 6. Generate (LLM과 결합)
from langchain.chains import RetrievalQA
from langchain.chat_models import init_chat_model

model = init_chat_model("gemini-2.5-flash", model_provider="google_genai")

qa_chain = RetrievalQA.from_chain_type(
    llm=model,
    retriever=retriever,
    return_source_documents=True
)

result = qa_chain.invoke({"query": "Tesla 투자 비중은?"})
print(result["result"])

마무리

이 실습을 통해 배운 내용:

  • RAG의 개념: 검색 + 생성의 결합
  • RAG 구축 순서: Load → Split → Embed → Store → Retrieve
  • Document Loaders: PDF, TXT 등 다양한 문서 로드
  • Text Splitters: 효율적인 청킹 전략
  • Embeddings: 텍스트를 벡터로 변환
  • Vector Stores: InMemoryVectorStore, Chroma
  • 유사도 측정: 코사인 유사도, 유클리드 거리, 내적
  • Agentic RAG: Agent가 필요시 검색 수행
  • Hybrid RAG: 키워드 + 벡터 검색 결합

RAG를 활용하면 LLM의 한계를 극복하고, 특정 도메인에 특화된 AI 시스템을 구축할 수 있습니다! 🚀


참고 자료

추가 팁

  • PDF가 복잡하면 PyMuPDF, 표가 많으면 PDFPlumber 사용
  • chunk_size는 보통 500-1000자가 적당 (도메인에 따라 조정)
  • chunk_overlap은 chunk_size의 10-20% 권장
  • 프로덕션에서는 캐시와 영구 저장소(Chroma, Pinecone 등) 필수
  • 유사도 임계값은 실험을 통해 최적값 찾기 (보통 0.3-0.5)
profile
치즈 키우는 사람

0개의 댓글