인덱싱 (Indexing)
인덱싱은 RAG 파이프라인의 첫 번째 단계로, 원본 문서를 검색 가능한 형태로 변환하는 과정입니다. 질문이 들어오기 전에 오프라인으로 미리 수행 하며, 인덱싱 품질이 전체 RAG 성능을 좌우합니다.
인덱싱 파이프라인
1. 문서 로딩 (Document Loading)
다양한 형식의 원본 문서를 텍스트로 변환합니다.
문서 형식 LangChain Loader 특징 PDF PyPDFLoader페이지 단위 분할, 메타데이터 자동 추출 HTML BSHTMLLoaderBeautifulSoup 기반 파싱 Markdown UnstructuredMarkdownLoader헤딩 기반 구조 유지 Word (docx) Docx2txtLoader텍스트 추출 CSV CSVLoader행 단위 문서 생성 JSON JSONLoaderjq 스타일 경로 지정 웹 페이지 WebBaseLoaderURL에서 직접 로딩
고급 문서 파싱 도구
복잡한 레이아웃(표, 그래프, 수식 등)의 문서를 정확하게 파싱하려면 전용 도구를 사용합니다.
Docling (IBM)
PyMuPDF4LLM
Docling 은 IBM Research에서 개발한 문서 변환 도구입니다. PDF, DOCX, PPTX, HTML 등을 Markdown/JSON으로 변환하며, 표 구조 인식 , OCR , 수식 변환 을 지원합니다.from docling.document_converter import DocumentConverter
converter = DocumentConverter()
result = converter.convert( "document.pdf" )
# Markdown으로 변환
markdown = result.document.export_to_markdown()
# LangChain 통합
from docling.chunking import HybridChunker
from langchain_core.documents import Document
chunker = HybridChunker()
chunks = list (chunker.chunk(result.document))
docs = [Document( page_content = chunk.text, metadata = chunk.meta.export_dict()) for chunk in chunks]
특징 설명 표 인식 TableFormer 모델로 복잡한 표 구조 보존 OCR 스캔된 문서, 이미지 내 텍스트 추출 수식 LaTeX 변환 지원 레이아웃 다단 레이아웃, 헤더/푸터 자동 분리
PyMuPDF4LLM 은 PyMuPDF 기반의 LLM 특화 PDF 파서입니다. PDF를 LLM-friendly Markdown 으로 변환하며, 표, 이미지, 메타데이터를 구조적으로 추출합니다.import pymupdf4llm
# Markdown으로 변환
md_text = pymupdf4llm.to_markdown( "document.pdf" )
# 페이지별 청크로 변환
md_chunks = pymupdf4llm.to_markdown( "document.pdf" , page_chunks = True )
# LangChain Document로 변환
docs = pymupdf4llm.to_markdown( "document.pdf" , page_chunks = True )
from langchain_core.documents import Document
lc_docs = [Document( page_content = chunk[ "text" ], metadata = chunk[ "metadata" ]) for chunk in docs]
특징 설명 속도 C 기반으로 매우 빠른 처리 표 변환 Markdown 테이블로 자동 변환 이미지 이미지 추출 및 Base64 인코딩 지원 경량 외부 AI 모델 불필요, 로컬에서 즉시 실행
일반적인 텍스트 중심 PDF에는 PyPDFLoader나 PyMuPDF4LLM으로 충분합니다. 복잡한 표, 수식, 다단 레이아웃이 포함된 문서에는 Docling이 더 정확한 결과를 제공합니다.
from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader
# PDF 로딩
loader = PyPDFLoader( "document.pdf" )
docs = loader.load()
# 웹 페이지 로딩
loader = WebBaseLoader( "https://example.com/article" )
docs = loader.load()
print ( f "로딩된 문서 수: { len (docs) } " )
print ( f "첫 문서 미리보기: { docs[ 0 ].page_content[: 200 ] } " )
print ( f "메타데이터: { docs[ 0 ].metadata } " )
2. 텍스트 전처리
로딩된 문서에서 노이즈를 제거하고 메타데이터를 정리합니다.
import re
def clean_text ( text : str ) -> str :
"""텍스트 전처리: 노이즈 제거 및 정규화"""
# 연속 공백/줄바꿈 정리
text = re.sub( r ' \n {3,} ' , ' \n\n ' , text)
text = re.sub( r ' {2,} ' , ' ' , text)
# 특수 문자 정리
text = re.sub( r ' [ \x00 - \x08\x0b\x0c\x0e - \x1f ] ' , '' , text)
return text.strip()
def enrich_metadata ( doc , source_type : str = "pdf" ):
"""메타데이터 보강"""
doc.metadata[ "source_type" ] = source_type
doc.metadata[ "char_count" ] = len (doc.page_content)
doc.metadata[ "word_count" ] = len (doc.page_content.split())
return doc
3. 청킹 (Chunking)
긴 문서를 검색에 적합한 크기의 청크로 분할합니다. 청킹 전략에 따라 검색 품질이 크게 달라집니다.
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 1000 , # 청크 최대 크기 (문자 수)
chunk_overlap = 200 , # 청크 간 오버랩
separators = [ " \n\n " , " \n " , ". " , " " , "" ],
)
chunks = text_splitter.split_documents(docs)
print ( f "청크 수: { len (chunks) } " )
청킹 전략의 상세 비교 (Fixed-size, Recursive, Semantic 등)는 청킹 전략 페이지에서 다룹니다.
4. 임베딩 (Embedding)
텍스트 청크를 고차원 벡터로 변환합니다. 동일한 의미의 텍스트는 벡터 공간에서 가까운 위치에 매핑됩니다.
주요 임베딩 모델
모델 차원 특징 비용 OpenAI text-embedding-3-small 1536 범용, 빠름, 가성비 유료 (API) OpenAI text-embedding-3-large 3072 고성능, 차원 축소 가능 유료 (API) Cohere embed-v4.0 1024 다국어 지원, 검색 특화 유료 (API) BAAI/bge-m31024 오픈소스, 다국어, Dense+Sparse 무료 intfloat/multilingual-e5-large1024 오픈소스, 다국어 무료
from langchain_openai import OpenAIEmbeddings
# OpenAI 임베딩
embeddings = OpenAIEmbeddings( model = "text-embedding-3-small" )
# 단일 텍스트 임베딩
vector = embeddings.embed_query( "RAG란 무엇인가요?" )
print ( f "벡터 차원: { len (vector) } " ) # 1536
from langchain_huggingface import HuggingFaceEmbeddings
# 오픈소스 임베딩 (로컬 실행)
embeddings = HuggingFaceEmbeddings(
model_name = "BAAI/bge-m3" ,
model_kwargs = { "device" : "cuda" },
encode_kwargs = { "normalize_embeddings" : True },
)
5. 벡터 데이터베이스 저장
임베딩된 벡터를 검색 가능한 데이터베이스에 저장합니다.
주요 벡터 DB 비교
벡터 DB 유형 특징 적합한 환경 Chroma 임베디드 간단한 설정, 로컬 개발 프로토타이핑 Qdrant 서버 고성능 필터링, 다양한 인덱스 프로덕션 Milvus 서버 대규모 확장, GPU 지원 대규모 프로덕션 Weaviate 서버 하이브리드 검색 내장 프로덕션 Pinecone 클라우드 완전 관리형, 서버리스 관리형 서비스 FAISS 라이브러리 인메모리, 초고속 연구/실험
LangChain v1부터 주요 벡터 DB는 전용 패키지로 분리되었습니다.
pip install langchain-chroma # Chroma
pip install langchain-qdrant # Qdrant
pip install langchain-pinecone # Pinecone
pip install langchain-milvus # Milvus
pip install langchain-weaviate # Weaviate
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
embeddings = OpenAIEmbeddings( model = "text-embedding-3-small" )
# 벡터 DB 생성 및 문서 저장
vectorstore = Chroma.from_documents(
documents = chunks,
embedding = embeddings,
collection_name = "my_documents" ,
persist_directory = "./chroma_db" , # 디스크에 영속화
)
# Retriever로 변환
retriever = vectorstore.as_retriever(
search_type = "similarity" ,
search_kwargs = { "k" : 4 },
)
전체 인덱싱 파이프라인
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
# 1. 문서 로딩
loader = PyPDFLoader( "document.pdf" )
docs = loader.load()
# 2. 청킹
splitter = RecursiveCharacterTextSplitter(
chunk_size = 1000 ,
chunk_overlap = 200 ,
)
chunks = splitter.split_documents(docs)
# 3. 임베딩 + 벡터 DB 저장
embeddings = OpenAIEmbeddings( model = "text-embedding-3-small" )
vectorstore = Chroma.from_documents(
documents = chunks,
embedding = embeddings,
persist_directory = "./chroma_db" ,
)
print ( f "원본 문서: { len (docs) } 개 → 청크: { len (chunks) } 개 → 벡터 DB 저장 완료" )
인덱싱 품질 체크리스트
너무 작으면 문맥이 손실되고, 너무 크면 노이즈가 포함됩니다. 일반적으로 500~1500자가 적절하며, 도메인에 따라 조정이 필요합니다.
출처, 페이지 번호, 섹션 제목 등의 메타데이터를 저장하면 검색 시 필터링과 출처 인용에 활용할 수 있습니다.
범용 임베딩 모델이 특정 도메인(의료, 법률 등)에서 성능이 낮을 수 있습니다. 파인튜닝이나 도메인 특화 모델을 고려하세요.
동일한 내용이 여러 청크에 반복되면 검색 결과의 다양성이 떨어집니다. 중복 제거(deduplication)를 적용하세요.