지식 베이스 검색 엔진 만들기
지식 베이스 검색 엔진 만들기 (Build a semantic search engine with LangChain)
검색이라고 하면 키워드 매칭을 떠올리기 쉽죠. 하지만 "개한테 친근한 반려동물" 같은 질문은 "loyal dog companion"이라는 문장과 단어가 겹치지 않아도 의미가 통한다는 걸 알 수 있어요. LangChain으로 의미론적 검색(semantic search) 엔진을 만들면, 단어가 아니라 뜻을 기준으로 문서를 찾을 수 있어요. 여기서는 PDF에서 문서를 뽑아 임베딩하고, 벡터 스토어에 색인한 뒤 검색하는 전체 흐름을 차근차근 따라가 볼게요.
개요 (Overview)
이 튜토리얼에서 배울 내용은 다음과 같아요.
- PDF에서
Document객체 만들기 - 임베딩 생성하기
- PDF 로드 및 분할하기
- 벡터 스토어에 청크를 색인하고 유사도로 검색하기
- 스토어를 리트리버로 감싸기
검색 엔진 위에 최소한의 RAG 구현도 함께 다룹니다.
개념 (Concepts)
설정 (Setup)
의존성을 설치합니다.
uv add pypdf
더 자세한 내용은 설치 가이드를 참고하세요.
문서 만들기 (Create documents)
LangChain은 텍스트 한 단위와 관련 메타데이터를 위한 Document 추상화를 구현해요. 세 가지 속성을 가집니다.
page_content: 내용을 나타내는 문자열metadata: 임의의 메타데이터를 담는 dictid: (선택) 문서의 문자열 식별자
metadata는 문서의 출처, 다른 문서와의 관계, 기타 정보를 담을 수 있어요. 개별 Document 하나는 보통 더 큰 문서의 한 조각(chunk)을 나타내곤 해요.
from langchain_core.documents import Document
documents = [
Document(
page_content="Dogs are great companions, known for their loyalty and friendliness.",
metadata={"source": "mammal-pets-doc"},
),
Document(
임베딩 생성하기 (Generate embeddings)
LangChain은 많은 제공자의 임베딩을 지원해요. 텍스트를 숫자 벡터로 어떻게 변환할지 모델을 선택하면 됩니다.
import getpass
import os
if not os.environ.get("VOYAGE_API_KEY"):
os.environ["VOYAGE_API_KEY"] = getpass.getpass("Enter API key for Voyage AI: ")
from langchain-voyageai import VoyageAIEmbeddings
벡터 스토어 선택하기 (Select a vector store)
LangChain VectorStore 객체는 스토어에 텍스트와 Document 객체를 추가하고, 유사도 메트릭으로 검색하는 역할을 해요.
PDF 로드 및 분할 (Load and split a PDF)
PyPDF로 PDF 페이지를 로드하는 최소한의 헬퍼를 만들어 볼게요.
import pypdf
from langchain_core.documents import Document
# Below is a minimal helper for demonstration purposes.
def load_pdf_pages(file_path: str) -> list[Document]:
reader = pypdf.PdfReader(file_path)
return [
Document(
page_content=page.extract_text() or "",
metadata={"source": file_path, "page": i},
)
for i, page in enumerate(reader.pages)
]
RecursiveCharacterTextSplitter는 새 줄 같은 일반적인 구분자에서 재귀적으로 분할하며, 각 청크가 목표 크기가 될 때까지 진행해요. 범용 텍스트에 권장되는 스플리터예요.
문서 색인하기 (Index documents)
청크들을 벡터 스토어에 색인합니다.
ids = vector_store.add_documents(documents=all_splits)
대부분의 벡터 스토어 통합은 기존 스토어에 연결하는 것도 지원해요(예: 클라이언트나 인덱스 이름으로). 자세한 내용은 특정 통합 문서를 참고하세요.
벡터 스토어 검색하기 (Query the vector store)
VectorStore에 문서를 추가했다면 이제 검색할 수 있어요. 지원하는 방식이 여러 가지 있습니다.
- 동기·비동기 방식
- 문자열 쿼리·벡터 쿼리
- 유사도 점수 포함·미포함
- 유사도와 최대 주변 관련성(maximum marginal relevance, 다양성과 유사도의 균형)
이 메서드들은 일반적으로 Document 객체 리스트를 반환해요.
벡터로 검색하기 (Search by vector)
리트리버 사용하기 (Use retrievers)
LangChain VectorStore 객체는 Runnable을 상속하지 않아요. 반면 리트리버는 Runnable이라서 동기·비동기 invoke, batch 같은 표준 메서드를 지원합니다.
from typing import List
from langchain_core.documents import Document
from langchain_core.runnables import chain
@chain
def retriever(query: str) -> List[Document]:
return vector_store.similarity_search(query, k=1)
retriever.batch(
벡터 스토어는 VectorStoreRetriever를 반환하는 as_retriever 메서드를 구현해요. 이 리트리버는 search_type과 search_kwargs를 노출해 내부 스토어 메서드를 선택하고 파라미터화합니다.
VectorStoreRetriever는 "similarity"(기본값), "mmr"(최대 주변 관련성), "similarity_score_threshold" 검색 유형을 지원해요. 마지막 옵션은 유사도 점수로 문서를 필터링할 때 사용합니다.