[LLM] Langchain_RAG

JAsmine_log·2024년 6월 10일

Langchain

LangChain is a framework for developing applications powered by large language models (LLMs).

LLM(large langugage model)과 Application의 통합을 간소화 시키는 SDK이다. 어떠한 모델의 API를 사용하든 LLM 기반 애플리케이션을 구축하기 위해서는 다소 복잡한 작업이 필요하다. 여러 기능들을 구현하고 사용하는 것을 지원하기 위해 Langchain을 사용한다.

RAG(retrival-augmented generation)

Concept

RAG(Retrieval-Augmented Generation) 파이프라인은 기존 LLM 모델에 검색기능을 추가해 질문이나 쿼리에 대해 더 정확하고 전문적인 응답을 생성할 수 있게 한다. 이는 데이터 로드, 텍스트 분할, 인덱싱, 검색, 생성의 파이프라인으로 구성된다.

RAG pipeline
데이터 로드 → 텍스트 분할 → 인덱싱 → 검색 → 생성

RAG Pipeline

01 데이터 로드(Load Data)

RAG에 필요한 데이터를 불러온다. 웹문서, 텍스트문서, 디렉토리 폴더, csv, PDF 를 로드할 수 있다. 데이터는 외부데이터 소스에서 정보를 수집하고, 필요한 형식으로 변환하여 사용한다.

# Data Loader - 웹페이지 데이터 가져오기
from langchain_community.document_loaders import WebBaseLoader

# 위키피디아 정책과 지침
url = 'https://ko.wikipedia.org/wiki/%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EC%A0%95%EC%B1%85%EA%B3%BC_%EC%A7%80%EC%B9%A8'
loader = WebBaseLoader(url)

# 웹페이지 텍스트 -> Documents
docs = loader.load()

print(len(docs))
print(len(docs[0].page_content))
print(docs[0].page_content[5000:6000])

02 텍스트 분할(Text Split)

로드한 데이터는 NLP 기술을 통해서 큰 단위에서 작은 단위(chunk)로 분할하여 사용한다.문서는 문단으로, 문단은 문장, 문장은 구, 구는 단어 단위 등으로 나누어 효율적인 검색을돕는다.

# Text Split (Documents -> small chunks: Documents)
from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)

print(len(splits))
print(splits[10])

# page_content 속성
splits[10].page_content

# metadata 속성
splits[10].metadata

03 인덱싱(Indexing)

분할한 텍스트를 검색하기 위한 형태로 만든 것을 인덱싱이라고 한다. 인덱싱은 검색시간을 줄이고, 검색 정확도를 높이는역할을 한다. 인덱싱을 위해서 Langchain library는 임베딩 변환, 벡터 저장, 유사성 검색의 과정을 거친다.

# Indexing (Texts -> Embedding -> Store)
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

vectorstore = Chroma.from_documents(documents=splits,
                                    embedding=OpenAIEmbeddings())

docs = vectorstore.similarity_search("격하 과정에 대해서 설명해주세요.")
print(len(docs))
print(docs[0].page_content)

Embeding

Embeding은 텍스트를 숫자로 변환하여 벡터 공간에 표현하는 것이다. 벡터 표현은 텍스트 데이터를 테스트간유사성 게산 머신러닝, NLP를 수행할 수 있도록 수학적으로 다룰 수 있게 한다. 또한, Embeding 후에도 텍스트는 의미적인 저오를 보존하고 있어 근접한 텍스트들은 의미적으로 유사한 특성을 갖는다.

  • Task : 의미검색, 문서 분류, 텍스트 유사도 계산
  • Provider: OpenAI, HuggingFace, GoogleGenerativeAI

vector Store(벡터 저장소)

벡터 저장소는 벡터로 표현한 데이터(embeding)를 효율적으로 저장하고 검색할 수 있는 데이터베이스이다. 고차원의 벡터데이터는 대규모 벡터데이터셋에서 빠르게 유사성을 가진 내용을 찾도록 설계 및 관리된다.

  • 벡터 저장
    • 텍스트, 이미지, 오디오, 비디오 등 다양한 데이터를 벡터 공간에 매핑
    • 데이터는 의미적·시각적·오디오적·비디오적 특성으로 수치로 표현
    • 이러한 고차원 벡터 데이터는 효율적인 관리 및 최적화된 저장소와 구조가 필요함
    • Faiss(Facebook AI Similarity Search), Chroma, Elasticsearch, Pinecone 등 오픈소스 및 상용 솔루션을 저장소로 사용할 수 있음
  • 벡터 검색
    • 저장된 벡터에서 사용자 쿼리로 유사성 높은 벡터를 찾아내는 것
    • 코사인 유사도, 유클리드 거리, 맨해튼 거리 등 활용
  • 결과 반환
    • 유사성 높은 항목은 유사도 점수를 기반으로 정렬되어, 사용자에게 반환됨
    • 이 외에도, 검색 결과 관련성, 다양성, 신뢰도 등도 고려가능함

04 검색 (Retrieval)

사용자의 질문과 문맥(context)와 관련한 정보를 검색하여 찾아낸다. 사용자 입력은 쿼리를 생성하고, 데이터는 인덱싱된 것 중 관련성이 높은 정보를 찾아낸다. Retriever는 Vector Store Retriever, Multi Query Retriever,
Contextural Compression 가 있다. 아래에서는 대표적으로 벡터로 저장한 데이터를 검색해 보겠다.

Vector Store Retriever

우선, 인덱싱하여 데이터를 준비하고 정보를 검색하겠다.

  • 인덱싱(임베딩-벡터스토어)
# 벡터스토어에 문서 임베딩을 저장
from langchain_community.vectorstores import FAISS
from langchain_community.vectorstores.utils import DistanceStrategy
from langchain_community.embeddings import HuggingFaceEmbeddings

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  
                                  )
  • 단일 문서 검색
    • 유사도가 가장 높은 문서 1개 검색
# 검색 쿼리
query = '카카오뱅크의 환경목표와 세부추진내용을 알려줘'

# 가장 유사도가 높은 문장을 하나만 추출
retriever = vectorstore.as_retriever(search_kwargs={'k': 1})

docs = retriever.get_relevant_documents(query)
print(len(docs))
docs[0]
  • MMR(Maximal Marginal Relevance) 검색
    • 유사도 높은 문서 5개 검색
    • 유사도 다양성은 fetch_k(lambda_mult)로 설정하며 0.5보다 0.15가 더 다양성이 높음
# MMR 01 - 다양성 고려 (lambda_mult = 0.5)
retriever = vectorstore.as_retriever(
    search_type='mmr',
    search_kwargs={'k': 5, 'fetch_k': 50}
)

docs = retriever.get_relevant_documents(query)
print(len(docs))
docs[0]

# MMR 02- 다양성 고려 (lambda_mult 작을수록 더 다양하게 추출)
retriever = vectorstore.as_retriever(
    search_type='mmr',
    search_kwargs={'k': 5, 'lambda_mult': 0.15}
)

docs = retriever.get_relevant_documents(query)
print(len(docs))
docs[-1]
  • 유사도 점수 임계값 기반 검색
    • 유사도 점수가 score_threshold 이상인 것만 추출
# Similarity score threshold (기준 스코어 이상인 문서를 대상으로 추출)
retriever = vectorstore.as_retriever(
    search_type='similarity_score_threshold',
    search_kwargs={'score_threshold': 0.3}
)

docs = retriever.get_relevant_documents(query)
print(len(docs))
docs[0]
  • 메타데이터 필터링을 사용한 검색
    • 특정 필드를 기준으로 만족하는 경우에만 필터링하여 추출(검색)
# 문서 객체의 metadata를 이용한 필터링
retriever = vectorstore.as_retriever(
    search_kwargs={'filter': {'format':'PDF 1.4'}}
)

docs = retriever.get_relevant_documents(query)
print(len(docs))
docs[0]

05 생성(Generation)

응답은 검색되 정보를 기반으로 최종적으로 생성한다. 내부에서, LLM 모델의 검색결과와 사용자의 입력을 전달한다. 질문에 대한 답변은 모델의 사전 학습 지식과 검색결과를 결합하여 생성된다.


from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# Prompt
template = '''Answer the question based only on the following context:
{context}

Question: {question}
'''

prompt = ChatPromptTemplate.from_template(template)

# LLM
model = ChatOpenAI(model='gpt-3.5-turbo-0125', temperature=0)

# Rretriever
retriever = vectorstore.as_retriever()

# Combine Documents
def format_docs(docs):
    return '\n\n'.join(doc.page_content for doc in docs)

# RAG Chain 연결
rag_chain = (
    {'context': retriever | format_docs, 'question': RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

# Chain 실행
rag_chain.invoke("격하 과정에 대해서 설명해주세요.")

Reference
[1] https://wikidocs.net/231364
[2] https://wikidocs.net/233820

profile
Everyday Research & Development

0개의 댓글