벡터 저장소(VectorStore)는 Retrieval-Augmented Generation (RAG) 시스템의 네 번째 단계로, 이전 단계에서 생성된 임베딩 벡터를 효율적으로 저장하고 관리하는 과정입니다. 이 단계는 향후 검색에서 벡터들을 빠르게 조회하고 관련 문서를 신속하게 찾아내는 데 필수적입니다.
빠른 검색 속도: 임베딩 벡터를 효과적으로 저장하고 색인화하면 대량의 데이터에서도 관련 정보를 빠르게 검색할 수 있습니다.
스케일러빌리티: 데이터가 증가함에 따라 벡터 저장소는 이를 수용할 수 있어야 합니다. 효율적인 저장 구조는 데이터베이스의 확장성을 보장하고, 시스템의 성능 저하 없이 대규모 데이터를 관리할 수 있도록 합니다.
의미 검색(Semantic Search) 지원: 벡터 저장소는 키워드 기반 검색이 아닌 의미적으로 유사한 문장을 검색할 수 있도록 지원합니다. 이는 사용자 질문과 의미상으로 유사한 단락을 조회할 수 있게 해줍니다.
벡터 저장소는 RAG 시스템의 검색 기능과 직접적으로 연결되어 있으며, 전체 시스템의 응답 시간과 정확성에 큰 영향을 미칩니다. 데이터를 잘 관리하고 필요할 때 즉시 접근할 수 있도록 함으로써, 사용자에게 신속하고 정확한 정보를 제공합니다.
다음은 FAISS(VectorStore 중 하나)를 사용하여 벡터 저장소를 생성하고 데이터를 저장하는 코드 예시입니다.
from langchain_community.vectorstores import FAISS
# 단계 4: DB 생성(Create DB) 및 저장
# documents: 텍스트를 임베딩한 결과물들이 포함된 리스트
# embeddings: 임베딩 객체
# 벡터스토어를 생성합니다.
vectorstore = FAISS.from_documents(documents=documents, embedding=embeddings)
# 생성된 벡터스토어를 사용해 검색 작업을 수행할 수 있습니다.
이 코드를 통해 생성된 벡터 저장소는 다양한 검색 작업에 사용될 수 있으며, 특히 RAG 시스템에서 중요한 역할을 합니다.
Chroma는 벡터 데이터를 효율적으로 저장하고 검색하는 오픈 소스 벡터 데이터베이스로, 개발자의 생산성과 행복을 목표로 설계되었습니다. 아래는 Chroma 벡터 저장소의 주요 기능과 사용 예제를 다룹니다.
# 필요한 패키지 설치
%pip install chromadb langchain-openai langchain_community
# API 키를 환경 변수로 관리하기 위한 설정
from dotenv import load_dotenv
load_dotenv()
from langchain_community.document_loaders import TextLoader
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 텍스트 파일을 로드하고 문서로 변환
loader1 = TextLoader("data/nlp-keywords.txt")
loader2 = TextLoader("data/finance-keywords.txt")
# 텍스트 분할 설정
text_splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=0)
split_doc1 = loader1.load_and_split(text_splitter)
split_doc2 = loader2.load_and_split(text_splitter)
# 문서 개수 확인
len(split_doc1), len(split_doc2)
from langchain_chroma import Chroma
# 기본 벡터 저장소 생성
db = Chroma.from_documents(
documents=split_doc1, embedding=OpenAIEmbeddings(), collection_name="my_db"
)
# 저장할 경로 지정
DB_PATH = "./chroma_db"
# 벡터 저장소를 디스크에 저장
persist_db = Chroma.from_documents(
split_doc1, OpenAIEmbeddings(), persist_directory=DB_PATH, collection_name="my_db"
)
# 디스크에서 벡터 저장소 로드
persist_db = Chroma(
persist_directory=DB_PATH,
embedding_function=OpenAIEmbeddings(),
collection_name="my_db",
)
# 저장된 데이터 확인
persist_db.get()
# 문자열 리스트로 벡터 저장소 생성
db2 = Chroma.from_texts(
["안녕하세요. 정말 반갑습니다.", "제 이름은 테디입니다."],
embedding=OpenAIEmbeddings(),
)
# 유사도 검색
db.similarity_search("TF IDF 에 대하여 알려줘")
# 결과 개수 지정
db.similarity_search("TF IDF 에 대하여 알려줘", k=2)
# 메타데이터 필터 적용
db.similarity_search(
"TF IDF 에 대하여 알려줘", filter={"source": "data/nlp-keywords.txt"}, k=2
)
from langchain_core.documents import Document
# 문서 추가 및 업데이트
db.add_documents(
[
Document(
page_content="안녕하세요! 이번엔 도큐먼트를 새로 추가해 볼께요",
metadata={"source": "mydata.txt"},
id="1",
)
]
)
# 문서 삭제
db.delete(ids=["1"])
# 컬렉션 초기화
db.reset_collection()
# 기본 검색기 생성
retriever = db.as_retriever()
# MMR 알고리즘을 사용한 다양성 높은 검색
retriever = db.as_retriever(
search_type="mmr", search_kwargs={"k": 6, "lambda_mult": 0.25, "fetch_k": 10}
)
# 특정 임계값 이상의 유사도를 가진 문서만 검색
retriever = db.as_retriever(
search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.8}
)
Chroma는 텍스트 외에도 이미지 등의 멀티모달 데이터를 저장하고 검색할 수 있습니다.
from langchain_experimental.open_clip import OpenCLIPEmbeddings
# OpenCLIP 임베딩 함수 객체 생성
image_embedding_function = OpenCLIPEmbeddings(
model_name="ViT-H-14-378-quickgelu", checkpoint="dfn5b"
)
# 이미지 경로 설정
image_uris = ["path_to_image1.jpg", "path_to_image2.jpg"]
# 벡터 저장소 생성 및 이미지 추가
image_db = Chroma(
collection_name="multimodal",
embedding_function=image_embedding_function,
)
image_db.add_images(uris=image_uris)
# 이미지 검색
retriever = image_db.as_retriever(search_kwargs={"k": 3})
image_retriever = ImageRetriever(retriever)
result = image_retriever.invoke("A Dog on the street")
Facebook AI Similarity Search (FAISS)는 밀집 벡터의 효율적인 유사도 검색 및 클러스터링을 위한 강력한 라이브러리입니다. 이 튜토리얼에서는 FAISS를 사용하여 벡터 저장소를 생성, 관리 및 검색하는 방법을 설명합니다.
먼저 필요한 패키지를 설치하고 환경을 설정합니다.
# 필요한 패키지 설치
%pip install faiss-cpu langchain langchain_openai langchain_community
# API 키를 환경변수로 관리하기 위한 설정
from dotenv import load_dotenv
load_dotenv()
텍스트 데이터를 로드하고 분할합니다.
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 텍스트 파일을 로드하고 문서로 변환
loader1 = TextLoader("data/nlp-keywords.txt")
loader2 = TextLoader("data/finance-keywords.txt")
# 텍스트 분할 설정
text_splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=0)
split_doc1 = loader1.load_and_split(text_splitter)
split_doc2 = loader2.load_and_split(text_splitter)
# 문서 개수 확인
len(split_doc1), len(split_doc2)
FAISS 벡터 저장소를 생성하고 초기화합니다.
import faiss
from langchain_community.vectorstores import FAISS
from langchain_community.docstore.in_memory import InMemoryDocstore
from langchain_openai import OpenAIEmbeddings
# 임베딩 생성
embeddings = OpenAIEmbeddings()
dimension_size = len(embeddings.embed_query("hello world"))
# FAISS 벡터 저장소 생성
db = FAISS(
embedding_function=embeddings,
index=faiss.IndexFlatL2(dimension_size),
docstore=InMemoryDocstore(),
index_to_docstore_id={},
)
FAISS 벡터 저장소를 문서로부터 생성합니다.
db = FAISS.from_documents(documents=split_doc1, embedding=embeddings)
FAISS 벡터 저장소에서 유사한 문서를 검색합니다.
# 유사도 검색
results = db.similarity_search("TF IDF 에 대하여 알려줘", k=2)
for result in results:
print(result.page_content)
벡터 저장소에 문서를 추가하고 삭제할 수 있습니다.
from langchain_core.documents import Document
# 문서 추가
db.add_documents([Document(page_content="새로운 문서", metadata={"source": "mydata.txt"})])
# 문서 삭제
db.delete(["new_doc1"])
FAISS 저장소를 로컬에 저장하고 다시 로드할 수 있습니다.
# 로컬에 저장
db.save_local(folder_path="faiss_db", index_name="faiss_index")
# 로컬에서 로드
loaded_db = FAISS.load_local(
folder_path="faiss_db",
index_name="faiss_index",
embeddings=embeddings,
allow_dangerous_deserialization=True,
)
두 개의 FAISS 객체를 병합할 수 있습니다.
db2 = FAISS.from_documents(documents=split_doc2, embedding=embeddings)
db.merge_from(db2)
FAISS 벡터 저장소를 검색기로 변환하여 다양한 검색 옵션을 사용할 수 있습니다.
# 기본 검색기 생성
retriever = db.as_retriever()
# MMR 검색
retriever = db.as_retriever(search_type="mmr", search_kwargs={"k": 6, "lambda_mult": 0.25})
results = retriever.invoke("Word2Vec 에 대하여 알려줘")
for result in results:
print(result.page_content)
Pinecone은 대규모 데이터셋에 대해 고성능 벡터 저장 및 검색을 제공하는 벡터 데이터베이스입니다. 이 튜토리얼에서는 Pinecone을 사용하여 벡터 데이터를 저장, 관리, 검색하는 방법을 안내합니다.
Pinecone은 다음과 같은 장점이 있습니다:
그러나 비용이 높고, 커스터마이징에 제한이 있으며, 데이터 주권 문제도 있을 수 있습니다. 반면, Chroma와 FAISS는 오픈소스이며, 로컬에서 실행 가능하고 초기 비용이 낮습니다. 하지만 확장성에서는 Pinecone에 비해 제한적입니다.
from dotenv import load_dotenv
load_dotenv()
PDF 파일을 로드하고 텍스트로 분할합니다.
from langchain_community.document_loaders import PyMuPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import glob
text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50)
split_docs = []
files = sorted(glob.glob("data/*.pdf"))
for file in files:
loader = PyMuPDFLoader(file)
split_docs.extend(loader.load_and_split(text_splitter))
len(split_docs)
전처리된 문서에서 필요한 메타데이터를 추출합니다.
from langchain_teddynote.community.pinecone import preprocess_documents
contents, metadatas = preprocess_documents(
split_docs=split_docs,
metadata_keys=["source", "page", "author"],
min_length=5,
use_basename=True,
)
Pinecone에서 새로운 인덱스를 생성합니다.
from langchain_teddynote.community.pinecone import create_index
import os
pc_index = create_index(
api_key=os.environ["PINECONE_API_KEY"],
index_name="teddynote-db-index",
dimension=4096,
metric="dotproduct",
)
from langchain_teddynote.community.pinecone import create_sparse_encoder, fit_sparse_encoder
sparse_encoder = create_sparse_encoder(stopwords(), mode="kiwi")
saved_path = fit_sparse_encoder(sparse_encoder=sparse_encoder, contents=contents, save_path="./sparse_encoder.pkl")
문서를 Pinecone DB에 업로드합니다.
from langchain_teddynote.community.pinecone import upsert_documents
from langchain_upstage import UpstageEmbeddings
upsert_documents(
index=pc_index,
namespace="teddynote-namespace-01",
contents=contents,
metadatas=metadatas,
sparse_encoder=sparse_encoder,
embedder=UpstageEmbeddings(),
batch_size=32,
)
Pinecone을 사용한 하이브리드 검색기를 생성하고, 검색을 수행합니다.
from langchain_teddynote.community.pinecone import init_pinecone_index, PineconeKiwiHybridRetriever
pinecone_params = init_pinecone_index(
index_name="teddynote-db-index",
namespace="teddynote-namespace-01",
api_key=os.environ["PINECONE_API_KEY"],
sparse_encoder_path="./sparse_encoder.pkl",
stopwords=stopwords(),
tokenizer="kiwi",
embeddings=UpstageEmbeddings(),
top_k=5,
alpha=0.5,
)
pinecone_retriever = PineconeKiwiHybridRetriever(**pinecone_params)
# 예시 검색
search_results = pinecone_retriever.invoke("gpt-4o 미니 출시 관련 정보에 대해서 알려줘")
for result in search_results:
print(result.page_content)
print(result.metadata)
print("\n====================\n")
동적 파라미터를 사용하여 검색 결과를 조정할 수 있습니다.
# 필터링을 적용한 검색
search_results = pinecone_retriever.invoke(
"앤스로픽의 claude 출시 관련 내용을 알려줘",
search_kwargs={"filter": {"page": {"$lt": 5}}, "k": 2},
)
for result in search_results:
print(result.page_content)
print(result.metadata)
print("\n====================\n")