본문 바로가기
Data & Research

[LLM & RAG] Langchain 기초 - Retriever

by 물박사의 저장공간 2025. 4. 2.

들어온 Query에 대해서  Vector DB에서 이와 유사한 정보를 추출하는 작업을 Retrieval 이라고 했었죠. 이번 포스팅에서는 Retriever에 대해서 정리해보겠습니다. 

 

1. Retriever의 검색유형(Search Type)

1) Similarity Search

질의와 문서 간의 유사도를 기반으로 검색합니다.
벡터 임베딩을 사용하여 의미론적 유사성을 측정합니다.
similarity_score_threshold 옵션을 통해 유사도 점수 임계값을 설정할 수 있습니다.

2) MMR (Maximum Marginal Relevance)

유사도와 다양성을 동시에 고려하여 검색합니다.
관련성이 높으면서도 중복되지 않는 다양한 문서를 반환합니다.
k (반환 문서 수), fetch_k (검색 문서 수) 등의 옵션을 사용하여 검색 결과를 제어할 수 있습니다.

 

2. Multi-query Retriever

Multi-query Retriever는 사용자가 입력한 쿼리의 의미를 LLM을 활용해서 다각도로(여러 버전으로 늘려서) 포착하여 검색 효율성을 높이고자 하는 목적에서 활용됩니다. 예를들어서 '삼성전자의 실적을 알려줘'라는 사용자 쿼리를 줬다고 한다면 LLM에 이 쿼리를 먹여서 '삼성전자의 경영 현황에 대해서 알려줘.', '삼성전자의 최근 영업 실적을 알려줘.' 등등 다각도로 쿼리를 늘려주는 것입니다. 

 

3. Contextual Compression Retriever

Contextual Compression Retriever는 검색된 문서의 문맥을 압축하여 LLM에 필요한 정보만 제공하는 기법입니다. LLM을 사용하여 검색된 문서 중에서 질의와 관련된 핵심 문장이나 구절을 추출하고, 불필요한 정보를 제거합니다.

 

4. Parent Document Retriever

Parent Document Retriever는 문서의 계층적 구조를 활용하여 검색 성능을 향상시키는 기법입니다. 문서를 더 작은 청크(chunk)로 분할하고, 각 청크의 부모 문서(parent document)와의 관계를 저장합니다. 검색 시에는 청크를 사용하여 관련 문서를 찾고, 최종적으로 부모 문서 전체를 반환하여 풍부한 문맥 정보를 제공합니다.

참고) Chunk size와 Retrieval accuracy의 Trade-off : Chunking하는 사이즈가 작으면 Retrieval Accuracy는 높아지지만 전달할 수 있는 context 정보는 줄어듭니다. 

 

from langchain.retrievers import MMRRetriever, ContextualCompressionRetriever, ParentDocumentRetriever
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.llms import OpenAI
from langchain.retrievers.document_compressors import LLMChainFilter, LLMChainExtractor
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate

# 문서 로드 및 청크 분할
loader = TextLoader("your_document.txt")
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
chunks = text_splitter.split_documents(documents)

# 벡터 저장소 생성
embedding = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(chunks, embedding)

# MMR Retriever 생성
mmr_retriever = MMRRetriever.from_documents(documents=chunks, embedding=embedding, search_kwargs={'k': 4})

# Multi Query Retriever 생성
llm = OpenAI()
multi_query_retriever = MultiQueryRetriever.from_llm(retriever=vectorstore.as_retriever(), llm=llm)

# Contextual Compression Retriever 생성
llm = OpenAI()
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(base_compressor=compressor, base_retriever=vectorstore.as_retriever())

# Parent Document Retriever 생성
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)
parent_document_retriever = ParentDocumentRetriever(vectorstore=vectorstore, docstore=InMemoryStore(), parent_splitter=parent_splitter, child_splitter=child_splitter)

# 검색 실행
query = "What is the main topic?"
mmr_results = mmr_retriever.get_relevant_documents(query)
multi_query_results = multi_query_retriever.get_relevant_documents(query)
compression_results = compression_retriever.get_relevant_documents(query)
parent_document_results = parent_document_retriever.get_relevant_documents(query)

# 결과 출력
print("MMR Results:", mmr_results)
print("Multi Query Results:", multi_query_results)
print("Compression Results:", compression_results)
print("Parent Document Results:", parent_document_results)