🧠 LangChain + FAISS + Gradio로 만드는 RAG 기반 지식 챗봇
문서를 검색해 대화형으로 응답해 주는 RAG 기반 지식 챗봇을 만들어보는 프로젝트입니다.
OpenAI의 LLM을 기반으로 벡터 검색, 대화 메모리, 웹 인터페이스까지 연결하며 실습하였습니다.
🔍 FAISS란 무엇인가요?
FAISS는 Facebook AI Similarity Search의 약자로,
Facebook AI Research에서 개발한 벡터 유사도 검색 라이브러리입니다.
대규모 벡터 데이터에서 유사한 항목을 빠르게 검색할 수 있어,
문서 검색 기반 생성(RAG) 시스템에 매우 적합합니다.
1. 📦 필수 라이브러리 임포트
🔹 주요 라이브러리 설명
import os, glob
from dotenv import load_dotenv
import gradio as gr
os
,glob
: 파일 탐색 및 경로 관리에 사용합니다.dotenv
:.env
파일에 저장된 API 키를 안전하게 불러옵니다.gradio
: 챗봇 UI를 손쉽게 웹에 띄우는 라이브러리입니다.
2. 📄 LangChain 핵심 모듈 불러오기
from langchain.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.schema import Document
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.vectorstores import FAISS
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
🔹 모듈별 역할
DirectoryLoader
,TextLoader
: 문서 폴더 및 텍스트 파일 불러오기CharacterTextSplitter
: 문서를 청크 단위로 분할OpenAIEmbeddings
,FAISS
: 문서 임베딩 후 벡터 저장소 구성ConversationBufferMemory
,ConversationalRetrievalChain
: 대화형 RAG 체인 구성
3. 🧪 문서 로딩 및 전처리
🔹 저비용 모델 선택 & 벡터 DB 이름 설정
MODEL = "gpt-4o-mini"
# 🤖 사용할 LLM 모델 이름 설정
# - gpt-4o-mini: OpenAI의 최신 저비용 모델 중 하나
# - 성능도 준수하면서 요금이 낮아 비용 효율적인 선택임
db_name = "vector_db"
# 🗃️ 벡터 데이터베이스 이름 설정
# - 문서 임베딩 정보를 저장할 DB 이름
# - 검색 기반 생성(RAG)에서 청크들을 저장하고 검색하는 데 사용됨
gpt-4o-mini
는 OpenAI의 저비용 모델로, 비용 효율을 고려해 선택하였습니다.
🔹 .env
환경 변수 로드
load_dotenv(override=True)
# 🔐 .env 파일에 저장된 환경변수 로드
# - .env 파일은 API 키처럼 민감한 정보를 안전하게 관리할 수 있게 해줌
# - override=True: 이미 설정된 환경 변수도 덮어쓰기 허용
os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY', 'your-key-if-not-using-env')
# 🔑 OpenAI API 키 설정
# - .env에 OPENAI_API_KEY가 정의되어 있으면 해당 값을 사용
# - 만약 없다면 기본값 'your-key-if-not-using-env' 사용 (테스트용 혹은 대체값)
.env
파일에 저장된 OpenAI API 키를 불러옵니다.
📥 LangChain의 로더를 사용해 문서 불러오기
# - knowledge-base 폴더 아래의 모든 하위 폴더에서 문서를 읽어옴
folders = glob.glob("knowledge-base/*")
# 📂 knowledge-base 디렉토리 내의 모든 하위 폴더 경로를 리스트로 수집
text_loader_kwargs = {'encoding': 'utf-8'}
# - 대부분의 시스템에서 UTF-8 인코딩으로 충분하지만,
# - 만약 인코딩 오류가 발생하면 아래 라인을 대신 사용할 수 있음 (Windows 한정)
# text_loader_kwargs = {'autodetect_encoding': True} # 👉 인코딩 자동 감지 (윈도우 사용자를 위한 대안)
documents = [] # 전체 문서를 담을 리스트
# 📚 각 폴더별로 문서를 로드하고 메타데이터 추가
for folder in folders:
doc_type = os.path.basename(folder) # 폴더 이름을 문서 유형으로 사용
loader = DirectoryLoader(
folder, # 현재 폴더 경로
glob="**/*.md", # 모든 Markdown(.md) 파일 대상
loader_cls=TextLoader, # 텍스트 로더 사용
loader_kwargs=text_loader_kwargs # 위에서 정의한 인코딩 설정 적용
)
folder_docs = loader.load() # 폴더 내 문서들 로딩
# 📎 각 문서에 메타데이터(doc_type)를 추가하고 리스트에 담기
for doc in folder_docs:
doc.metadata["doc_type"] = doc_type
documents.append(doc)
knowledge-base
폴더 내 모든 하위 폴더의 .md
파일을 불러오고, 폴더 이름을 문서 유형(doc\_type
)으로 저장합니다.
✂️ 문서 분할 (청크)
# ✂️ 문서를 LLM 입력에 맞게 청크(chunk) 단위로 분할하는 단계
# - 긴 문서는 한 번에 LLM에 넣기 어려우므로 적절한 길이로 나눠야 함
# - LangChain의 CharacterTextSplitter를 사용해 '문자 수 기준'으로 나눔
text_splitter = CharacterTextSplitter(
chunk_size=1000, # 📏 청크 하나당 최대 1,000자
chunk_overlap=200 # 🔁 청크 간에 200자씩 겹치도록 설정 (문맥 끊김 방지용)
)
# 🧩 실제 분할 실행: documents 리스트 내 각 문서를 위 설정대로 나눔
chunks = text_splitter.split_documents(documents)
문서를 LLM 입력에 적합하도록 1000자 단위로 나누고,
문맥 유지를 위해 200자씩 겹치게 설정하였습니다.
# 📦 전체 청크(문서 조각) 개수 확인
# - 위에서 분할된 chunks 리스트의 길이를 확인함
# - 얼마나 많은 청크가 생성되었는지 파악 가능 (즉, 나눠진 문서의 총 개수)
len(chunks) # 👉 예: 132 → 총 132개의 청크가 생성됨
💡 이 숫자는 나중에 벡터 저장소에 몇 개의 벡터가 들어갈지,
임베딩 처리 시간이 얼마나 걸릴지를 예측하는 데도 유용해요!
4. 🧠 문서 임베딩 및 벡터 저장소 구성
# 🏷️ 생성된 청크들에서 문서 유형(doc_type)만 추출해 중복 없이 모음
# - 각 청크에는 어느 폴더에서 왔는지를 나타내는 doc_type 메타데이터가 있음
# - set을 사용하면 중복된 항목 없이 고유한 유형들만 수집됨
doc_types = set(chunk.metadata['doc_type'] for chunk in chunks)
# 🖨️ 어떤 문서 유형들이 포함되어 있는지 확인
# - 예: "Document types found: faq, policy, tutorial"
print(f"Document types found: {', '.join(doc_types)}")
💡 이 코드는 문서를 폴더별로 구분해 로딩했기 때문에,
그 분류(tag)들이 실제 청크에도 잘 전달되었는지 확인할 수 있어요!
🔹 FAISS 벡터 저장소 생성
# 📥 청크 데이터를 벡터 저장소(Vector Store)에 저장하는 단계
# - 각 텍스트 청크를 의미 기반 벡터로 변환하고, 그 벡터를 저장소에 매핑함
# - 나중에 질문이 들어오면 이 벡터들을 바탕으로 가장 관련 있는 청크를 검색할 수 있음
# 🤖 OpenAI 임베딩 모델 초기화
embeddings = OpenAIEmbeddings()
# 🧠 벡터 저장소(Vector Store) 생성
# ✅ 기존 코드 (Chroma 사용) → 현재는 사용하지 않음
# vectorstore = Chroma.from_documents(documents=chunks, embedding=embeddings, persist_directory=db_name)
# ✅ 새 코드 (FAISS 사용)
vectorstore = FAISS.from_documents(chunks, embedding=embeddings)
# - FAISS는 Facebook에서 만든 빠르고 효율적인 벡터 검색 엔진
# - 메모리 기반이라 빠르지만, 영속성(persistence)을 원하면 따로 저장 설정이 필요함
# 📊 저장된 벡터 정보 확인
total_vectors = vectorstore.index.ntotal # 총 벡터 개수
dimensions = vectorstore.index.d # 각 벡터의 차원 수 (ex: 1536)
# 🖨️ 벡터 저장소 요약 출력
print(f"There are {total_vectors} vectors with {dimensions:,} dimensions in the vector store")
# → 예: There are 132 vectors with 1,536 dimensions in the vector store
OpenAI Embedding 모델을 통해 각 청크를 벡터화한 후, FAISS에 저장합니다.
이후 벡터 저장소 내 벡터 개수와 차원 수를 출력하여 임베딩 결과를 확인합니다.
💡 Chroma는 SQLite 기반이라 디스크에 저장되지만,
FAISS는 기본적으로 메모리 기반이므로, 껐다 켜도 유지하려면 따로 저장/불러오기 설정을 해줘야 해요.
🛠️ 설치 이슈 해결 가이드
❗ faiss
설치 오류
ModuleNotFoundError: No module named 'faiss'
✅ 해결 방법
💻 Mac 또는 CPU-only 환경에서는 아래 명령어 사용:
pip install faiss-cpu
⚡ GPU(CUDA) 환경이라면:
pip install faiss-gpu
❗ Python 버전 호환 문제
FAISS는 Python 3.12에서 정상 설치되지 않습니다.
✅ 권장 환경
① Python 3.10 환경 만들기
FAISS는 Python 3.12에서는 공식적으로 지원되지 않고, 일반적으로 Python 3.8 ~ 3.10까지만 안정적으로 지원되기 때문에 Python 3.10 또는 3.9로 가상 환경을 새로 만드는 것이 가장 깔끔해요.
conda create -n rag-faiss python=3.10
conda activate rag-faiss
② 필요한 패키지 설치
pip install faiss-cpu
pip install jupyter
pip install langchain openai tiktoken
③ Jupyter 커널 등록
python -m ipykernel install --user --name=rag-faiss --display-name "Python 3.10 (FAISS)"
④ Jupyter Notebook에서 커널 선택
Jupyter에서 상단 메뉴 → 커널 → 커널 변경 → Python 3.10 (FAISS) 선택
❗ dotenv, gradio 설치 오류
ModuleNotFoundError: No module named 'dotenv'
✅ 지금 바로 해볼 수 있는 해결책
1️⃣ 현재 Jupyter Notebook의 Python 경로 확인
아래 셀을 실행해 봅니다:
import sys
print(sys.executable)
출력 예시:
/opt/anaconda3/bin/python
또는 다른 경로일 수 있어요.
2️⃣ Jupyter 커널에서 직접 패키지를 다시 설치해 보기
지금 Jupyter 커널이 사용하는 환경에 확실하게 설치되도록 아래 코드 셀을 실행해 봅니다:
import sys
!{sys.executable} -m pip install python-dotenv --upgrade
이 방식은 현재 노트북이 사용하는 Python 인터프리터에 직접 설치하기 때문에,
대부분의 인식 문제를 해결할 수 있어요.
3️⃣ 설치 후 다시 import 시도
from dotenv import load_dotenv
📌 만약 그래도 안 된다면?
- !which python 또는 !echo $PATH로 현재 Jupyter가 어떤 환경을 보고 있는지 확인해봐야 해요.
- 혹은 Jupyter의 커널 자체가 잘못된 환경을 가리키고 있을 수 있습니다.
- 이 경우에는 ipykernel로 새로운 커널을 등록해 주면 해결됩니다.
'🧠 LLM 엔지니어링' 카테고리의 다른 글
📘 제품 가격 예측 AI 만들기: Amazon 리뷰 데이터로 LLM 학습하기 01 (0) | 2025.04.30 |
---|---|
문서 기반 챗봇 만들기: RAG 실습 with OpenAI, FAISS, Gradio 02 (0) | 2025.04.29 |
LangChain과 Chroma를 활용한 문서 임베딩 시각화 실습 02 (0) | 2025.04.27 |
LangChain과 Chroma를 활용한 문서 임베딩 시각화 실습 01 (1) | 2025.04.26 |
LLM 문서에서 특정 키워드 추출하는 방법 02 (Shopwise 지식기반 예제) (1) | 2025.04.25 |