랭체인 Retriever
Retriever
LangChain에서 외부 데이터베이스(벡터DB 등)에서 관련 정보를 검색(Retrieve)하는 역할을 하는 모듈.
🔹주요 역할
- 사용자의 질문(Query)에 맞는 문서 검색
- RAG(Retrieval-Augmented Generation) 시스템에서 컨텍스트 제공
- LLM이 더 정확한 답변을 생성하도록 도움
🔹종류
종류는 크게 Sparse Retriever와 Dense Retriever 가 있다.
구분 | Sparse Retriever | Dense Retriever |
기반 기술 | 희소 벡터, TF-IDF, BM25 | 밀집 벡터, 딥러닝 (예: BERT) |
주요 특징 | 키워드 중심, 해석 가능 | 문맥적 의미 반영, 의미적 유사성 |
장점 | 구현 간단, 빠른 검색, 해석 용이 | 문맥 인식, 유연성, 의미적 맥락 반영, 이음동의어 가능 |
단점 | 문맥 무시, 키워드 제한,이음동의어 불가 | 계산 비용 높음, 해석 어려움 |
적합한 사용 케이스 | 간단하고 구체적인 질의 | 문맥을 중시하는 복잡한 질의 |
📌 즉, Retriever는 LLM이 필요할 때 정보를 찾아주는 검색 엔진 역할! 🚀
🔸주로 사용하는 것
Multi-Query | 대충 질문해도 좋은 답변 원할 때 |
Parent Document | 앞뒤 문맥 잘 담아야 할 때 |
Self Query | 시맨틱 검색 말고 쿼리가 필요할 때 |
Time-Weighted | 오래된 자료를 덜 참고하면 좋을 때 |
Ensemble Retriever | 검색의 정확도 높일 때 |
Long Context Reorder | 참고 문헌들이 많을 때 |
실습
실습 코드 : Retriever.ipynb
1. RetrievalQA
- tiktoken 설정
import tiktoken
tokenizer = tiktoken.get_encoding("cl100k_base")
def tiktoken_len(text):
tokens = tokenizer.encode(text)
return len(tokens)
- 라이브러리 import
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.document_loaders import PyPDFLoader
- 임베딩 모델
loader = PyPDFLoader("/content/drive/MyDrive/NLP톺아보기/file/[이슈리포트 2022-2호] 혁신성장 정책금융 동향.pdf")
pages = loader.load_and_split()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50, length_function = tiktoken_len)
texts = text_splitter.split_documents(pages)
from langchain.embeddings import HuggingFaceEmbeddings
model_name = "jhgan/ko-sbert-nli"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
hf = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
docssearch = Chroma.from_documents(texts, hf)
- MMR 검색 방식 활용
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
openai = ChatOpenAI(model_name="gpt-3.5-turbo",
streaming=True, callbacks=[StreamingStdOutCallbackHandler()],
temperature = 0)
qa = RetrievalQA.from_chain_type(llm = openai,
chain_type = "stuff",
retriever = docssearch.as_retriever(
search_type="mmr",
search_kwargs={'k':3, 'fetch_k': 10}),
return_source_documents = True)
query = "혁신성장 정책금융에 대해서 설명해줘"
result = qa(query)
혁신성장 정책금융은 기업 성장을 지원하고 건강한 혁신산업 생태계를 조성하기 위해 정부나 정책금융기관이 제공하는 금융 지원 제도를 말합니다. 이를 통해 혁신성장을 이루고 있는 기업들이 자금을 지원받아 더욱 발전하고 성장할 수 있도록 돕습니다. 이러한 정책금융은 혁신성장을 위한 정책금융 가이드라인에 따라 운영되며, 특정 테마와 분야, 품목을 중심으로 지원이 이루어집니다. 최근에는 ICT 산업을 중심으로 혁신성장 정책금융이 활발히 이루어지고 있습니다.
2. Retriever의 기본형, 벡터DB 기반 Retriever
- Chroma 벡터 DB 기반 기본 유사 문서 검색
from langchain.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
import os
import openai
from google.colab import userdata
Chroma().delete_collection()
openai.api_key = userdata.get('OPENAI_API_KEY')
#헌법 PDF 파일 로드
loader = PyPDFLoader(r"/content/drive/MyDrive/NLP톺아보기/file/대한민국헌법(헌법)(제00010호)(19880225).pdf")
pages = loader.load_and_split()
#PDF 파일을 500자 청크로 분할
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
docs = text_splitter.split_documents(pages)
#ChromaDB에 청크들을 벡터 임베딩으로 저장(OpenAI 임베딩 모델 활용)
db = Chroma.from_documents(docs, OpenAIEmbeddings(model = 'text-embedding-3-small'))
#Chroma를 Retriever로 활용
retriever = db.as_retriever()
retriever.invoke("국회의원의 의무")
- 유사도 점수 함께 출력
result_score = db.similarity_search_with_score("국회의원의 의무")
result_r_score = db.similarity_search_with_relevance_scores("국회의원의 의무")
print("[유사 청크 1순위]")
print(result_score[0][0].page_content)
print("\n\n[점수]")
print(result_score[0][1])
print(result_r_score[0][1])
- 유사 청크 1개만 반환
retriever = db.as_retriever(search_kwargs={"k": 1})
retriever.invoke("국회의원의 의무")
- MMR 검색 방식
from langchain.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
#헌법 PDF 파일 로드
loader = PyPDFLoader(r"/content/drive/MyDrive/NLP톺아보기/file/대한민국헌법(헌법)(제00010호)(19880225).pdf")
pages = loader.load_and_split()
#PDF 파일을 500자 청크로 분할
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
docs = text_splitter.split_documents(pages)
#ChromaDB에 청크들을 벡터 임베딩으로 저장(OpenAI 임베딩 모델 활용)
db = Chroma.from_documents(docs, OpenAIEmbeddings(model = 'text-embedding-3-small'))
#Chroma를 Retriever로 활용
retriever = db.as_retriever(
search_type="mmr",
search_kwargs = {"lambda_mult": 0, "fetch_k":10, "k":3}
)
retriever.invoke("국회의원의 의무")
- 일반 유사도 검색
retriever = db.as_retriever(search_kwargs = {"k":3})
retriever.get_relevant_documents("국회의원의 의무")
3. MultiQueryRetriever
- 사용자의 쿼리를 재해석하여 검색
- Chroma DB에 문서 벡터 저장
from langchain.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
#헌법 PDF 파일 로드
loader = PyPDFLoader(r"/content/drive/MyDrive/NLP톺아보기/file/대한민국헌법(헌법)(제00010호)(19880225).pdf")
pages = loader.load_and_split()
#PDF 파일을 500자 청크로 분할
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
docs = text_splitter.split_documents(pages)
#ChromaDB에 청크들을 벡터 임베딩으로 저장(OpenAI 임베딩 모델 활용)
db = Chroma.from_documents(docs, OpenAIEmbeddings(model = 'text-embedding-3-small'))
- 질문을 여러 버전으로 재해석
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI
#질문 문장 question으로 저장
question = "국회의원의 의무는 무엇이 있나요?"
#여러 버전의 질문으로 변환하는 역할을 맡을 LLM 선언
llm = ChatOpenAI(model_name="gpt-4o-mini",
temperature = 0)
#MultiQueryRetriever에 벡터DB 기반 Retriever와 LLM 선언
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=db.as_retriever(), llm=llm
)
# 여러 버전의 문장 생성 결과를 확인하기 위한 로깅 과정
import logging
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
#여러 버전 질문 생성 결과와 유사 청크 검색 개수 출력
unique_docs = retriever_from_llm.invoke(input=question)
len(unique_docs)
4. MultiVectorRetriever
- 문서를 여러 벡터로 재해석
- Chroma DB에 문서 벡터 저장
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryByteStore
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.document_loaders import PyPDFLoader
loaders = PyPDFLoader(r"/content/drive/MyDrive/NLP톺아보기/file/대한민국헌법(헌법)(제00010호)(19880225).pdf"),
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
docs = text_splitter.split_documents(docs)
- Multi Vector 생성
- InMemoryByteStore() : 상위 문서 저장을 위한 레이어
- 상위 문서와 하위 문서를 연결할 키값으로 doc_id 사용
- uuid : 문서 id로 고유한 값을 지정
from langchain.embeddings import HuggingFaceEmbeddings
model_name = "jhgan/ko-sbert-nli"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
embedding = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
vectorstore = Chroma(
collection_name="full_documents", embedding_function=embedding
)
store = InMemoryByteStore()
id_key = "doc_id"
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
byte_store=store,
id_key=id_key,
)
import uuid
doc_ids = [str(uuid.uuid4()) for _ in docs]
- child_text_splitter : 하위 청크로 쪼개기 위함
- 상위 청크들을 순회하며 하위 청크로 분할한 후 상위 청크 id 상속
- vectorstore.add_documents(sub_docs) : vectorstore에 하위 청크 추가
- docstore에 상위청크 저장할 때, doc_ids 지정
child_text_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
sub_docs = []
for i, doc in enumerate(docs):
_id = doc_ids[i]
_sub_docs = child_text_splitter.split_documents([doc])
for _doc in _sub_docs:
_doc.metadata[id_key] = _id
sub_docs.extend(_sub_docs)
retriever.vectorstore.add_documents(sub_docs)
retriever.docstore.mset(list(zip(doc_ids, docs)))
- 하위 청크 및 상위 청크 출력
print("[하위 청크] \n")
print(retriever.vectorstore.similarity_search("국민의 권리")[0].page_content)
print("-"*50)
print("[상위 청크] \n")
print(retriever.invoke("국민의 권리")[0].page_content)
[하위 청크]
③공공필요에 의한 재산권의 수용ㆍ사용 또는 제한 및 그에 대한 보상은 법률로써 하되, 정당한 보상을 지급하여야
한다.
제24조 모든 국민은 법률이 정하는 바에 의하여 선거권을 가진다.
제25조 모든 국민은 법률이 정하는 바에 의하여 공무담임권을 가진다.
제26조 ①모든 국민은 법률이 정하는 바에 의하여 국가기관에 문서로 청원할 권리를 가진다.
②국가는 청원에 대하여 심사할 의무를 진다.
제27조 ①모든 국민은 헌법과 법률이 정한 법관에 의하여 법률에 의한 재판을 받을 권리를 가진다.
②군인 또는 군무원이 아닌 국민은 대한민국의 영역 안에서는 중대한 군사상 기밀ㆍ초병ㆍ초소ㆍ유독음식물공급
ㆍ포로ㆍ군용물에 관한 죄중 법률이 정한 경우와 비상계엄이 선포된 경우를 제외하고는 군사법원의 재판을 받지
--------------------------------------------------
[상위 청크]
법제처 3 국가법령정보센터
대한민국헌법
제21조 ①모든 국민은 언론ㆍ출판의 자유와 집회ㆍ결사의 자유를 가진다.
②언론ㆍ출판에 대한 허가나 검열과 집회ㆍ결사에 대한 허가는 인정되지 아니한다.
③통신ㆍ방송의 시설기준과 신문의 기능을 보장하기 위하여 필요한 사항은 법률로 정한다.
④언론ㆍ출판은 타인의 명예나 권리 또는 공중도덕이나 사회윤리를 침해하여서는 아니된다. 언론ㆍ출판이 타인의
명예나 권리를 침해한 때에는 피해자는 이에 대한 피해의 배상을 청구할 수 있다.
제22조 ①모든 국민은 학문과 예술의 자유를 가진다.
②저작자ㆍ발명가ㆍ과학기술자와 예술가의 권리는 법률로써 보호한다.
제23조 ①모든 국민의 재산권은 보장된다. 그 내용과 한계는 법률로 정한다.
②재산권의 행사는 공공복리에 적합하도록 하여야 한다.
③공공필요에 의한 재산권의 수용ㆍ사용 또는 제한 및 그에 대한 보상은 법률로써 하되, 정당한 보상을 지급하여야
한다.
제24조 모든 국민은 법률이 정하는 바에 의하여 선거권을 가진다.
제25조 모든 국민은 법률이 정하는 바에 의하여 공무담임권을 가진다.
제26조 ①모든 국민은 법률이 정하는 바에 의하여 국가기관에 문서로 청원할 권리를 가진다.
②국가는 청원에 대하여 심사할 의무를 진다.
제27조 ①모든 국민은 헌법과 법률이 정한 법관에 의하여 법률에 의한 재판을 받을 권리를 가진다.
②군인 또는 군무원이 아닌 국민은 대한민국의 영역 안에서는 중대한 군사상 기밀ㆍ초병ㆍ초소ㆍ유독음식물공급
ㆍ포로ㆍ군용물에 관한 죄중 법률이 정한 경우와 비상계엄이 선포된 경우를 제외하고는 군사법원의 재판을 받지
아니한다.
③모든 국민은 신속한 재판을 받을 권리를 가진다. 형사피고인은 상당한 이유가 없는 한 지체없이 공개재판을 받을
권리를 가진다.
④형사피고인은 유죄의 판결이 확정될 때까지는 무죄로 추정된다.
⑤형사피해자는 법률이 정하는 바에 의하여 당해 사건의 재판절차에서 진술할 수 있다.
제28조 형사피의자 또는 형사피고인으로서 구금되었던 자가 법률이 정하는 불기소처분을 받거나 무죄판결을 받은 때
에는 법률이 정하는 바에 의하여 국가에 정당한 보상을 청구할 수 있다.
제29조 ①공무원의 직무상 불법행위로 손해를 받은 국민은 법률이 정하는 바에 의하여 국가 또는 공공단체에 정당한
배상을 청구할 수 있다. 이 경우 공무원 자신의 책임은 면제되지 아니한다.
②군인ㆍ군무원ㆍ경찰공무원 기타 법률이 정하는 자가 전투ㆍ훈련등 직무집행과 관련하여 받은 손해에 대하여는
법률이 정하는 보상 외에 국가 또는 공공단체에 공무원의 직무상 불법행위로 인한 배상은 청구할 수 없다.
제30조 타인의 범죄행위로 인하여 생명ㆍ신체에 대한 피해를 받은 국민은 법률이 정하는 바에 의하여 국가로부터 구조
를 받을 수 있다.
제31조 ①모든 국민은 능력에 따라 균등하게 교육을 받을 권리를 가진다.
②모든 국민은 그 보호하는 자녀에게 적어도 초등교육과 법률이 정하는 교육을 받게 할 의무를 진다.
③의무교육은 무상으로 한다.
④교육의 자주성ㆍ전문성ㆍ정치적 중립성 및 대학의 자율성은 법률이 정하는 바에 의하여 보장된다.
⑤국가는 평생교육을 진흥하여야 한다.
⑥학교교육 및 평생교육을 포함한 교육제도와 그 운영, 교육재정 및 교원의 지위에 관한 기본적인 사항은 법률로 정
한다.
5. Long-Context Reorder
- 컨텍스트 재정렬
- Long-Context Reorder 없이 유사 문서 출력
from langchain.chains import LLMChain, StuffDocumentsChain
from langchain_chroma import Chroma
from langchain.document_transformers import (
LongContextReorder,
)
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.prompts import PromptTemplate
from langchain_openai import OpenAI
Chroma().delete_collection()
# 한글 임베딩 모델 선언
model_name = "jhgan/ko-sbert-nli"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
embedding = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
texts = [
"바스켓볼은 훌륭한 스포츠입니다.",
"플라이 미 투 더 문은 제가 가장 좋아하는 노래 중 하나입니다.",
"셀틱스는 제가 가장 좋아하는 팀입니다.",
"이것은 보스턴 셀틱스에 관한 문서입니다."
"저는 단순히 영화 보러 가는 것을 좋아합니다",
"보스턴 셀틱스가 20점차로 이겼어요",
"이것은 그냥 임의의 텍스트입니다.",
"엘든 링은 지난 15 년 동안 최고의 게임 중 하나입니다.",
"L. 코넷은 최고의 셀틱스 선수 중 한 명입니다.",
"래리 버드는 상징적인 NBA 선수였습니다.",
]
# Chroma Retriever 선언(10개의 유사 문서 출력)
retriever = Chroma.from_texts(texts, embedding=embedding).as_retriever(
search_kwargs={"k": 10}
)
query = "셀틱에 대해 설명해줘"
# 유사도 기준으로 검색 결과 출력
docs = retriever.invoke(query)
docs
- Long-Context Reorder 활용하여 유사 문서 출력
- 검색된 유사문서 중 관련도가 높은 문서를 맨 앞과 맨뒤에 재정배치
reordering = LongContextReorder()
reordered_docs = reordering.transform_documents(docs)
reordered_docs