RAG (Retrieval-Augmented Generation)는 외부 지식 베이스에서 관련 정보를 검색(Retrieve)하여, 그 정보를 바탕으로 LLM이 답변을 생성(Generate)하는 기술입니다.
LLM은 학습 데이터에 기반하여 답변하기 때문에:
RAG를 사용하면:
RAG 시스템은 4단계로 구축됩니다:
Load → Split → Embed → Store → Retrieve
!pip install -q -U google-genai
!pip install -U langchain-google-genai
!pip install -qU langchain-community pdfplumber
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: 페이지 번호, 출처 등 메타 정보!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
문서를 작은 단위(Chunk)로 나누는 단계입니다.
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: 청크 간 겹치는 문자 수 (문맥 유지에 도움)텍스트를 고정된 길이의 숫자 벡터로 변환하는 단계입니다.
"Tesla 투자 비중" → [0.234, -0.156, 0.892, ..., 0.445] (예: 768차원 벡터)
의미가 유사한 텍스트는 벡터 공간에서 가까이 위치합니다.
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를 사용하면 동일한 텍스트를 반복해서 임베딩하지 않아 비용과 시간을 절약할 수 있습니다.
임베딩된 벡터를 저장하고 검색하는 단계입니다.
from langchain_core.vectorstores import InMemoryVectorStore
vectorstore = InMemoryVectorStore.from_documents(
docs,
cached_embeddings
)
# 유사도 검색
query = "Tesla 투자 비중이 얼마나 되나요?"
results = vectorstore.similarity_search(query)
print(results)
특징:
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})검색 시 사용되는 유사도 측정 방법입니다.
벡터 간의 각도를 측정합니다.
코사인 유사도 = cos(θ) = (A · B) / (||A|| ||B||)
벡터 간의 직선 거리를 측정합니다.
거리 = √[(x₁-x₂)² + (y₁-y₂)² + ...]
벡터의 곱을 측정합니다.
내적 = A · B = a₁b₁ + a₂b₂ + ...
Agentic RAG는 Agent가 필요에 따라 RAG 검색을 동적으로 수행하는 방식입니다.
| 구분 | 전통적 RAG | Agentic RAG |
|---|---|---|
| 검색 시점 | 항상 검색 수행 | 필요할 때만 검색 |
| 판단 주체 | 사전 정의된 규칙 | Agent가 자율 판단 |
| 유연성 | 낮음 | 높음 |
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 비중은?"}]}
)
장점:
Hybrid RAG는 여러 검색 방법을 결합하여 더 정확한 검색을 수행합니다.
Keyword Search (키워드 검색) + Vector Search (벡터 검색)
Dense Retrieval + Sparse Retrieval
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 투자 비중은?")
장점:
# 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를 활용하면 LLM의 한계를 극복하고, 특정 도메인에 특화된 AI 시스템을 구축할 수 있습니다! 🚀