FastAPI와 ChromaDB를 사용하여 로컬에서 실행되는 RAG 챗봇 구축하기 (API 키 불필요)
요약
FastAPI와 ChromaDB를 활용하여 API 키 없이 로컬에서 실행 가능한 RAG 챗봇 구축 방법을 소개합니다. 문서 인덱싱부터 쿼리 파이프라인까지의 전 과정을 설명하며, 오픈 소스 스택을 활용한 효율적인 설계 방안을 제시합니다.
핵심 포인트
- RAG의 핵심인 인덱싱과 쿼리 파이프라인 단계 상세 설명
- FastAPI, ChromaDB, SentenceTransformers를 활용한 기술 스택 제안
- Ollama를 사용하여 API 비용 없이 완전 로컬 환경 구축 가능
- 질문과 문서에 반드시 동일한 임베딩 모델을 사용해야 함을 강조
모두가 자신의 문서 — 회사 핸드북, 계약서, 연구 논문 등 — 에서 질문에 답할 수 있는 챗봇을 원합니다. 이 기술의 이면에 있는 기법을 **RAG (Retrieval-Augmented Generation, 검색 증강 생성)**라고 부릅니다.
저는 생업으로 프로덕션 수준의 RAG 및 LLM (Large Language Model) 시스템을 구축하고 있으며, 매 프로젝트마다 동일한 기초 구조를 계속해서 다시 설계해야 했습니다. 그래서 이를 정리하여 작고 오픈 소스인 스타터 키트로 만들었습니다. 이 포스트에서는 RAG가 실제로 어떻게 작동하는지, 중요한 설계 결정 사항은 무엇인지, 그리고 API 키 없이 전체 과정을 어떻게 무료로 실행할 수 있는지에 대해 설명합니다.
마지막에는 몇 분 안에 클론(clone)하여 실행할 수 있는 전체 오픈 소스 저장소(repo) 링크가 있습니다.
왜 RAG인가?
GPT나 Claude와 같은 LLM은 학습된 내용만을 알고 있습니다. 여러분의 노트북에 있는 문서에 대해 질문하면, 모델은 추측하거나 — 환각 (hallucination) 현상을 일으킬 것입니다.
RAG는 간단한 아이디어로 이 문제를 해결합니다: 먼저 관련 정보를 검색한 다음, LLM이 오직 그 정보만을 사용하여 답변하게 하는 것입니다. 그 결과, 실제 문서에 근거한 답변을 얻을 수 있으며, 특정 페이지로 추적 가능한 인용(citation)을 제공할 수 있습니다.
두 가지 경로의 파이프라인 (pipeline)
경로 1 — 인덱싱 (Indexing, 문서가 들어올 때):
- PDF에서 텍스트 추출
- 텍스트를 중첩되는 청크 (chunks) 단위로 분할
- 각 청크의 의미를 포착하는 벡터 (vector, 임베딩 (embedding))로 변환
- 해당 벡터들을 벡터 데이터베이스 (vector database)에 저장
경로 2 — 쿼리 (Querying, 사용자가 질문할 때):
- 동일한 모델을 사용하여 질문을 임베딩 (embed)
- 벡터 데이터베이스에서 가장 유사한 청크를 검색
- 해당 청크들과 질문을 LLM에 전달
- LLM이 해당 컨텍스트 (context)를 사용하여 답변하고, 우리는 출처 페이지를 반환
스택 (stack) 선택하기
이러한 시스템을 몇 개 구축해 본 결과, 시작하기 쉽고 프로덕션에서도 견딜 수 있는 스택은 다음과 같습니다:
- FastAPI — 작성하기 빠르고 깔끔한 비동기 (async) API
- ChromaDB — 별도의 서버를 관리할 필요 없이 임베디드 (embedded)로 실행되는 가벼운 벡터 데이터베이스
- SentenceTransformers — 다국어 모델을 사용할 수 있는 무료 임베딩 (embeddings)
- Any LLM — OpenAI, Claude, Gemini 또는 로컬 Ollama
사람들이 놓치는 한 가지 세부 사항은, 매칭된 청크(chunks)를 반환하는 검색 전용(retrieval-only) 모드로 실행하거나, Ollama를 사용하여 아무것도 기기를 벗어나지 않도록 완전히 로컬에서 실행함으로써 API 키 없이도 전체 파이프라인을 테스트할 수 있다는 점입니다.
검색 단계의 핵심
쿼리(query) 측면의 흐름은 대략 다음과 같습니다. 질문을 임베딩(embed)하고, 검색한 뒤, 해당 청크들을 LLM에 전달합니다.
def answer_question(question: str, top_k: int = 3):
# 1. 인덱싱(indexing) 시 사용했던 것과 동일한 모델로 질문을 임베딩합니다
query_embedding = embed(question)
...
핵심 제약 사항: 질문과 문서에 동일한 임베딩 모델 (embedding model)을 사용해야 합니다. 서로 다른 모델에서 생성된 벡터(Vectors)는 비교할 수 없으며, 이는 놀라울 정도로 흔하게 발생하는 버그입니다.
품질을 결정짓는 세부 사항
경험에 비추어 볼 때, RAG 시스템이 답변을 잘하는지는 사람들이 과소평가하는 몇 가지 요소에 달려 있습니다.
- 청크 크기 (Chunk size). 너무 크면 관련 없는 콘텐츠가 섞여 버리고, 너무 작으면 각 청크가 문맥(context)을 잃게 됩니다. 청크 간의 중첩(Overlap)은 문장이 생각 중간에 끊기는 것을 방지하는 데 도움이 됩니다.
- 스캔된 PDF. 파일이 이미지인 경우, 일반적인 텍스트 추출로는 아무것도 반환되지 않습니다. OCR 폴백(fallback)이 필요하며, 그렇지 않으면 해당 페이지들은 빈 페이지로 조용히 인덱싱됩니다.
- 재순위화 (Re-ranking). 순수 벡터 검색 (vector search)은 빠르지만 정교함이 떨어집니다. 질문과 각 청크를 함께 읽는 크로스 인코더 재순위화기 (cross-encoder re-ranker)를 추가하면 모델에 실제로 전달되는 청크의 품질이 눈에 띄게 향상됩니다.
직접 시도해 보세요 (무료, 오픈 소스)
이 모든 것을 스타터 템플릿으로 패키징하여 MIT 라이선스로 오픈 소스화했습니다. 클론(Clone)하고 Docker 명령어를 한 번 실행하면, 출처 인용 기능이 포함된 작동 가능한 문서 Q&A 챗봇을 가질 수 있습니다. 다국어를 지원하며, API 키 없이 무료로 실행됩니다.
👉 GitHub: https://github.com/panutpl/rag-chatbot-template-starter
git clone https://github.com/panutpl/rag-chatbot-template-starter
cd rag-chatbot-template-starter
cp .env.example .env
...
그 다음 http://localhost:8000/docs를 열고, PDF를 업로드한 뒤 질문을 시작하세요.
RAG를 구축 중이거나 이제 막 시작하는 단계라면 피드백을 부탁드립니다. 특히 저도 여전히 조정 중인 청킹 (Chunking) 및 검색 (Retrieval) 전략에 대한 의견이 궁금합니다. 여러분에게 잘 작동하는 방식이 있다면 댓글로 남겨주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기