목차
- 1. Retriever 는 무엇인가
- 2. Retriever의 종류
- 3. Retriever 만들기
- 4. 하이브리드 검색(키워드 검색)
- 5. 실제 프로젝트에서 필요한 사항 정리

출처 : Langchain 공식문서
How-to guides | 🦜️🔗 LangChain
Here you’ll find answers to “How do I….?” types of questions.
python.langchain.com
1. Retriever 는 무엇인가
- Retriever는 주어진 질문에 대해 벡터스토어 에서 관련된 정보를 검색
- 검색은 챗봇이 챗 모델의 훈련 데이터 외부의 데이터로 응답을 증강하는 데 사용
retriever를 적용하는 것은 다음을 고려한다.
방법이 다양하기 때문에, 상황과 데이터에 맞는 방법을 채택한다.
- Custom으로 만들 것인가
- 간단하게 만들 것인가.
- 다양한 방법을 조합해서 만들 것인가
- 어느 부분에 적용할 것인가 (prompt에 문서 삽입, 질문을 메타에 맞게 변형, 명확한 질문으로 질문 재생성 등등...)
검색은 다양한 방법이 있을 수 있다.
- 어휘 검색
- 벡터 검색
- 관계형 데이터베이스 검색
- 그래프 데이터베이스 검색
Langchain에서는 다음과 같은 검색 방법을 제공한다.
- Input: 쿼리 (string, 문자열)
- Output: 문서 리스트 (표준화된 LangChain Document 객체)
2. Retriever의 종류
Retriever 종류 | 분류 | 내용 |
Vectorstore Retriever |
설명 | 각 텍스트 조각마다 임베딩을 생성하여 검색을 수행 주로 유사도 검색을 위해 벡터 공간에 문서를 매핑하여 활용 |
장점 | 가장 기본적이고 쉽게 시작 | |
ParentDocument Retriever |
설명 | 대규모 문서를 상위 문서 단위로 묶고, 맥락을 보존한 채 검색을 단순화하려는 시나리오 임베딩 공간에서 가장 유사한 청크를 찾은 후 해당 청크가 속한 전체 문서를 반환 |
장점 | - 긴 문서를 관리해야 할 때 유용 - 전체 문서를 검색할 때 유용. - 검색 시, 상위 문서만 확보하면 되기에 적용 로직이 단순 - 문서의 '맥락'을 그대로 유지해야 할 때 유용 - 낮은 노이즈(자잘하게 쪼개진 문서들에서 발생할 수 있는 중복 검색 결과나 불필요한 노이즈를 줄임) |
|
한계 | - 문서의 길이가 너무 짧은 데이터가 많을때, 문서의 개수가 적을 때에는 필요없음 - 아주 세밀한 문장 단위 검색이 필요한 경우는 부적합함 - 사용자가 특정 섹션 정보만 필요하고, 다른 섹션은 크게 의미가 없는 질의를 한다면, 굳이 ‘전체 문서’ 또는 그 상당 부분을 반환할 필요가 없음 (질의 자체가 특정 섹션에 강하게 종속되어 있을 때) - 세밀한 Ranking, Scoring이 중요한 경우 |
|
Multi Vector Retriever |
설명 | 같은 문서에 여러 개의 벡터를 생성하는 방식 |
장점 | - 한 문서가 여러 주제를 다루거나, 다양한 내용이 뒤섞여 있는 경우 좋음 - 세밀한 문단/문장 단위 검색이 필요한 경우 - 정밀도가 중요한 검색 또는 Q&A 시스템 - 특화된 임베딩 조합 가능(기술 용어가 잦은 구간, 숫자·통계 데이터가 주된 구간, 문어체·구어체 구간 등) - 길이가 긴 문서 처리에 유리(길이가 매우 긴 문서를 단일 벡터로 만들면 임베딩 하나가 거대 -> 유사도 검색시 힘들어짐) |
|
한계 | - 리소스(메모리, 인덱스 크기)와 운영 비용이 많이듦 - 문서 자체가 너무 짧거나 단일 토픽으로만 구성된 경우 굳이 필요없음 - 구현하기가 복잡함 - 검색 속도가 느려짐 |
|
Self Query Retriever |
설명 | LLM을 사용해 사용자의 질문을 자체를 분석·변환 주로 질문이 문서의 메타데이터와 관련된 경우, LLM이 질문을 검색할 문자열과 메타데이터 필터로 변환. 해당 조건에 맞춰 검색을 수행 |
장점 | - 문서 메타데이터나 구조화된 필드가 있는 경우, 조건으로 필터링 가능 - 질의 의도를 깊이 이해, 정교한 검색 정확도 - 확장성 (문서 메타데이터(작성자, 날짜, 토픽, 지역, 버전 등)가 늘어나더라도, 모델이 질의 문장에서 이를 찾아낼 수만 있다면, 새로운 필터 타입도 자동으로 대응 가능) |
|
한계 | - 구조화된 필드나 메타데이터가 거의 없는 경우, 사용 불가능 - LLM(대형 언어모델) 적용 비용/복잡도가 부담스러운 경우 어려움 - 질의 구조가 거의 단순하거나, 필터 없이 전체에서 찾는 단순한 검색이 주된 경우 필요없음 |
|
Contextual Compression Retriever |
설명 | 문서 압축 기법 검색된 문서에 불필요한 정보가 너무 많을 때 유용 검색 후, 후처리 과정으로 LLM 또는 임베딩을 사용해 가장 중요한 정보만 추출 |
장점 | - 문서가 길고, 사용자가 원하는 정보는 그 일부일 때 유용 - LLM 처리 비용(토큰 수)이 중요한 경우 절약 가능 - 노이즈가 많은 데이터 필터링 - 정확하고 간결한 요약이 필요한 상황 |
|
한계 | - 문서가 원체 짧거나, 압축이 필요 없는 상황은 필요없음' - 질문 맥락이 광범위해, 문서의 여러 부분이 두루두루 관련될 때, 요약해도 압축이 안됨 - 잘못된 압축으로 정답의 근거가 사라지면, 최종 답변이 틀릴 수 있다 - 추출·요약 과정 자체가 비싸거나 복잡해질 수 있음 |
|
Time-Weighted Vectorstore Retriever |
설명 | 최신성 기준을 결합하여 검색 최신 문서 검색이 중요한 경우에 유용 |
장점 | - 뉴스 기사, 소셜 미디어 포스트, 기술 문서(버전이 자주 업데이트됨) 등은 최근 정보가 중요한 경우 유용, 자주 업데이트되거나 변동이 큰 문서를 다뤄야 할 때 - ‘가장 최신’ 또는 ‘가장 최근의 이벤트’가 핵심인 질의가 자주 발생할 때 |
|
한계 | - 문서 업데이트 빈도가 낮고, 대부분 오래된 문서로 구성된 데이터 - 정확한 시간 정보(타임스탬프)가 없는 데이터셋은 못씀 |
|
Multi-Query Retriever |
설명 | LLM을 사용해 원래 질문에서 여러 개의 쿼리를 생성 각 쿼리에 대해 문서를 검색한 후 이를 조합 |
장점 | - 질의가 포괄적이거나, 여러 표현이 가능할 때 - 다양한 표현(동의어, 유의어) 커버 - 검색 누락(정보 손실) 감소 |
|
한계 | - 질의가 매우 구체적이고 단순할 때, 필요없음 - 리소스(시간, 비용) 제한이 엄격한 경우 - 검색 노이즈가 크게 증가할 위험이 있음 |
|
Ensemble Retriever |
설명 | 여러 가지 검색 방식을 결합 다양한 retriever를 사용해 각기 다른 방식으로 문서를 검색, 이를 합쳐서 최종적으로 문서를 반환 |
장점 | 다양한 방법을 취합해 성능 고도화 | |
단점 |
리소스 과소비, 느려질 수 있음 | |
Long-Context Reorder Retriever |
설명 | 긴 문맥을 처리하는 모델에서 중간 정보를 무시하는 경향이 있을 때 사용 검색된 문서를 재배열하여 가장 유사한 정보를 처음과 끝에 배치하여, 긴 문맥에서 유용한 정보를 놓치지 않도록 한다. |
장점 | 간단한 구현 | |
단점 | - 모델별 편향적임 (모든 모델이 맨 끝의 내용을 중요하게 반영하는지 확인 필요) - 맥락 훼손 가능성 - 청크 선별 정확도가 보장 되어야 함 |
3. Retriever 만들기
3.1. Vectorstore Retriever
벡터 기반 Retriver : 베이스가 되는 Retriever
vectorstore.as_retriiver() 메서드로 생성
예시코드(FAISS 사용)는 아래와 같다.
from langchain.vectorstores import FAISS
vectorstore = FAISS.from_documents(docs, embeddings)
retriever = vectorstore.as_retriever()
다음은 전체 코드이다.
실행 순서는 다음과 같다.
1. 문서를 청크로 분할 (CharacterTextSplitter)
2. 임베딩을 생성 (OpenAIEmbeddings)
3. 벡터스토어를 구축 (FAISS)
4. Retriever 정의 (vectorstore.as_retriever())
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.text_splitter import CharacterTextSplitter
from langchain.document_loaders import TextLoader
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
# 문서 로드
loader = TextLoader('your_document.txt')
documents = loader.load()
# 텍스트 분할
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
# 임베딩 및 벡터스토어 생성
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(docs, embeddings)
# Retriever 설정
retriever = vectorstore.as_retriever()
# QA 체인 생성
qa = RetrievalQA.from_chain_type(
llm=OpenAI(),
chain_type="stuff",
retriever=retriever
)
# 질문에 답변
query = "여기에 질문을 입력하세요."
answer = qa.run(query)
print(answer)
3.2. Self Query Retriever
설명 : 질문에 대한 메타데이터를 함께 생성해주는 Retirever
SelfQueryRetriever: Retirever 생성
metadata: 문서에 메타데이터를 추가하여 필터링에 사용
from langchain.retrievers import SelfQueryRetriever
from langchain_chroma import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.llms import OpenAI
from langchain.schema import Document
# 문서 생성
documents = [
Document(page_content="이 문서는 과학에 관한 내용입니다.", metadata={"title": "과학", "year": 2025}),
Document(page_content="이 문서는 예술에 관한 내용입니다.", metadata={"title": "예술", "year": 2024}),
]
# 메타데이터 정의
metadata_field_info = [
AttributeInfo(
name="title",
description="The category of the cosmetic product. One of ['과학', '예술']",
type="string",
),
AttributeInfo(
name="year",
description="The year the cosmetic product was released",
type="integer",
),
]
# 벡터스토어 생성
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(docs, embeddings)
# Self Query Retriever 설정
llm = OpenAI()
retriever = SelfQueryRetriever.from_llm(
llm=llm,
vectorstore=vectorstore,
metadata_field_info=metadata_field_info
)
3.3. Contextual Compression Retriever
설명 : 문서 길이가 너무 길때, 텍스트를 압축해서 핵심 부분만 사용
압축 방법에는 LLMChainExtractor, LLMChainFilter, LLMListwiseRerank, EmbeddingsFilter 가 있다.
ContextualCompressionRetriever : Retriever 생성
3.3.1. LLMChainExtractor 사용
LLM을 사용하여 문서의 중요한 부분만 압축(각 문서마다 요약함)
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import OpenAI
llm = OpenAI(temperature=0)
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor, base_retriever=retriever
)
3.3.2. LLMChainFilter 사용
문서를 필터링해서 반환함
from langchain.retrievers.document_compressors import LLMChainFilter
_filter = LLMChainFilter.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=_filter, base_retriever=retriever
)
3.3.3. LLMListwiseRerank 사용
문서를 다시 순위를 매김.
from langchain.retrievers.document_compressors import LLMListwiseRerank
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
_filter = LLMListwiseRerank.from_llm(llm, top_n=1)
compression_retriever = ContextualCompressionRetriever(
base_compressor=_filter, base_retriever=retriever
)
3.3.4. EmbeddingsFilter 사용
유사한 임베딩을 가진 문서만 반환(필터링함)
from langchain.retrievers.document_compressors import EmbeddingsFilter
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
embeddings_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)
compression_retriever = ContextualCompressionRetriever(
base_compressor=embeddings_filter, base_retriever=retriever
)
3.4. Time-Weighted Vectorstore Retriever
설명 : 시간에 따른 가중치를 적용하여 최신 문서를 우선적으로 검색
TimeWeightedVectorStoreRetriever : Retirever 생성
decay_rate: 시간에 따른 중요도 감소율을 설정
from langchain.retrievers import TimeWeightedVectorStoreRetriever
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
import datetime
# 문서에 타임스탬프 추가
documents = [
Document(page_content="2021년의 정보입니다.", metadata={"timestamp": datetime.datetime(2021, 1, 1)}),
Document(page_content="현재의 정보입니다.", metadata={"timestamp": datetime.datetime.now()}),
]
# 벡터스토어 생성
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(documents, embeddings)
# Time-Weighted Retriever 설정
retriever = TimeWeightedVectorStoreRetriever(
vectorstore=vectorstore,
decay_rate=0.1,
time_key='timestamp'
)
# 질문에 대한 검색
query = "최신 정보를 알려주세요."
docs = retriever.get_relevant_documents(query)
for doc in docs:
print(doc.page_content)
3.5. Multi-Query Retriever
설명 : 여러 쿼리를 생성, 각 쿼리를 이용해 문서를 검색, 합집합을 사용함
필요 객체 : Retriever, llm
MultiQueryRetriever : Retriever 생성
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI
# VectorDB
embedding = OpenAIEmbeddings()
vectordb = Chroma.from_documents(documents=splits, embedding=embedding)
question = "What are the approaches to Task Decomposition?"
llm = ChatOpenAI(temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=vectordb.as_retriever(), llm=llm
)
3.6. Ensemble Retriever
설명 : 여러 Retriever의 결과를 결합
EnsembleRetriever: Retriever 생성
retrievers: 결합하고자 하는 Retriever들의 리스트를 설정합니다.
from langchain.retrievers import EnsembleRetriever
# 여러 Retriever 설정
retriever1 = vectorstore1.as_retriever()
retriever2 = vectorstore2.as_retriever()
# Ensemble Retriever 설정
ensemble_retriever = EnsembleRetriever(
retrievers=[retriever1, retriever2], weights=[0.5, 0.5]
)
# 질문에 대한 검색
query = "통합된 검색 결과를 보여주세요."
docs = ensemble_retriever.get_relevant_documents(query)
for doc in docs:
print(doc.page_content)
3.7. Long-Context Reorder Retriever
설명: retriever의 검색 결과값에서 문서들의 순서를 다시 정렬한다.
LongContextReorder : 문서 재 정렬기
from langchain_community.document_transformers import LongContextReorder
reordering = LongContextReorder()
docs = retriever.invoke(query)
reordered_docs = reordering.transform_documents(docs)
4. 하이브리드 검색(키워드 검색)
키워드 기반으로 검색을 하는 기능도 있다. (벡터데이터베이스에서 지원하는지 확인)
Astra DB 벡터스토어는 body_search로 검색을 필터링하는 데 사용할 수 있다.
template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
retriever = vectorstore.as_retriever()
# config 추가
configurable_retriever = retriever.configurable_fields(
# 키워드 필드
search_kwargs=ConfigurableField(
id="search_kwargs",
name="Search Kwargs",
description="The search kwargs to use",
)
)
chain = (
{"context": configurable_retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
chain을 실행 할 때, 키워드 등록
chain.invoke(
"What city did I visit last?",
config={"configurable": {"search_kwargs": {"body_search": "new"}}},
)
5. 실제 프로젝트에서 필요한 사항 정리
- 서비스를 만드는 단계 : Vectorstore Retriever로 시작하거나 상황에 맞는 Retriever 선택
- Retriver 성능평가 : 정말 잘 문서를 검색하는지 평가
- 운영 단계 : 대규모 채팅 데이터와 벡터스토어를 분석해, 적절한 Retriever로 만들어 최적화
- 채팅 데이터 분석 : 어떠한 주제가 많이 나오는지 분석
- 벡터스토어 분석 : 수집되는 데이터가 가변적인 경우, 처음 개발시의 가정과 달라졌는지 확인
'자연어처리 > Langchain' 카테고리의 다른 글
[Langchain] RAG 성능 평가 with Regas (0) | 2025.03.07 |
---|---|
[Langchain] 임베딩(Embedding)과 유사도 검색 방법 for Retriever (0) | 2025.03.06 |
[langchain] 채팅 히스토리와 메모리 (History, Memory) (2) | 2025.02.05 |
[langchain] Message 다루기 (9) | 2024.10.04 |
[langchain] LangChain Expression Language(LCEL) (1) | 2024.09.30 |