Retrieval-Augmented Generation: LLM을 귀하의 데이터에 연결하기
발행: (2025년 12월 7일 오후 12:00 GMT+9)
6 min read
원문: Dev.to
Source: Dev.to
기술 약어 참고표
| 약어 | 의미 |
|---|---|
| API | 애플리케이션 프로그래밍 인터페이스 |
| BERT | 양방향 인코더 표현 변환기 |
| FAISS | 페이스북 AI 유사도 검색 |
| GPU | 그래픽 처리 장치 |
| JSON | 자바스크립트 객체 표기법 |
| LLM | 대형 언어 모델 |
| RAG | 검색‑증강 생성 |
| ROI | 투자 대비 수익 |
| SQL | 구조화 질의 언어 |
| VRAM | 비디오 랜덤 액세스 메모리 |
왜 LLM이 외부 데이터가 필요한가
대형 언어 모델(LLM)은 근본적인 한계가 있다: 지식이 학습 시점에 고정된다는 점이다.
GPT‑4에 물어보기:
- “우리 Q3 매출은 어땠나요?” → ❌ 데이터가 없음
- “우리 직원 핸드북에 뭐가 있나요?” → ❌ 문서가 없음
- “어제 티켓을 보여줘” → ❌ 실시간 접근 불가
- “티켓 #45632에서 고객이 뭐라고 했나요?” → ❌ 데이터베이스를 볼 수 없음
LLM은 당신의 특정 데이터에 대해 알지 못한다.
솔루션 개요
| 접근 방식 | 장점 | 단점 |
|---|---|---|
| 파인‑튜닝 | 모델을 데이터에 맞춤 | 비용 많이 들고, 느리며, 정적 |
| 긴 컨텍스트 | 프롬프트만으로 간단 | 컨텍스트 창 제한, 비용 많이 듦 |
| 검색‑증강 생성 (RAG) | 관련 데이터를 검색한 뒤 생성 | 유연하고, 확장 가능하며, 비용 효율적 |
이 글에서는 RAG에 초점을 맞춘다. 이는 프로덕션 시스템에 가장 실용적인 접근 방식이다.
검색‑증강 생성(RAG)이란?
RAG는 LLM을 대규모 사유 데이터와 연결한다. 세 단계로 구성된다:
- 인덱싱 (오프라인) – 문서를 벡터 임베딩으로 변환하고 벡터 데이터베이스에 저장한다.
- 검색 (쿼리 시점) – 사용자 쿼리를 임베딩하고, 벡터 저장소를 검색해 상위 k개의 가장 관련성 높은 청크를 반환한다.
- 생성 – 검색된 청크와 원본 쿼리를 LLM에 전달해 최종 답변을 만든다.
실생활 비유: 연구 보조원
| 단계 | 보조원이 하는 일 |
|---|---|
| 인덱싱 | 모든 회사 문서를 읽고, 정리된 메모를 만들고, 빠르게 찾을 수 있게 파일링한다. |
| 검색 | 질문을 하면 메모를 검색해 가장 관련 있는 문서를 꺼낸다. |
| 생성 | 검색된 문서를 읽고 답변을 구성해 응답한다. |
RAG 워크플로우 다이어그램
┌─────────────────────────────────────────────────────────┐
│ INDEXING (Offline) │
├─────────────────────────────────────────────────────────┤
│ Documents → Chunking → Embeddings → Vector Database │
│ "handbook.pdf" → paragraphs → vector representations │
│ "policies.docx" → paragraphs → vector representations │
│ "faqs.md" → paragraphs → vector representations │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ RETRIEVAL (Query Time) │
├─────────────────────────────────────────────────────────┤
│ User Query → Embed Query → Search Vector DB → Top‑K │
│ "What's the return policy?" → vector → find similar chunks │
│ → return 5 most relevant chunks │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ GENERATION (Response) │
├─────────────────────────────────────────────────────────┤
│ Retrieved Docs + Query → LLM → Final Answer │
│ Context: [5 relevant chunks about returns] │
│ Question: "What is the return policy?" │
│ LLM Output: "Our return policy allows returns within 30 │
│ days of purchase. Items must be in original condition..." │
└─────────────────────────────────────────────────────────┘
설치
pip install langchain
pip install chromadb # Vector database
pip install sentence-transformers # Embeddings
pip install litellm # LLM interface
pip install pypdf # PDF processing
파이썬 예시: 문서 로드 및 청크 만들기
from typing import List
import re
def load_documents(file_paths: List[str]) -> List[str]:
"""Load plain‑text documents from a list of file paths."""
documents = []
for path in file_paths:
with open(path, "r", encoding="utf-8") as f:
documents.append(f.read())
return documents
def chunk_text(text: str, chunk_size: int = 500, overlap: int = 50) -> List[str]:
"""
Split `text` into overlapping chunks.
Parameters
----------
text : str
Input text to chunk.
chunk_size : int, default 500
Target size of each chunk (characters).
overlap : int, default 50
Number of characters to overlap between consecutive chunks.
"""
# Simple sentence‑aware chunking
sentences = re.split(r"(? chunk_size and current_chunk:
chunks.append(" ".join(current_chunk))
# Preserve overlap for the next chunk
overlap_sentences = []
overlap_len = 0
for s in reversed(current_chunk):
if overlap_len + len(s) List[dict]:
"""
Retrieve the `top_k` most similar chunks for `query_text`.
Returns
-------
List[dict] with keys `id`, `document`, `metadata`, `distance`.
"""
query_emb = self.embedding_model.encode([query_text]).tolist()
results = self.collection.query(
query_embeddings=query_emb,
n_results=top_k,
include=["documents", "metadatas", "distances", "ids"]
)
# Re‑format results for easier consumption
hits = []
for i in range(len(results["ids"][0])):
hits.append({
"id": results["ids"][0][i],
"document": results["documents"][0][i],
"metadata": results["metadatas"][0][i],
"distance": results["distances"][0][i],
})
return hits
이제 청크 로직을 VectorStore와 결합해 전체 RAG 파이프라인을 구축할 수 있다:
- 원시 문서를 로드한다.
chunk_text로 청크화한다.- 청크를
VectorStore에 삽입한다. - 쿼리 시점에 사용자의 질문을 임베딩하고, 상위 k개의 청크를 검색한 뒤, 연결된 컨텍스트와 원본 질문을 LLM에 전달한다(예:
litellm또는langchain사용).
기사 끝.