RAG(검색 증강 생성) 시스템 구축 — LangChain + PostgreSQL
LLM의 한계를 넘어: RAG 시스템의 부상
최근 몇 년간 인공지능 분야는 거대 언어 모델(LLM)의 등장으로 전에 없던 혁신을 경험하고 있습니다. ChatGPT를 필두로 하는 LLM들은 자연어 이해와 생성 능력에서 놀라운 성능을 보여주며, 다양한 산업 분야에 걸쳐 새로운 가능성을 열어주고 있습니다. 하지만 이러한 LLM에도 명확한 한계점이 존재합니다.
- 환각(Hallucination) 현상: LLM은 때때로 사실과 다른 정보를 마치 진실인 양 생성하는 환각 현상을 보입니다. 이는 모델이 학습한 데이터 내에서 패턴을 찾아 답변을 생성하기 때문에, 정확한 사실 관계를 보장하기 어렵다는 본질적인 문제에서 기인합니다.
- 최신 정보 부족: LLM은 특정 시점까지의 데이터로 학습되기 때문에, 그 이후에 발생한 최신 정보나 실시간 데이터에 대해서는 알지 못합니다. 빠르게 변화하는 현대 사회에서 이는 치명적인 약점이 될 수 있습니다.
- 비공개/내부 데이터 접근 불가: 기업의 내부 문서, 특정 도메인의 전문 지식, 사용자의 개인 정보 등 LLM이 학습하지 않은 비공개 데이터에 대해서는 답변할 수 없습니다. 이는 기업용 AI 솔루션 구축 시 가장 큰 걸림돌 중 하나입니다.
- 출처 명시의 어려움: LLM이 생성한 답변의 근거를 명확히 제시하기 어렵기 때문에, 신뢰성 있는 정보가 요구되는 분야에서는 활용에 제약이 따릅니다.
이러한 LLM의 한계를 극복하고, 더욱 정확하고 신뢰성 있으며 최신 정보를 반영하는 AI 시스템을 구축하기 위한 핵심적인 기술 패러다임이 바로 **RAG(Retrieval Augmented Generation, 검색 증강 생성)**입니다. RAG는 외부 검색 시스템을 통해 최신 정보나 특정 도메인 지식을 검색하고, 이를 LLM의 생성 과정에 통합함으로써 위에서 언급된 문제들을 효과적으로 해결합니다.
CodePick은 이러한 RAG 시스템이 미래 AI 서비스 개발의 핵심 동력이 될 것이라 확신하며, 이번 포스트에서는 LangChain 프레임워크와 PostgreSQL 데이터베이스를 활용하여 RAG 시스템을 구축하는 실질적인 방법을 심층적으로 다루고자 합니다. 트렌드 분석을 넘어, 실제 개발 프로젝트에 적용할 수 있는 구체적인 가이드라인을 제시하여 독자 여러분의 AI 서비스 개발 여정에 도움이 되기를 바랍니다.
RAG(검색 증강 생성) 시스템, 왜 필요한가?
RAG 시스템은 LLM이 자체적으로 해결하기 어려운 문제들을 외부 검색 메커니즘을 통해 보완하는 방식입니다. 그 핵심 원리는 다음과 같습니다.
- 검색(Retrieval): 사용자의 질문이 들어오면, 먼저 질문과 관련된 정보를 외부 지식 베이스(문서, 데이터베이스, 웹 등)에서 검색합니다. 이 과정에서 질문의 의도를 파악하고 가장 관련성이 높은 정보 조각들을 찾아냅니다.
- 증강(Augmentation): 검색된 정보 조각들을 LLM에 전달되는 프롬프트에 추가하여 LLM이 답변을 생성할 때 참고할 수 있도록 증강시킵니다.
- 생성(Generation): 증강된 프롬프트를 바탕으로 LLM은 더욱 정확하고 근거 있는 답변을 생성합니다. 이때 LLM은 단순히 학습된 지식에 의존하는 것이 아니라, 제공된 외부 정보를 활용하여 답변의 신뢰도를 높입니다.
이러한 RAG의 작동 방식은 기업 및 서비스 개발자에게 다음과 같은 중요한 가치를 제공합니다.
- 정확성과 신뢰성 향상: 외부의 검증된 데이터를 기반으로 답변을 생성하므로, LLM의 환각 현상을 크게 줄이고 답변의 정확도를 높일 수 있습니다. 또한, 사용자가 답변의 출처를 확인할 수 있도록 정보를 함께 제공할 수 있어 신뢰성을 확보할 수 있습니다.
- 최신 정보 반영: 검색 대상이 되는 외부 지식 베이스만 업데이트하면 LLM 모델 자체를 재학습할 필요 없이 최신 정보를 반영할 수 있습니다. 이는 정보의 휘발성이 높은 분야에서 매우 강력한 장점입니다.
- 내부 데이터 활용: 기업의 기밀 문서, 사내 매뉴얼, 고객 데이터 등 LLM이 학습하지 않은 비공개 데이터를 RAG 시스템의 검색 대상으로 포함시켜, 기업 맞춤형 AI 서비스를 구축할 수 있습니다.
- 비용 효율성: 대규모 LLM을 특정 도메인에 맞게 미세 조정(Fine-tuning)하는 것은 막대한 컴퓨팅 자원과 시간, 비용을 요구합니다. RAG는 상대적으로 적은 비용으로도 LLM의 성능을 특정 도메인에 최적화할 수 있는 효과적인 대안입니다.
- 규제 준수 및 투명성: 데이터의 출처를 명확히 제시할 수 있기 때문에, 의료, 법률, 금융 등 규제가 엄격하고 투명성이 요구되는 분야에서 AI 활용 가능성을 높입니다.
RAG는 이처럼 LLM의 본질적인 한계를 보완하고, 실제 비즈니스 환경에서 AI를 더욱 효과적으로 활용할 수 있게 하는 핵심 기술입니다. 다음은 RAG와 LLM 미세 조정(Fine-tuning)의 주요 차이점을 비교한 표입니다.
| 특징 | RAG (검색 증강 생성) | Fine-tuning (미세 조정) |
|---|---|---|
| **목적** | 최신/특정 정보 제공, 환각 감소, 출처 명시 | 특정 도메인에 대한 LLM의 지식/스타일 학습 |
| **데이터 필요량** | 검색 대상 문서 (대량 가능) | 고품질의 질문-답변 쌍 또는 지시문 (수천~수만 개) |
| **정보 업데이트** | 검색 데이터베이스 업데이트로 실시간 반영 가능 | 모델 재학습 필요 (시간, 비용 소모) |
| **환각(Hallucination)** | 검색된 정보 기반으로 감소, 출처 명시로 신뢰성 증대 | 학습 데이터에 따라 환각 발생 가능, 출처 명시 어려움 |
| **비용 효율성** | 임베딩 및 검색 비용, LLM 추론 비용 | 모델 학습 비용 (GPU), LLM 학습 비용, LLM 추론 비용 |
| **구현 복잡성** | retriever, vector DB, LLM 연동 | 학습 데이터 준비, 모델 학습, 배포 |
| **주요 장점** | 최신성, 출처 명시, 데이터 보안, 비용 효율성 | 특정 도메인 전문성, 스타일 일관성, 추론 속도 (경량 모델) |
| **주요 단점** | 검색 품질에 따라 답변 품질 좌우, 프롬프트 엔지니어링 필요 | 학습 데이터 편향, 높은 학습 비용, 정보 업데이트 어려움 |
RAG 시스템의 핵심 구성 요소
RAG 시스템을 구축하기 위해서는 몇 가지 핵심적인 구성 요소들이 유기적으로 결합되어야 합니다.
데이터 소스 (Data Source):
RAG 시스템에 제공될 지식의 원천입니다. 기업의 내부 문서(PDF, Word, Excel), 데이터베이스 레코드, 웹 페이지, API 응답 등 다양한 형태가 될 수 있습니다. 이 데이터는 LLM이 직접 학습한 내용이 아닌, RAG를 통해 LLM에 제공될 보조 정보입니다.문서 로더 및 분할기 (Document Loader & Splitter):
다양한 형식의 데이터 소스를 LLM이 처리하기 쉬운 텍스트 형태로 로드하고, 너무 길지 않은 적절한 크기의 청크(Chunk)로 분할하는 역할을 합니다. LLM의 토큰 제한과 검색 효율성을 고려하여 청크 크기와 오버랩(overlap) 전략을 신중하게 결정해야 합니다.임베딩 모델 (Embedding Model):
텍스트 청크를 고차원 벡터 공간의 숫자 배열(임베딩)로 변환하는 모델입니다. 이 벡터는 텍스트의 의미론적 유사성을 나타내며, 의미가 유사한 텍스트는 벡터 공간에서 서로 가깝게 위치하게 됩니다. OpenAI의text-embedding-ada-002나 다양한 오픈소스 임베딩 모델(예: Sentence-Transformers)이 널리 사용됩니다.벡터 데이터베이스 (Vector Database):
임베딩 모델을 통해 생성된 벡터들을 저장하고, 주어진 쿼리 벡터와 가장 유사한 벡터들을 효율적으로 검색하는 전문 데이터베이스입니다.pgvector가 활성화된 PostgreSQL, Pinecone, Weaviate, Chroma, Qdrant 등 다양한 솔루션이 있습니다. RAG 시스템의 검색 성능과 확장성에 직접적인 영향을 미칩니다.리트리버 (Retriever):
사용자의 질문(쿼리)을 임베딩하고, 이 임베딩을 사용하여 벡터 데이터베이스에서 가장 관련성이 높은 문서 청크(벡터)를 찾아내는 구성 요소입니다. 단순한 유사도 검색 외에도 MMR(Maximal Marginal Relevance) 등 고급 검색 전략을 적용하여 검색 품질을 향상시킬 수 있습니다.거대 언어 모델 (Large Language Model, LLM):
사용자의 질문과 리트리버가 검색한 관련 정보를 바탕으로 최종 답변을 생성하는 핵심 AI 모델입니다. OpenAI의 GPT 시리즈, Google의 Gemini, Anthropic의 Claude, 그리고 다양한 오픈소스 LLM(Llama, Mistral 등)이 활용될 수 있습니다.오케스트레이션 프레임워크 (Orchestration Framework):
위에서 언급된 다양한 구성 요소들을 효율적으로 연결하고, 데이터 흐름과 로직을 관리하는 프레임워크입니다. LangChain이 대표적인 예시이며, 복잡한 RAG 워크플로우를 간결하게 구현할 수 있도록 돕습니다.
LangChain으로 RAG 워크플로우 구축하기
LangChain은 LLM 기반 애플리케이션 개발을 위한 강력한 오픈소스 프레임워크입니다. 다양한 LLM, 임베딩 모델, 문서 로더, 텍스트 분할기, 벡터 저장소, 체인(Chain) 및 에이전트(Agent) 컴포넌트들을 모듈화하여 제공하므로, 복잡한 RAG 워크플로우를 쉽고 유연하게 구축할 수 있습니다.
LangChain의 주요 특징은 다음과 같습니다.
- 모듈성: LLM 애플리케이션의 각 단계를 독립적인 컴포넌트로 제공하여, 필요에 따라 교체하거나 조합하기 용이합니다.
- 체인(Chains): 여러 컴포넌트를 연결하여 특정 작업을 수행하는 일련의 단계를 정의합니다. 예를 들어, "문서 로드 → 텍스트 분할 → 임베딩 → 벡터 저장 → 검색 → LLM 호출"과 같은 RAG 워크플로우를 하나의 체인으로 구성할 수 있습니다.
- 에이전트(Agents): LLM이 어떤 도구를 사용해야 할지 스스로 판단하고 실행하도록 돕는 고급 추론 메커니즘입니다. RAG 시스템에서 특정 도구를 사용하여 정보를 검색하는 등의 복잡한 상호작용에 활용될 수 있습니다.
- LangChain Expression Language (LCEL): 파이프(|) 연산자를 사용하여 체인을 구성하는 간결하고 유연한 방법입니다. 복잡한 데이터 흐름을 직관적으로 표현할 수 있습니다.
LangChain을 사용하면 RAG 시스템의 핵심 로직인 검색된 문서를 프롬프트에 추가하여 LLM이 답변을 생성하도록 하는 과정을 매우 효율적으로 구현할 수 있습니다. LangChain은 다양한 문서 로더를 통해 여러 형식의 데이터를 처리하고, 텍스트 분할기를 통해 청크를 생성하며, 임베딩 모델과 벡터 저장소를 통합하여 검색 기능을 제공합니다. 이 모든 과정을 하나의 파이프라인으로 묶어 쉽게 관리할 수 있게 해주는 것이 LangChain의 가장 큰 강점입니다.
PostgreSQL을 벡터 데이터베이스로 활용하는 방법
RAG 시스템의 핵심은 효율적인 벡터 검색입니다. 전통적으로 벡터 데이터베이스는 Pinecone, Weaviate, Chroma 등 전문 솔루션을 사용하는 경우가 많았습니다. 그러나 기존 관계형 데이터베이스의 강점을 활용하면서도 벡터 검색 기능을 제공하는 pgvector 확장이 등장하면서, PostgreSQL을 벡터 데이터베이스로 활용하는 것이 강력한 대안으로 떠오르고 있습니다.
pgvector란?
pgvector는 PostgreSQL 데이터베이스에 벡터 데이터 타입과 벡터 유사성 검색 기능을 추가하는 오픈소스 확장(extension)입니다. 이를 통해 PostgreSQL 내에서 임베딩 벡터를 저장하고, L2 거리, 코사인 유사도, 내적(inner product)과 같은 다양한 유사성 측정 방식을 사용하여 벡터 검색을 수행할 수 있습니다.
pgvector의 장점
PostgreSQL과 pgvector 조합은 다음과 같은 강력한 장점을 제공합니다.
- 기존 인프라 활용: 이미 PostgreSQL을 사용하고 있는 기업이라면, 새로운 벡터 데이터베이스 솔루션을 도입하고 관리할 필요 없이 기존 인프라를 그대로 활용할 수 있습니다. 이는 운영 및 유지보수 비용을 크게 절감시킵니다.
- 데이터 일관성 및 통합 관리: 관계형 데이터와 벡터 데이터를 하나의 데이터베이스에서 통합하여 관리할 수 있습니다. 이는 데이터 모델링을 단순화하고, 트랜잭션 일관성을 보장하며, 백업 및 복구 전략을 통합할 수 있게 합니다.
- 강력한 기능: PostgreSQL은 이미 전 세계적으로 검증된 강력한 데이터베이스입니다.
pgvector와 함께 ACID 트랜잭션, 복제, 샤딩, 다양한 인덱싱 옵션 등 PostgreSQL의 모든 기능을 벡터 데이터에도 그대로 적용할 수 있습니다. - 개발 편의성: 대부분의 개발자에게 익숙한 SQL을 사용하여 벡터 데이터를 조작하고 쿼리할 수 있어 학습 곡선이 낮습니다.
- 오픈소스:
pgvector는 오픈소스이므로 라이선스 비용 부담이 없으며, 커뮤니티의 지원을 받을 수 있습니다.
pgvector 설치 및 설정 (간단한 예시)
pgvector를 사용하기 위해서는 PostgreSQL 서버에 확장을 설치하고 활성화해야 합니다.
- PostgreSQL 설치: 먼저 PostgreSQL 서버가 설치되어 있어야 합니다. (버전 13 이상 권장)
pgvector설치: 운영체제에 따라 설치 방식이 다를 수 있습니다. Debian/Ubuntu의 경우:또는 소스 코드를 직접 컴파일하여 설치할 수도 있습니다.bashsudo apt-get install postgresql-16-pgvector- 데이터베이스 내에서
pgvector활성화: PostgreSQL 클라이언트(psql 등)에 접속하여 사용하려는 데이터베이스에서 다음 명령을 실행합니다.sqlCREATE EXTENSION vector; - 벡터 데이터를 저장할 테이블 생성: 임베딩 벡터를 저장할 컬럼을
VECTOR(차원)타입으로 지정하여 테이블을 생성합니다. 예를 들어, OpenAI의text-embedding-ada-002모델은 1536차원 벡터를 생성합니다.이제 이sqlCREATE TABLE documents ( id SERIAL PRIMARY KEY, content TEXT NOT NULL, embedding VECTOR(1536) );documents테이블에 문서 내용과 해당 문서의 임베딩 벡터를 함께 저장할 수 있습니다.
실전! LangChain + PostgreSQL 기반 RAG 시스템 구축 가이드
이제 LangChain과 pgvector를 활용하여 실제 RAG 시스템을 구축하는 과정을 단계별로 살펴보겠습니다.
1단계: 개발 환경 설정
먼저 Python 환경을 설정하고 필요한 라이브러리를 설치합니다.
# 가상 환경 생성 및 활성화 (선택 사항이지만 권장)
python -m venv .venv
source .venv/bin/activate
# 필요한 라이브러리 설치
pip install langchain langchain-openai psycopg2-binary pgvector python-dotenv
python-dotenv는 API 키와 같은 민감 정보를 .env 파일에 저장하여 관리하기 위함입니다. 프로젝트 루트에 .env 파일을 생성하고 OpenAI API 키를 추가합니다.
# .env 파일 예시
OPENAI_API_KEY="your_openai_api_key_here"
PG_CONNECTION_STRING="postgresql://user:password@host:port/database"
2단계: PostgreSQL 데이터베이스 준비
pgvector 확장을 활성화하고 문서와 임베딩을 저장할 테이블을 생성합니다. psql 등 PostgreSQL 클라이언트를 통해 다음 SQL 명령을 실행합니다.
-- 데이터베이스 생성 (필요하다면)
-- CREATE DATABASE rag_db;
-- 해당 데이터베이스에 접속 후 pgvector 확장 활성화
-- \c rag_db;
CREATE EXTENSION vector;
-- 문서와 임베딩을 저장할 테이블 생성
-- OpenAI text-embedding-ada-002는 1536차원 벡터를 생성합니다.
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
embedding VECTOR(1536)
);
-- 검색 성능 향상을 위한 인덱스 생성 (선택 사항이지만 대량 데이터에 필수)
-- IVFFlat 인덱스는 Approximate Nearest Neighbor (ANN) 검색에 사용됩니다.
-- 100은 리스트 개수 (num_lists)로, 데이터 양에 따라 적절히 조절해야 합니다.
CREATE INDEX ON documents USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
3단계: 문서 로드 및 임베딩 생성
이제 RAG 시스템에 사용할 문서 데이터를 로드하고, 이를 작은 청크로 분할한 후 임베딩 벡터를 생성합니다. 여기서는 간단한 텍스트 파일을 가정합니다. 실제 프로젝트에서는 PDF, HTML 등 다양한 형식의 문서를 로드할 수 있습니다.
data/ 디렉토리에 example.txt 파일을 생성하고 다음 내용을 추가합니다.
# data/example.txt
CodePick은 기획부터 개발, 운영까지 전 과정을 함께하는 전문 개발 기업입니다.
스타트업의 MVP 개발, AI 서비스 개발, 웹 플랫폼 구축, 기업 시스템 및 모바일 앱 개발 등 다양한 프로젝트를 성공적으로 수행합니다.
저희는 고객의 아이디어를 현실로 만들고, 비즈니스 성장을 위한 최적의 기술 솔루션을 제공합니다.
특히 RAG 시스템 구축과 같은 최신 AI 기술 도입에 강점을 가지고 있습니다.
무료 개발 상담을 통해 아이디어 실현 가능성을 함께 논의해 보세요.
이제 Python 코드로 문서를 처리합니다.
# rag_system.py
import os
from dotenv import load_dotenv
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import PGVector
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate
load_dotenv() # .env 파일 로드
# 1. 문서 로드
loader = TextLoader("./data/example.txt", encoding="utf-8")
documents = loader.load()
# 2. 텍스트 분할
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)
# 3. 임베딩 모델 초기화
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
# PostgreSQL 연결 정보
CONNECTION_STRING = os.getenv("PG_CONNECTION_STRING")
COLLECTION_NAME = "codepick_rag_docs" # 벡터 저장소 내 컬렉션 이름
# 4. 벡터 데이터베이스에 저장
# PGVector.from_documents()는 문서들을 임베딩하고 PGVector에 저장하는 과정을 한 번에 처리합니다.
# collection_name은 PGVector 내에서 특정 문서 집합을 구분하는 데 사용됩니다.
vectorstore = PGVector.from_documents(
embedding=embeddings,
documents=chunks,
collection_name=COLLECTION_NAME,
connection_string=CONNECTION_STRING,
pre_delete_collection=True # 기존 컬렉션이 있다면 삭제 후 새로 생성 (개발 시 유용)
)
print(f"{len(chunks)}개의 문서 청크가 PostgreSQL에 성공적으로 저장되었습니다.")
4단계: RAG 체인 구성 및 실행
이제 저장된 벡터 데이터를 기반으로 RAG 체인을 구성하고 사용자의 질문에 답변하는 코드를 작성합니다.
# rag_system.py (위 코드에 이어서)
# 5. 리트리버 설정
# PGVector 인스턴스에서 retriever를 생성합니다. k=2는 가장 유사한 문서 2개를 가져오라는 의미입니다.
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 6. LLM 초기화
llm = ChatOpenAI(model_name="gpt-4o", temperature=0) # gpt-4o 최신 모델 사용
# 7. 프롬프트 템플릿 정의
# 검색된 context를 포함하여 LLM에 전달할 프롬프트입니다.
template = """
당신은 CodePick의 친절한 AI 어시스턴트입니다. 다음 정보를 참고하여 질문에 답변해주세요.
만약 주어진 정보만으로는 답변하기 어렵다면, "주어진 정보만으로는 답변하기 어렵습니다."라고 말해주세요.
답변은 항상 한국어로 해주세요.
---
주어진 정보:
{context}
---
질문: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 8. RAG 체인 구성
# LCEL (LangChain Expression Language)을 사용하여 체인을 구성합니다.
# - RunnablePassthrough: 이전 단계의 입력을 다음 단계로 그대로 전달하거나, 추가 키를 전달합니다.
# - retriever: 질문에 관련된 문서를 검색합니다.
# - stuff_documents_chain: 검색된 문서들을 하나의 문자열로 합쳐 context로 만듭니다.
# - prompt: context와 question을 포함한 최종 프롬프트를 생성합니다.
#