개요

대형 언어모델(LLM)은 학습 데이터에 포함된 정보만 답변할 수 있다는 본질적 한계가 있습니다. 사내 문서, 최신 뉴스, 도메인 전문 지식 등 모델이 학습하지 않은 정보에 대해서는 할루시네이션(Hallucination)을 일으키기 쉽습니다.

RAG(Retrieval-Augmented Generation)는 이 문제를 해결하는 가장 실용적인 접근법입니다. 외부 지식 저장소에서 관련 문서를 검색(Retrieve)하고, 이를 LLM의 컨텍스트에 주입(Augment)하여 더 정확한 응답을 생성(Generate)합니다. 이 글에서는 벡터 데이터베이스를 활용한 RAG 파이프라인을 처음부터 구축하는 방법을 다룹니다.

핵심 개념: RAG 파이프라인 아키텍처

RAG 시스템은 크게 세 단계로 구성됩니다.

  • 인덱싱(Indexing): 문서를 청크로 분할하고, 임베딩 벡터로 변환하여 벡터DB에 저장
  • 검색(Retrieval): 사용자 질문을 임베딩하고, 벡터 유사도 검색으로 관련 문서 조각 추출
  • 생성(Generation): 검색된 문서를 프롬프트에 포함시켜 LLM으로 답변 생성

벡터DB 선택지로는 Pinecone, Weaviate, Chroma, Qdrant, pgvector(PostgreSQL) 등이 있습니다. Supabase를 이미 사용 중이라면 pgvector 확장을 활용하는 것이 가장 효율적입니다.

실전 예제: Python으로 RAG 구축하기

LangChain과 OpenAI 임베딩, Chroma 벡터DB를 사용한 기본 RAG 파이프라인입니다.

from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA

# 1. 문서 로딩
loader = DirectoryLoader('./docs', glob="**/*.md", loader_cls=TextLoader)
documents = loader.load()

# 2. 텍스트 분할 (청킹)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", ".", " "]
)
chunks = text_splitter.split_documents(documents)

# 3. 벡터DB 생성 (임베딩 + 저장)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

# 4. 검색 + 생성 체인
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(search_kwargs={"k": 5}),
    return_source_documents=True
)

# 5. 질의
result = qa_chain.invoke({"query": "프로젝트의 인증 방식은 무엇인가요?"})
print(result["result"])
for doc in result["source_documents"]:
    print(f"  출처: {doc.metadata['source']}")

Supabase pgvector를 사용하는 경우 다음과 같이 구성합니다.

-- Supabase에서 pgvector 활성화
create extension if not exists vector;

-- 문서 저장 테이블
create table documents (
  id bigserial primary key,
  content text,
  metadata jsonb,
  embedding vector(1536)
);

-- 유사도 검색 함수
create function match_documents(
  query_embedding vector(1536),
  match_count int default 5,
  filter jsonb default '{}'
) returns table (id bigint, content text, metadata jsonb, similarity float)
language plpgsql as $$
begin
  return query
  select d.id, d.content, d.metadata,
    1 - (d.embedding <=> query_embedding) as similarity
  from documents d
  where d.metadata @> filter
  order by d.embedding <=> query_embedding
  limit match_count;
end;
$$;

활용 팁

  • 청크 크기 최적화: 500~1500자 사이에서 실험하세요. 너무 작으면 맥락이 부족하고, 너무 크면 노이즈가 증가합니다.
  • 하이브리드 검색: 벡터 유사도 검색과 키워드 검색(BM25)을 결합하면 정확도가 크게 향상됩니다.
  • 리랭킹(Re-ranking): Cohere Rerank나 Cross-Encoder를 사용하여 검색 결과의 순위를 재조정하세요.
  • 메타데이터 필터링: 문서에 카테고리, 날짜 등 메타데이터를 태깅하여 검색 범위를 좁히세요.
  • 평가 체계 구축: RAGAS 프레임워크로 Faithfulness, Relevancy 등을 정량적으로 측정하세요.

마무리

RAG는 LLM의 한계를 극복하는 가장 현실적인 방법이며, 기업 환경에서 AI를 도입할 때 첫 번째로 고려해야 할 아키텍처입니다. 벡터DB 선택, 청킹 전략, 검색 최적화 등 세부 요소를 잘 튜닝하면 파인튜닝 없이도 높은 품질의 도메인 특화 AI 시스템을 구축할 수 있습니다.