
Retrieval-Augmented Generation(RAG) 개념부터
RAG 관련 파이썬 모듈을 제공하는 LangChain 프레임워크 파이프라인까지 살펴보도록 하겠습니다.
What is RAG?
RAG는 Retrieval-Augmented Generation의 약자로 LLM에 외부 지식소스를 연결해서 LLM의 답변 생성에 추가적인 정보를 사용하는 개념입니다.
Background
그렇다면 RAG는 왜 사용하는 것일까요? LLM의 훈련데이터에는 아주 최신의 데이터나 개인이 가지고 있는 자료 그리고 domain-specific한 정보는 거의 포함되어 있지 않습니다. 포함된다고 할지라도 포함된 양이 적어 사용자의 입력 프롬프트에 대해서 확률이 가장 높은 텍스트를 반환하는 LLM 특성상 이러한 프롬프트에 대한 답변 생성은 부정확하거나 적합하지 않을 가능성이 높습니다.
따라서 이러한 문제점을 극복하기 위해서 기존에 LLM이 가지고 있는 general한 지식과 추가적인 context(e.g. 최신정보, 개인정보, domain-specific knowledge 등)을 결합하는 것에 대한 필요성이 대두되었습니다. 추가적인 context를 기존의 LLM의 지식과 결합시키기 위해서 가장 많이 사용되는 방식은 fine-tuning 방식입니다. fine-tuning 방식은 효과적이었지만 컴퓨팅 리소스나 여러 테크닉들이 많이 요구된다는 한계가 있습니다.
이에 2020년에 new context를 LLM에 결합시키는 방법론으로 RAG가 등장했습니다. Paper의 원제목은 Retrieval-Augmented Generation for Knowledge-Intensive NLP Task 입니다.
RAG Concept
RAG의 핵심개념은 생성형 모델에 Retriever 모듈을 덧붙인것 입니다. 이는 마치 오픈북 시험에 비유할 수 있는데요. 우리가 오픈북 시험을 볼 때는 해당 내용을 "기억"하는 것보다는 해당 내용을 바탕으로 "추론" 할 수 있는 능력을 평가하게 됩니다. 마찬가지로 RAG는 LLM의 학습을 통해 가중치에 저장된 지식(parametric knowledge)보다는 외부 데이터 소스가 제공되었을 때 그로부터 사용자 프롬프트를 완성하는 추론(reasoning) 능력 즉, non-parametric knowledge에 초점을 맞추는 것이 핵심입니다.

RAG의 워크플로우는 다음의 세 단계로 구성되어 있습니다.
1. Retrieve
2. Augment
3. Generation
1. Retrieve
사용자가 모델에 쿼리를 날리면 이 쿼리를 기반으로 기존 LLM 지식 source가 아닌 외부 지식소스에서 관련된 context에 대한 검색을 수행합니다. 벡터 db(database)에 외부 지식소스들이 임베딩 되어있는 상태이고 사용자의 쿼리가 해당 벡터 공간에 임베딩되어서 유사성 검색을 수행하고 상위 k개의 데이터 객체를 반환하게 됩니다.
2. Augment
1.에서 사용자 쿼리에 대해서 검색된 k개의 context를 프롬프트 템플릿에 추가합니다.
3. Generation
2.에서 만들어진 프롬프트를 LLM에 전달해서 텍스트를 생성합니다.
RAG Implementation using LangChain
LangChain 프레임워크에서 RAG의 파이썬 구현을 위한 파이프라인 모듈을 제공하고 있습니다.
Get started | 🦜️🔗 Langchain
Get started with LangChain
python.langchain.com
LangChain에서 제공하는 RAG 관련 모듈은 다음과 같습니다.
1. Document Loaders
2. Text Splitters
3. Text Embedding Models
4. Vector Stores
5. Retrievers
이외에도 LangChain에서는 위에서 언급한 RAG관련 모듈뿐만 아니라 멀티턴과 관련된 메모리, 콜백 기능, 다양한 Agent, Chain 모듈들도 제공하고 있습니다. 더 많은 기능과 자세한 사용법은 위의 링크를 참고하세요. 본 게시글에서는 위에서 언급한 5개의 모듈에 대해서 다뤄보도록 하겠습니다:)
1. Document Loaders
첫번째는 Document Loaders입니다. LangChain에서는 다양한 소스로부터 문서를 로드해올 수 있는 Document Loader 모듈을 제공하고 있습니다. HTML, PDF, code 등 100개 이상의 다양한 문서 로더를 제공합니다. 우리는 이 로더를 이용해서 LLM에 추가하고 싶은 context 내용이 담긴 문서들을 로드해올 수 있습니다.
Document loaders | 🦜️🔗 Langchain
Head to Integrations for documentation on built-in document loader integrations with 3rd-party tools.
python.langchain.com
2. Text Splitting
두번째는 Text Splitting 모듈입니다. Document Loader를 이용해서 문서를 로드한 원본 문서들은 너무 길고 분량이 많기 떄문에 그대로 벡터 DB에 임베딩하게 되면 사용자 쿼리와 유사한 context로 검색이 되어 이 결과가 프롬프트에 들어갈 때 LLM Context Window 제한이 걸릴 수 있습니다. 또한 내용이 너무 많게 되면 사용자 쿼리와 유사도의 검색에서 효율성이 떨어지는 문제도 있습니다.
따라서 효율적인 검색을 위해서 로드한 문서를 더 작은 청크로 분할하는 역할을 해주는 것이 Text Splitter 입니다. LangChain에서는 특정한 문서유형(Code, Markdown .. etc.)에 최적화된 여러가지 변환알고리즘들을 제공하고 있습니다.
Text Splitters | 🦜️🔗 Langchain
Once you've loaded documents, you'll often want to transform them to better suit your application. The simplest example
python.langchain.com
3. Text Embedding Models
세번째는 문서에 대한 임베딩을 생성하는 Text Embedding Models 모듈입니다. 청크로 분할된 문서들을 임베딩하기 위해서 오픈소스부터 독점 API까지 25개 이상의 다양한 임베딩 provider와 방법 그리고 integration을 제공합니다.
Text embedding models | 🦜️🔗 Langchain
Head to Integrations for documentation on built-in integrations with text embedding model providers.
python.langchain.com
4. Vectore Stores
네번째는 청크로 분할된 문서데이터 임베딩을 효율적으로 저장하고 검색하기 위한 Vector Stores입니다. 오픈 소스 로컬 저장소부터 클라우드 호스팅 독점 저장소까지 50개 이상의 다양한 벡터 저장소와의 통합을 제공하고 있습니다.
Vector stores | 🦜️🔗 Langchain
Head to Integrations for documentation on built-in integrations with 3rd-party vector stores.
python.langchain.com
5. Retrievers
다섯번째는 데이터베이스 안에 있는 데이터에서 원하는 데이터를 검색할 수 있는 Retrivers입니다. 가장 간단한 의미검색(semantic search)기능부터 검색성능을 향상시킬 수 있는 다양한 검색 알고리즘을 지원하고 있습니다.
Retrievers | 🦜️🔗 Langchain
A retriever is an interface that returns documents given an unstructured query. It is more general than a vector store.
python.langchain.com
RAG의 워크플로우와 LangChain에서 제공하는 모듈들을 연결시켜서 보면
1. 사용자 문서 혹은 최신 데이터, domain-specific 데이터 등 기존 LLM이 가지고 있지 않은 new context 와 관련된 데이터를 Document Loader로 로드 후에
2. Text Splitter로 작은 단위의 청크로 분할하여 Vector Stores에 Text Embedding Models을 이용해서 임베딩을 진행합니다.
3. 그 후 사용자의 쿼리가 들어오면 해당 쿼리를 데이터를 임베딩할 때 사용했던 Text Embedding Models을 이용해서 임베딩하여
4. Retriever 모듈을 통해 유사한 Context를 찾아 프롬프트에 통합하여 LLM에 전달하는 것으로 이해할 수 있습니다.
OpenAI의 Text Embedding 모델과 LLM, Weaviate Vector DB를 이용한 간단한 RAG 워크플로우를 LangChain 모듈을 통해 구현해보겠습니다.
1. 먼저 필요한 라이브러리 및 패키지를 설치해줍니다.
!pip install langchain openai weaviate-client tiktoken
2. OpenAI의 모델을 사용하기 위해서는 OpenAI API Key가 필요합니다.
OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>
3. Document Loader를 이용해서 RAG에 사용할 문서를 로드합니다.
### 사용자 데이터 준비
### url에서 데이터를 .txt 파일로 다운로드 받은 후에
### LangChain의 DocumentLoader를 이용해서 문서를 로드한다.
import requests
from langchain.document_loaders import TextLoader
url = "https://raw.githubusercontent.com/langchain-ai/langchain/master/docs/docs/modules/state_of_the_union.txt"
res = requests.get(url)
with open("state_of_the_union.txt", "w") as f:
f.write(res.text)
loader = TextLoader('./state_of_the_union.txt')
documents = loader.load()
4. Text Splitter를 이용해서 로드한 문서를 청크단위로 분할합니다.
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)
5. 분할한 청크를 Vector Stores에 임베딩하여 저장합니다.
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Weaviate
import weaviate
from weaviate.embedded import EmbeddedOptions
client = weaviate.Client(
embedded_options = EmbeddedOptions()
)
vectorstore = Weaviate.from_documents(
client = client,
documents = chunks,
embedding = OpenAIEmbeddings(openai_api_key = OPENAI_API_KEY ),
by_text = False
6. 사용자의 쿼리와 유사한 데이터 객체를 검색하고 반환할 Retriever 모듈을 정의합니다.
retriever = vectorstore.as_retriever()
7. 사용자의 질의와 그에 따른 유사한 Context를 위한 프롬프트 템플릿을 정의합니다.
from langchain.prompts import ChatPromptTemplate
template = """You are an assistant for question-answering tasks.
Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know.
Use three sentences maximum and keep the answer concise.
Question: {question}
Context: {context}
Answer:
"""
prompt = ChatPromptTemplate.from_template(template)
print(prompt)
8. LLM 모델에 쿼리와 Context를 포함한 프롬프트를 전달하여 답변을 생성합니다.
from langchain.chat_models import ChatOpenAI
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0, openai_api_key = OPENAI_API_KEY)
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
query = "What did the president say about Justice Breyer"
rag_chain.invoke(query)
이번 게시글에서는 RAG의 개념부터 LangChain에서 제공하는 RAG 모듈들을 이용해서 파이썬으로 RAG의 워크플로우를 간단하게 구현해보았습니다. 감사합니다.
References
Retrieval-Augmented Generation (RAG): From Theory to LangChain Implementation
From the theory of the original academic paper to its Python implementation with OpenAI, Weaviate, and LangChain
towardsdatascience.com
해당 게시글은 위의 게시글을 한국어로 번역 및 LangChain 공식 document 기반으로 재구성한 글임을 밝힙니다.