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을 대규모 사유 데이터와 연결한다. 세 단계로 구성된다:

  1. 인덱싱 (오프라인) – 문서를 벡터 임베딩으로 변환하고 벡터 데이터베이스에 저장한다.
  2. 검색 (쿼리 시점) – 사용자 쿼리를 임베딩하고, 벡터 저장소를 검색해 상위 k개의 가장 관련성 높은 청크를 반환한다.
  3. 생성 – 검색된 청크와 원본 쿼리를 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 파이프라인을 구축할 수 있다:

  1. 원시 문서를 로드한다.
  2. chunk_text로 청크화한다.
  3. 청크를 VectorStore에 삽입한다.
  4. 쿼리 시점에 사용자의 질문을 임베딩하고, 상위 k개의 청크를 검색한 뒤, 연결된 컨텍스트와 원본 질문을 LLM에 전달한다(예: litellm 또는 langchain 사용).

기사 끝.

Back to Blog

관련 글

더 보기 »