본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 26. 15:10

RAG 시스템 실전 구축 (v48)

요약

RAG(검색 증강 생성) 시스템의 핵심 원리와 실전 구축 방법을 다룹니다. 검색, 보완, 생성으로 이어지는 루프를 설명하며, Semantic 및 Recursive Chunking과 같은 텍별 텍스트 분할 전략을 코드 예시와 함께 제공합니다.

핵심 포인트

  • RAG의 3단계 루프(Retrieval, Augmentation, Generation) 이해
  • 의미 단위 분할을 위한 Semantic Chunking 구현 방법
  • LLM의 지식 제한 극복을 위한 도메인 지식 활용 전략
  • 효율적인 데이터 처리를 위한 다양한 Chunking 전략 비교

RAG 시스템 실전 구축 (v48)

실제 ML 엔지니어와 백엔드 개발자가 RAG 시스템을 빠르게 구축하고 운영할 수 있는 실전 가이드

1. RAG 기초 원리 (Retrieval → Augmentation → Generation 루프)

RAG (Retrieval-Augmented Generation)는 다음과 같은 세 가지 핵심 단계로 구성됩니다:

  1. Retrieval (검색): 입력 쿼리와 가장 관련성이 높은 문서/조각을 찾습니다
  2. Augmentation (보완): 검색된 정보를 프롬프트에 포함시켜 LLM의 컨텍스트를 확장합니다
  3. Generation (생성): 확장된 컨텍스트를 기반으로 정확한 응답을 생성합니다
쿼리 → [검색] → 문서 조각 → [보완] → 프롬프트 → [생성] → 응답

이 루프는 대규모 모델의 지식 제한을 극복하고, 특정 도메인 지식을 활용할 수 있게 해줍니다.

2. Chunking 전략 (Semantic, Recursive, Agentic)

2.1 Semantic Chunking

Semantic Chunking은 의미 단위로 텍스트를 분할하여 의미적 연결이 강한 문장들을 묶습니다.

from sentence_transformers import SentenceTransformer
import numpy as np
from sklearn.cluster import KMeans

class SemanticChunker:
    def __init__(self, model_name="all-MiniLM-L6-v2"):
        self.model = SentenceTransformer(model_name)

    def chunk_semantically(self, text, threshold=0.7):
        # 문장별로 분할
        sentences = text.split('. ')
        embeddings = self.model.encode(sentences)

        # 클러스터링으로 의미 단위 분할
        kmeans = KMeans(n_clusters=max(1, len(sentences) // 3))
        kmeans.fit(embeddings)

        # 클러스터별 문장 그룹화
        clusters = {}
        for i, cluster_id in enumerate(kmeans.labels_):
            if cluster_id not in clusters:
                clusters[cluster_id] = []
            clusters[cluster_id].append(sentences[i])

        # 그룹화된 문장들을 하나의 chunk로 결합
        chunks = [' '.join(cluster_sentences) for cluster_sentences in clusters.values()]
        return chunks

# 사용 예시
chunker = SemanticChunker()
text = "RAG 시스템은 검색과 생성을 결합한 아키텍처입니다. 이 시스템은 대규모 언어 모델의 지식 제한을 극복합니다. Semantic Chunking은 의미 단위로 텍스트를 분할합니다."
chunks = chunker.chunk_semantically(text)

2.2 Recursive Chunking

문서를 재귀적으로 분할하여 최적의 chunk 크기를 찾습니다.

class RecursiveChunker:
    def __init__(self, max_chunk_size=500, min_chunk_size=100):
        self.max_chunk_size = max_chunk_size
        self.min_chunk_size = min_chunk_size

    def chunk_recursive(self, text, separators=["\n\n", "\n", " ", ""]):
        chunks = []
        current_chunk = ""

        for separator in separators:
            if separator in text:
                parts = text.split(separator)
                for part in parts:
                    if len(current_chunk) + len(part) + len(separator) <= self.max_chunk_size:
                        current_chunk += part + separator
                    else:
                        if current_chunk:
                            chunks.append(current_chunk.strip())
                        current_chunk = part + separator
                if current_chunk:
                    chunks.append(current_chunk.strip())
                break

        if not chunks:
            chunks = [text]

        return chunks

# 사용 예시
recursive_chunker = RecursiveChunker()
text = "이 문서는 RAG 시스템 구축에 대한 실전 가이드입니다. 각 단계별로 코드 예제와 설명을 포함합니다."
chunks = recursive_chunker.chunk_recursive(text)

2.3 Agentic Chunking

LLM을 사용하여 chunking을 자동화하고, 특정 목적에 맞춘 chunk를 생성합니다.

from openai import OpenAI
import json

class AgenticChunker:
    def __init__(self, api_key):
        self.client = OpenAI(api_key=api_key)

    def generate_chunking_prompt(self, text, target_chunks=5):
        prompt = f"""
        다음 텍스트를 {target_chunks}개의 의미 단위로 분할하세요.
        각 chunk는 최대 1000자 이내여야 하며, 의미가 완전히 끝나야 합니다.
        JSON 형식으로 반환하세요:
        {{
            "chunks": ["chunk1", "chunk2", "..."]
        }}

        텍스트: {text}
        """
        return prompt

    def chunk_agentic(self, text, target_chunks=5):
        response = self.client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": self.generate_chunking_prompt(text, target_chunks)}],
            response_format={"type": "json_object"}
        )
        result = json.loads(response.choices[0].message.content)
        return result["chunks"]

3. Embedding 모델 선택 및 비교

3.1 주요 임베딩 모델

# 다양한 임베딩 모델 비교
from sentence_transformers import SentenceTransformer
from transformers import AutoTokenizer, AutoModel
import torch

class EmbeddingEvaluator:
    def __init__(self):
        self.models = {
            'all-MiniLM-L6-v2': SentenceTransformer('all-MiniLM-L6-v2'),
            'all-mpnet-base-v2': SentenceTransformer('all-mpnet-base-v2'),
            'sentence-t5-xxl': SentenceTransformer('sentence-t5-xxl'),
            'bge-small-en': SentenceTransformer('BAAI/bge-small-en')
        }

    def get_embedding(self, text, model_name):
        model = self.models[model_name]
        return model.encode(text)

    def compare_models(self, texts, reference_embedding):
        results = {}
        for name, model in self.models.items():
            embeddings = model.encode(texts)
            # 코사인 유사도 계산
            similarities = []
            for emb in embeddings:
                sim = np.dot(emb, reference_embedding) / (
                    np.linalg.norm(emb) * np.linalg.norm(reference_embedding)
                )
                similarities.append(sim)
            results[name] = np.mean(similarities)
        return results

# 사용 예시
evaluator = EmbeddingEvaluator()
texts = ["RAG 시스템은 검색 기반 생성 모델입니다.", "검색을 통해 관련 정보를 가져와 생성합니다."]
reference = evaluator.get_embedding("RAG 시스템의 핵심 개념", "all-MiniLM-L6-v2")
results = evaluator.compare_models(texts, reference)

4. Vector Database 비교 (Chroma, Qdrant, pgvector, Milvus)

4.1 Chroma

import chromadb
from chromadb.config import Settings
import numpy as np

class ChromaVectorDB:
    def __init__(self, collection_name="rag_collection"):
        self.client = chromadb.Client(Settings(anonymized_telemetry=False))
        self.collection = self.client.get_or_create_collection(collection_name)

    def add_documents(self, documents, embeddings, ids):
        self.collection.add(
            embeddings=embeddings,
            documents=documents,
            ids=ids
        )

    def search(self, query_embedding, n_results=5):
        results = self.collection.query(
            query_embeddings=[query_embedding],
            n_results=n_results
        )
        return results

4.2 Qdrant

from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Filter, FieldCondition, MatchValue

class QdrantVectorDB:
    def __init__(self, host="localhost", port=6333):
        self.client = QdrantClient(host=host, port=port)
        self.collection_name = "rag_collection"

    def create_collection(self):
        self.client.create_collection(
            collection_name=self.collection_name,
            vectors_config=VectorParams(size=384, distance="Cosine")
        )

    def add_documents(self, documents, embeddings, ids):
        self.client.upsert(
            collection_name=self.collection_name,
            points=[
                {
                    "id": id,
                    "vector": embedding.tolist(),
                    "payload": {"document": doc}
                }
                for id, doc, embedding in zip(ids, documents, embeddings)
            ]
        )

    def search(self, query_embedding, n_results=5):
        results = self.client.search(
            collection_name=self.collection_name,
            query_vector=query_embedding.tolist(),
            limit=n_results
        )
        return [(hit.id, hit.payload["document"]) for hit in results]

4.3 pgvector


python
import psycopg2
from psycopg2.extras import Json
import numpy as np

class PGVectorDB:
    def __init

---

📥 **Get the full guide on Gumroad**: https://gumroad.com/l/auto ($7)

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0