# RAG 시스템: 작동 원리와 최소 Python 구현
언어 모델은 훈련 데이터의 한계로 인해 지식이 고정되어 있고, 최신 사건을 다루지 못하며, 환각(hallucination)—사실을 지어내는 문제—에 취약합니다. 컨텍스트 창의 입력 제한으로 상황이 더 악화되죠. 검색 증강 생성(Retrieval-Augmented Generation, RAG)은 생성 과정에서 외부 소스에서 관련 컨텍스트를 가져와 오류를 줄이고 출처를 인용합니다.
RAG은 문서, 데이터베이스, 코드 저장소를 연결해 응답을 검증 가능하고 최신 상태로 만듭니다. 모델이 훈련되지 않은 전문 분야에서 필수적입니다.
문서 청킹
청킹은 텍스트를 인덱싱을 위한 조각(청크)으로 나누는 과정입니다. LLM은 제한된 청크만 처리하므로 청크 품질이 사실 접근성을 결정합니다.
큰 청크는 노이즈를 증가시키고 비용을 올리며 검색 정확도를 떨어뜨립니다. 작은 청크는 컨텍스트를 끊어 관련 아이디어를 분리합니다. 전략:
- 고정 문자나 토큰 길이.
- 의미적 경계(단락, 제목).
- 연속성을 위한 오버랩.
코퍼스와 쿼리에 맞게 매개변수를 실험적으로 조정하세요.
RAG 워크플로 단계
RAG은 전처리, 검색, 생성을 포괄합니다:
- 인덱싱: 청크를 벡터로 변환해 벡터 DB에 저장.
- 검색: 쿼리를 벡터화하고 상위 관련 청크 추출.
- 증강: 결과 순위화, 중복 및 노이즈 제거.
- 생성: 컨텍스트를 쿼리에 추가해 LLM에 전달.
기본 Naive RAG은 단순합니다. 고급 RAG은 쿼리 재작성과 재순위를 추가합니다. 모듈러 RAG은 라우팅과 결과 융합 같은 모듈을 사용합니다.
RAG vs. 파인튜닝
RAG은 모델 변경 없이 지식 접근을 확장합니다. 반면 파인튜닝은 데이터와 막대한 컴퓨팅이 필요합니다. 프롬프트 엔지니어링은 쿼리를 조정하지만 외부 데이터를 추가하지 않습니다.
| 접근법 | 장점 | 단점 |
|----------|------|------|
| RAG | 최신 데이터, 저비용, 출처 인용 | 검색 품질 의존 |
| 파인튜닝 | 깊이 있는 맞춤화 | 자원 집약적, 오래됨 |
RAG은 동적 데이터에서 빛을 발합니다. 파인튜닝은 정적 도메인에 적합합니다.
최소 RAG 파이프라인: 실습
위키피디아(500개 기사)와 Qwen2.5-3B-Instruct를 사용해 Python으로 Naive RAG을 구축합니다. LangChain, Chroma, sentence-transformers 활용.
1단계: 로드 및 청킹
import torch
from datasets import load_dataset
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
device = "cuda" if torch.cuda.is_available() else "cpu"
dataset = load_dataset(
"wikimedia/wikipedia",
"20231101.ru",
split="train[:500]"
)
documents = [
Document(
page_content=row["text"],
metadata={"title": row["title"]}
)
for row in dataset
]
splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100
)
chunks = splitter.split_documents(documents)
print("Chunks:", len(chunks))
800자 청크에 100자 오버랩으로 약 17,892개 청크 생성.
2단계: 벡터 인덱스
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
embedding_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
model_kwargs={"device": device}
)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embedding_model
)
다국어 MiniLM으로 임베딩, Chroma로 저장.
3단계: 검색
import textwrap
def search(query, k=3, width=80):
results = vectorstore.similarity_search(query, k=k)
for i, doc in enumerate(results, 1):
print(f"\n--- Result {i} ---")
print("Title:", doc.metadata["title"])
print(textwrap.fill(doc.page_content[:1000], width))
search("Who is Pushkin?")
푸시킨 관련 기사에서 관련 스니펫 추출.
4단계: 생성
검색된 청크로 LLM 생성(코드 생략; 전체 버전은 소스 참조).
주요 요점
- RAG은 외부 컨텍스트로 환각 줄임, 모델 수정 불필요.
- 성공은 청킹과 임베딩에 달림: 도메인에 맞게 조정.
- Naive RAG부터 시작해 프로덕션에서 고급/모듈러로 확장.
- LangChain으로 프로토타이핑 간편.
- 실제 데이터셋으로 테스트하며 최적화.
— Editorial Team
아직 댓글이 없습니다.