RAG - 완전한 실무 가이드
요약
RAG(Retrieval Augmented Generation, 검색 증강 생성)는 LLM의 한계점인 지식 구식화, 환각 현상, 비공개 데이터 접근 불가 문제를 해결하는 핵심 아키텍처입니다. RAG는 외부 지식을 검색하여 이를 프롬프트에 주입함으로써 모델이 근거 기반의 정확하고 도메인 특화된 답변을 생성하도록 돕습니다. 이 과정은 원시 문서 → 청킹 → 임베딩 → 벡터 DB 저장 → 유사도 검색 → 프롬프트 구성 → LLM 생성을 거치는 단계로 이루어집니다.
핵심 포인트
- RAG는 정보 검색(IR)과 LLM 응답 생성의 결합 시스템 디자인 패턴이다.
- LLM은 학습 데이터에 의존하여 환각 현상이나 최신/비공개 데이터 접근 불가 등의 한계가 있다.
- 핵심 아키텍처는 청킹, 임베딩 모델, 벡터 데이터베이스(Pinecone, Weaviate 등), 리트리버를 포함한다.
- 문서의 의미론적 이해와 유사도 검색을 위해 텍스트를 벡터로 변환하는 '임베딩' 과정이 필수적이다.
- 벡터 DB의 차원(Dimension)은 사용된 임베딩 모델의 차원과 반드시 일치해야 한다.
서론
Retrieval Augmented Generation (RAG, 검색 증강 생성)은 오늘날 AI 분야에서 가장 큰 기둥 중 하나입니다. 주로 대기업에서 더 나은 내부 문서 관리 및 검색을 위해 사용됩니다. 이 글에서는 더 나은 이해를 돕기 위해 코드 스니펫과 함께 몇 가지 RAG 개념을 설명하고, 제가 직접 RAG를 구현할 때 직면했던 몇 가지 일반적인 문제들과 그에 대한 해결책을 함께 제시하겠습니다.
RAG란 무엇인가?
RAG (R etrieval A ugmented G eneration)는 다음을 결합하는 시스템 디자인 패턴입니다:
- 정보 검색 (Information retrieval, 관련 지식 찾기)
- 대규모 언어 모델 (Large Language Models (LLMs), 응답 생성)
모델이 학습 과정에서 배운 것에만 의존하는 대신, RAG 시스템은 외부 지식을 검색하여 프롬프트 (Prompt)에 주입합니다.
전통적인 LLM
질문 ↓
모델 메모리 (학습 데이터) ↓
답변
문제점:
- 지식이 구식일 수 있음
- 환각 (Hallucinations) 발생
- 기업의 비공개 데이터에 접근할 수 없음
RAG 기반 LLM
질문 ↓
관련 지식 검색 ↓
프롬프트에 컨텍스트 추가 ↓
LLM이 근거 있는 답변 생성 ↓
이를 통해 답변은 다음과 같은 특징을 갖습니다:
- 더 정확함
- 문서에 기반함 (Grounded)
- 맞춤형 도메인 특화 가능
왜 RAG인가?
LLM은 강력하지만 한계가 있습니다. 일반적인 문제점은 다음과 같습니다:
-
환각 (Hallucinations)
모델이 사실을 지어냅니다. 예시: 질문: Company X의 설립자는 누구인가요? 답변: John Smith. 설령 John Smith라는 사람이 존재하지 않더라도 말입니다. -
지식 컷오프 (Knowledge Cutoff)
모델은 학습된 내용만 알고 있습니다. 모델은 다음을 자동으로 알지 못합니다:
- 귀하의 PDF
- 내부 문서
- GitHub 저장소
- 최근 업데이트
- 비공개 데이터 (Private Data)
기업은 다음 사항에 대한 AI를 필요로 합니다:
- 내부 문서
- 정책
- 티켓 (Tickets)
- 코드베이스
RAG가 이 문제를 해결합니다.
핵심 아키텍처 (Core Architecture)
RAG 시스템은 보통 다음을 포함합니다:
- 문서 (Documents)
- 청킹 시스템 (Chunking system)
- 임베딩 모델 (Embedding model)
- 벡터 데이터베이스 (Vector database)
- 리트리버 (Retriever)
- 프롬프트 생성기 (Prompt constructor)
- LLM
아키텍처:
문서 ↓
청킹 (Chunking) ↓
임베딩 (Embeddings) ↓
벡터 데이터베이스 ↓
사용자 질문 ↓
질문 임베딩 ↓
유사도 검색 (Similarity Search) ↓
관련 청크 (Relevant Chunks) ↓
프롬프트 구성 ↓
LLM ↓
답변
RAG의 단계별 작동 방식
- 문서 (Documents)
시스템은 원시 문서(Raw documents)로 시작합니다.
예시: TXT 파일, PDF, Markdown 파일, HTML 페이지, GitHub 리포지토리(repos)
예시 텍스트: RAG 시스템은 LLM을 위한 관련 정보를 검색하기 위해 벡터 데이터베이스(Vector databases)를 사용합니다.
-
청킹 (Chunking)
문서는 더 작은 섹션으로 분할됩니다. 왜 그럴까요? 책 전체를 임베딩(Embedding)하는 것은 비효율적이기 때문입니다. 대신 다음과 같이 수행합니다:
대형 문서 ↓ 작은 청크 (Small Chunks)
예시: 청크 1 → 서론, 청크 2 → 임베딩 (Embeddings), 청크 3 → Pinecone -
임베딩 (Embeddings)
모든 청크는 벡터(Vector)가 됩니다.
예시: "RAG systems use retrieval"은 다음과 같이 변합니다: [0.12, -0.77, 0.48, ...] -
벡터 데이터베이스에 저장 (Store in Vector Database)
벡터는 다음 서비스들에 저장됩니다:
Pinecone, Weaviate, Qdrant, Chroma, FAISS -
사용자 질문 (User Question)
예시: "임베딩(embeddings)이란 무엇인가요?"
질문 또한 벡터가 됩니다. -
유사도 검색 (Similarity Search)
벡터 데이터베이스는 다음을 찾아냅니다:
수학적 유사도(Mathematical similarity)를 기반으로 가장 유사한 청크들. -
프롬프트 구성 (Prompt Construction)
검색된 청크들이 프롬프트(Prompt)에 주입됩니다.
예시: 문맥(Context): 임베딩은 벡터 표현입니다. 질문(Question): 임베딩이란 무엇인가요? -
LLM 생성 (LLM Generation)
LLM은 검색된 문맥(Context)을 사용하여 답변을 생성합니다.
주요 개념 및 정의
-
임베딩 (Embedding)
텍스트의 수치적 의미 표현(Numerical semantic representation).
예시: "Machine learning" ↓ [0.12, -0.34, ...]
목적: 의미론적 이해(Semantic understanding), 유사도 검색(Similarity search) -
벡터 (Vector)
숫자들의 순서가 있는 목록.
예시: [ 0.12 , - 0.55 , 0.91 ] -
차원 (Dimension)
벡터 내부의 값의 개수.
예시: 768차원(768-dimensional) 벡터는 768개의 숫자를 의미합니다.
중요한 이유: 벡터 DB의 차원은 임베딩 차원과 일치해야 합니다.
예시: nomic-embed-text → 768, Pinecone 인덱스(index) → 반드시 768이어야 함 -
의미론적 검색 (Semantic Search)
정확한 키워드가 아닌 의미에 의한 검색.
예시: 질문: "메모리는 어떻게 작동하나요?"
검색 가능 내용: "에이전트(Agents)는 메모리 시스템을 사용하여 문맥(Context)을 유지합니다." -
유사도 점수 (Similarity Score)
벡터 간의 근접성을 측정합니다. 점수가 높을수록 관련성이 높습니다.
Top-K: 검색할 결과의 개수.
예시: top_k = 5는 가장 좋은 5개의 청크를 반환함을 의미합니다. -
메타데이터 (Metadata)
벡터에 첨부된 추가 정보.
예시: { "text" : "Embeddings are vectors" , "source" : "notes.txt" , "topic" : "rag" }
임베딩 설명
임베딩은 텍스트를 수학적 의미로 변환합니다.
유사한 의미를 가진 텍스트는 서로 가까이 위치하게 됩니다. 예시: "How to build AI agents"와 "Creating autonomous agents"는 서로 인접한 벡터 (vector)가 됩니다.
Ollama로 임베딩 (Embeddings) 생성하기
import ollama
def generate_embedding(text):
response = ollama.embeddings(model="nomic-embed-text", prompt=text)
return response["embedding"]
테스트:
embedding = generate_embedding("What is RAG?")
print(len(embedding))
print(embedding[:10])
위에서 본 코드 스니펫 (code snippets)은 제가 구현한 RAG 프로젝트의 일부입니다. 소스 코드는 여기에서 확인할 수 있습니다.
벡터 데이터베이스 (Vector Databases)
벡터 데이터베이스는 임베딩을 저장합니다.
- 전통적인 DB: 정확한 값으로 검색
- 벡터 DB: 유사성 (similarity)으로 검색
일반적인 벡터 DB: Pinecone, Qdrant, Weaviate, Chroma, FAISS
청킹 (Chunking)
청킹은 문서를 분할하는 것입니다.
-
청킹이 중요한 이유
잘못된 청킹 = 잘못된 검색 (retrieval).
예시 문제:
청크 1: RAG systems use semantic
청크 2: search through vectors
의미가 끊어지게 됩니다. -
문자 기반 청킹 (Character-Based Chunking)
def chunk_text(text, chunk_size=800, overlap=150):
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
chunks.append(chunk)
start += chunk_size - overlap
return chunks
- 오버랩 (Overlap)
문맥 (context)을 보존합니다.
예시: 청크 1 → 0-800, 청크 2 → 650-1450
오버랩: 150자
유사도 검색 (Similarity Search)
Pinecone은 벡터를 비교합니다. 주로 다음을 사용합니다:
코사인 유사도 (Cosine Similarity)
각도의 유사성을 측정합니다. 의미가 유사하면 코사인 점수 (cosine score)가 높습니다.
검색 파이프라인 (Retrieval Pipeline)
검색 예시:
query_embedding = generate_embedding(query)
results = index.query(vector=query_embedding, top_k=5, include_metadata=True)
설명:
- vector = query_embedding: 질문 벡터를 사용하여 검색합니다.
- top_k = 5: 상위 5개의 결과를 가져옵니다.
- include_metadata = True: 원본 청크 텍스트를 반환합니다.
프롬프트 증강 (Prompt Augmentation)
이것이 RAG에서 말하는 "증강 (augmentation)"입니다. 우리는 문맥을 주입합니다.
예시: context = "\n\n".
join ( match [ " metadata " ][ " text " ] for match in results [ " matches " ] )
Prompt Example
prompt = f """
You are a helpful assistant. Answer ONLY using the context.
Context: { context }
Question: { query }
Answer:
"""
Generation Phase (생성 단계)
Prompt (프롬프트)를 LLM에 전송합니다. 저는 로컬 LLM인 Mistral을 사용했습니다.
response = ollama . chat ( model = " mistral " , messages = [ { " role " : " user " , " content " : prompt } ] )
print ( response [ " message " ][ " content " ])
Pinecone Concepts (Pinecone 개념)
아래는 제가 사용한 몇 가지 Pinecone 개념이며, 도움이 되기를 바랍니다.
-
Index (인덱스)
벡터의 컨테이너 (Container of vectors). 데이터베이스 테이블 (Database table)과 동일합니다. -
Creating Index (인덱스 생성)
from pinecone import Pinecone
pc = Pinecone ( api_key = API_KEY )
pc . create_index ( name = " rag-demo " , dimension = 768 , metric = " cosine " , spec = { " serverless " : { " cloud " : " aws " , " region " : " us-east-1 " } } ) -
Upsert (업서트)
벡터를 삽입하거나 업데이트합니다.
index . upsert ( vectors = vectors ) -
Query (쿼리)
벡터를 검색합니다.
index . query (...) -
Delete (삭제)
벡터를 삭제합니다.
index . delete ( delete_all = True )
Metadata (메타데이터) in RAG
유용한 문맥 (context)을 저장합니다.
예시: metadata = { "text": chunk, "source": "notes.txt", "section": "embeddings" }
나중에 다음과 같은 용도로 유용하게 사용됩니다:
- 인용문 필터링 (filtering citations)
- 디버깅 (debugging)
Best Practices (권장 사항)
RAG 시스템을 구축할 때 따라야 할 몇 가지 권장 사항은 다음과 같습니다:
- 검색 품질 (Retrieval quality) > 모델 품질 (model quality)
- 메타데이터 (metadata) 사용하기
- 청크 (chunks)를 의미 있게 만들기
- 너무 작은 청크 피하기
- 문서 업데이트 후 재색인 (Re-index) 하기
- 오버랩 (overlap) 사용하기
- 프레임워크를 사용하기 전에 단순하게 시작하기
- 생성 (generation)과 별개로 검색 (retrieval)을 디버깅하기
하지만 몇 가지 고려 사항이 있습니다. 실제 운영 환경의 RAG 시스템은 저의 개인적인 단순한 RAG 시스템에는 없는 다음과 같은 기능들을 자주 추가하기 때문입니다:
- 인증 (authentication)
- 스트리밍 (streaming)
- 캐싱 (caching)
- 인용 (citations)
- 재순위화 (reranking)
- 하이브리드 검색 (hybrid search)
- 관찰 가능성 (observability)
- 평가 파이프라인 (evaluation pipelines)
- 벡터 버전 관리 (vector versioning)
- 문서 동기화 (document syncing)
Glossary (용어 사전)
| 용어 | 의미 |
|---|---|
| RAG | Retrieval-Augmented Generation (검색 증강 생성) |
| Embedding (임베딩) | 텍스트의 수치적 표현 |
| Vector (벡터) | 숫자의 순서 있는 목록 |
| Dimension (차원) | 벡터 내 값의 개수 |
| Chunk (청크) | 문서의 작은 섹션 |
| Metadata (메타데이터) | 추가적인 벡터 정보 |
| Top-K | 검색된 결과의 개수 |
| Similarity Search (유사도 검색) | 가장 가까운 벡터를 찾는 것 |
| Cosine Similarity (코사인 유사도) | 벡터 간의 근접도 측정 지표 |
| Index (인덱스) | Pinecone 벡터 컬렉션 |
| Upsert (업서트) | 벡터 삽입/업데이트 |
| Retrieval (검색) | 관련 지식을 찾는 것 |
| Generation (생성) | 최종 답변을 만들어내는 것 |
| Hallucination (환각) | 꾸며낸 답변 |
| Reranking (재순위화) | 검색된 청크의 순서를 다시 정하는 것 |
| Hybrid Search (하이브리드 검색) | 의미론적 (Semantic) + 키워드 검색 |
Conclusion (결론)
독자 여러분, 임베딩에서 검색, 그리고 적절한 응답 생성에 이르기까지 이러한 시스템이 내부적으로 어떻게 작동하는지 이해하는 데 저의 RAG에 대한 관점이 조금이나마 도움이 되었기를 바랍니다. 이것이 바로 RAG 시스템의 본질입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기