728x90
반응형

LangChain: Vector Store Retriever

대규모 언어 모델(LLM)을 활용한 애플리케이션에서, 외부 지식 기반을 효과적으로 활용하기 위해 RAG(Retrieval-Augmented Generation) 기법이 널리 사용됩니다. 이러한 RAG 시스템의 핵심 구성 요소 중 하나가 바로 Vector Store Retriever입니다.


개념 및 정의

Vector Store Retriever는 벡터 스토어에 저장된 문서 임베딩을 기반으로, 주어진 쿼리와 가장 유사한 문서를 검색하는 도구. 이는 벡터 스토어의 검색 기능을 래핑하여 LangChain의 Retriever 인터페이스에 맞게 구성한 것입니다. 유사도 검색(similarity search), 최대 여백 관련성 검색(Maximum Marginal Relevance, MMR) 등의 메서드를 활용하여 관련 문서를 효율적으로 검색합니다.


단계별 구현 가이드

1. 문서 로드 및 분할

먼저, 분석할 문서를 로드하고 적절한 크기로 분할해야 합니다. 예를 들어, PDF 문서를 로드하고 텍스트를 청크로 나누는 과정은 다음과 같습니다:

from langchain_community.document_loaders import PyMuPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# PDF 문서 로드
loader = PyMuPDFLoader('document.pdf')
data = loader.load()

# 텍스트 분할
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=1000,
    chunk_overlap=200,
    encoding_name='cl100k_base'
)
documents = text_splitter.split_documents(data)

2. 문서 임베딩 생성 및 벡터 스토어 구축

문서의 텍스트를 임베딩하여 벡터 스토어에 저장합니다. 예를 들어, HuggingFace의 한국어 SBERT 모델을 사용하여 FAISS 벡터 스토어를 구축하는 방법은 다음과 같습니다:

from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_community.vectorstores.utils import DistanceStrategy

# 임베딩 모델 설정
embeddings_model = HuggingFaceEmbeddings(
    model_name='jhgan/ko-sbert-nli',
    model_kwargs={'device': 'cpu'},
    encode_kwargs={'normalize_embeddings': True},
)

# 벡터 스토어 생성
vectorstore = FAISS.from_documents(
    documents,
    embedding=embeddings_model,
    distance_strategy=DistanceStrategy.COSINE
)

3. Retriever 생성 및 문서 검색

벡터 스토어에서 Retriever를 생성하고, 쿼리에 대한 관련 문서를 검색합니다. 예를 들어, 가장 유사한 문서 하나를 검색하는 방법은 다음과 같습니다:

# Retriever 생성
retriever = vectorstore.as_retriever(search_kwargs={'k': 1})

# 쿼리 입력
query = '카카오뱅크의 환경목표와 세부추진내용을 알려줘'

# 관련 문서 검색
docs = retriever.get_relevant_documents(query)
print(docs[0].page_content)

다양한 검색 전략

Vector Store Retriever는 기본적으로 유사도 검색을 수행하지만, 다양한 검색 전략을 지원합니다:

  • Maximum Marginal Relevance (MMR): 유사성과 다양성을 균형 있게 고려하여 중복을 줄입니다.
  retriever = vectorstore.as_retriever(search_type="mmr", search_kwargs={"k": 3})
  • 유사도 점수 임계값 설정: 특정 유사도 점수 이상인 문서만 반환합니다.
  retriever = vectorstore.as_retriever(
      search_type="similarity_score_threshold",
      search_kwargs={"score_threshold": 0.5, "k": 5}
  )

고급 활용: 시간 가중 검색기

시간 가중 벡터 스토어 검색기(Time-weighted Vector Store Retriever)는 문서의 마지막 접근 시간에 따라 가중치를 부여하여, 자주 사용되는 문서를 우선적으로 반환합니다. 이는 의미론적 유사성과 시간 가중치를 결합하여 검색 결과의 신선도를 유지하는 데 유용합니다.

from langchain.retrievers import TimeWeightedVectorStoreRetriever
from datetime import datetime, timedelta

# 시간 가중 Retriever 생성
retriever = TimeWeightedVectorStoreRetriever(
    vectorstore=vectorstore,
    decay_rate=0.01,
    k=3
)

# 문서 추가 시, 마지막 접근 시간 설정
retriever.add_documents([
    Document(page_content="예시 문서", metadata={"last_accessed_at": datetime.now() - timedelta(hours=1)})
])

전체 코드

from langchain_community.document_loaders import PyMuPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_community.vectorstores.utils import DistanceStrategy
from langchain.retrievers import TimeWeightedVectorStoreRetriever
from datetime import datetime, timedelta
from langchain.schema import Document

# Step 1: Load and split documents
def load_and_split_documents(file_path):
    loader = PyMuPDFLoader(file_path)
    data = loader.load()
    text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
        chunk_size=1000,
        chunk_overlap=200,
        encoding_name='cl100k_base'
    )
    return text_splitter.split_documents(data)

# Step 2: Create embeddings and vector store
def create_vector_store(documents):
    embeddings_model = HuggingFaceEmbeddings(
        model_name='jhgan/ko-sbert-nli',
        model_kwargs={'device': 'cpu'},
        encode_kwargs={'normalize_embeddings': True},
    )
    return FAISS.from_documents(
        documents,
        embedding=embeddings_model,
        distance_strategy=DistanceStrategy.COSINE
    )

# Step 3: Create retriever and search
def search_documents(vectorstore, query, k=1):
    retriever = vectorstore.as_retriever(search_kwargs={'k': k})
    return retriever.get_relevant_documents(query)

# Advanced: Time-weighted retriever
def create_time_weighted_retriever(vectorstore, decay_rate=0.01, k=3):
    retriever = TimeWeightedVectorStoreRetriever(
        vectorstore=vectorstore,
        decay_rate=decay_rate,
        k=k
    )
    retriever.add_documents([
        Document(page_content="Example document", metadata={"last_accessed_at": datetime.now() - timedelta(hours=1)})
    ])
    return retriever

if __name__ == "__main__":
    # Example usage
    file_path = 'document.pdf'
    query = 'ESG 기업의 환경목표와 세부추진내용을 알려줘'

    # Load and process documents
    documents = load_and_split_documents(file_path)

    # Create vector store
    vectorstore = create_vector_store(documents)

    # Search documents
    results = search_documents(vectorstore, query)
    for doc in results:
        print(doc.page_content)

Vector Store Load Local

from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.retrievers import TimeWeightedVectorStoreRetriever
from datetime import datetime, timedelta
from langchain.schema import Document

# Step 1: Load existing vector store
def load_vector_store(vector_store_path):
    embeddings_model = HuggingFaceEmbeddings(
        model_name='jhgan/ko-sbert-nli',
        model_kwargs={'device': 'cpu'},
        encode_kwargs={'normalize_embeddings': True},
    )
    return FAISS.load_local(vector_store_path, embedding=embeddings_model)

# Step 2: Search documents using the loaded vector store
def search_documents(vectorstore, query, k=1):
    retriever = vectorstore.as_retriever(search_kwargs={'k': k})
    return retriever.get_relevant_documents(query)

# Step 3: Use time-weighted retriever for advanced search
def create_time_weighted_retriever(vectorstore, decay_rate=0.01, k=3):
    retriever = TimeWeightedVectorStoreRetriever(
        vectorstore=vectorstore,
        decay_rate=decay_rate,
        k=k
    )
    retriever.add_documents([
        Document(page_content="Example document", metadata={"last_accessed_at": datetime.now() - timedelta(hours=1)})
    ])
    return retriever

if __name__ == "__main__":
    # Example usage
    vector_store_path = 'path_to_existing_vector_store'
    query = 'ESG 기업의 환경목표와 세부추진내용을 알려줘'

    # Load the existing vector store
    vectorstore = load_vector_store(vector_store_path)

    # Search documents
    results = search_documents(vectorstore, query)
    for doc in results:
        print(doc.page_content)

주요 변경점:

  1. 벡터 스토어 로드: FAISS.load_local 메서드를 사용하여 기존 벡터 스토어를 로드.
  2. 임베딩 모델 재사용: 로드 시 동일한 임베딩 모델(HuggingFaceEmbeddings)을 사용.
  3. 검색 및 고급 검색: 기존 벡터 스토어를 활용하여 문서를 검색하거나 시간 가중 검색기 생성.

마무리

Vector Store Retriever는 LangChain에서 RAG 시스템을 구축할 때 핵심적인 역할을 합니다. 문서의 임베딩을 생성하고 벡터 스토어에 저장한 후, 다양한 검색 전략을 활용하여 쿼리에 대한 관련 정보를 효율적으로 검색할 수 있습니다. 이를 통해 LLM의 응답을 외부 지식으로 보강하여 더 정확하고 신뢰할 수 있는 결과를 얻을 수 있습니다.


Keywords

LangChain, Vector Store Retriever, FAISS, HuggingFaceEmbeddings, PyMuPDFLoader, RecursiveCharacterTextSplitter, RAG, MMR, TimeWeightedVectorStoreRetriever, 유사도 검색

728x90
반응형

+ Recent posts