
최신 오픈소스로 릴리즈된 Llama3.1의 8B 모델을 사용해서 로컬로만 동작하는 RAG(Retrieval-Augmented Generation) 시스템을 구현 할 수 있지 않을까 하는 궁금증이 생겼습니다. 그리고 한국어도 처리가 가능한지도 확인해 볼겸 간단한 RAG를 구현해 보겠습니다.
Llama 3.1은 Meta AI에서 개발한 대규모 언어 모델의 최신 버전입니다. 이전 버전들에 비해 성능이 크게 향상되었으며, 다양한 자연어 처리 작업에서 뛰어난 성능을 보여줍니다. 특히, 8B 파라미터 버전은 상대적으로 작은 모델 크기에도 불구하고 높은 품질의 텍스트 생성이 가능합니다.
Ollama는 다양한 언어 모델을 로컬 환경에서 쉽게 실행할 수 있게 해주는 도구입니다. Llama 3.1을 Ollama를 통해 설치하고 사용하는 방법은 다음과 같습니다.
Ollama 설치: Ollama 공식 사이트에서 설치 파일을 다운로드하여 실행합니다.

Llama 3.1 모델 다운로드: 터미널에서 다음 명령어를 실행합니다.
ollama pull llama3.1:8b
이 명령은 Llama 3.1의 8B 파라미터 버전을 다운로드합니다. 4.7G 라서 시간이 좀 걸릴 수 있습니다.

ollama run llama3.1:8b "Hello, how are you?"이제 Llama 3.1 모델이 로컬 환경에 설치되었으며, 우리의 RAG 시스템에서 사용할 준비가 되었습니다.
FAISS는 Facebook AI Research에서 개발한 라이브러리로, 대규모 벡터 집합에서 효율적인 유사성 검색과 클러스터링을 수행하는 데 사용됩니다. RAG 시스템에서 FAISS의 역할은 매우 중요합니다.
우리의 RAG 시스템에서 FAISS는 다음과 같은 역할을 수행합니다
FAISS를 사용하여 벡터 저장소를 생성하고 검색하는 방법을 살펴보겠습니다.
from langchain_community.vectorstores import FAISS
# 벡터 저장소 생성
vectorstore = FAISS.from_documents(chunked_documents, embeddings)
# 로컬에 저장
vectorstore.save_local("./vector_store_path")
# 로컬에서 로드
loaded_vectorstore = FAISS.load_local("./vector_store_path", embeddings)
# 검색 수행
retriever = loaded_vectorstore.as_retriever()
relevant_documents = retriever.get_relevant_documents(query)
이 코드에서 FAISS.from_documents()는 문서 청크와 임베딩 모델을 사용하여 FAISS 인덱스를 생성합니다. save_local()과 load_local()메서드를 통해 인덱스를 파일로 저장하고 불러올 수 있습니다. as_retriever()메서드는 FAISS 인덱스를 기반으로 검색을 수행할 수 있는 retriever 객체를 생성합니다.
FAISS를 사용함으로써, 우리의 RAG 시스템은 대량의 문서에서도 빠르고 정확한 검색을 수행할 수 있게 되어, 사용자 쿼리에 대해 관련성 높은 컨텍스트를 신속하게 제공할 수 있습니다.
이제 Llama 3.1과 HuggingFace 임베딩을 사용하여 로컬에서 동작하는 RAG 시스템을 구현해 보겠습니다.
FAISS를 사용하여 문서의 벡터 표현을 저장하고 검색합니다.intfloat/multilingual-e5-small 모델을 사용하여 다국어 지원이 가능한 임베딩을 생성합니다.ChatOllama를 통해 Llama 3.1의 8B 파라미터 버전을 사용합니다.이제 RAG 시스템의 각 구성 요소를 상세히 살펴보고 구현해 보겠습니다.
먼저, 필요한 라이브러리들을 설치합니다.
pip3 install langchain_community langchain_huggingface langchainhub pypdf faiss-cpu
PDF 문서를 로드하고, 이를 청크로 분할한 후 벡터 저장소를 생성합니다.
import os
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
VECTOR_STORE_PATH = "./vectorstore"
EMBEDDINGS = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-small")
def create_vectorstore():
list_of_pdfs = [
"pdfs/2024 노무관리 가이드 북.pdf",
]
text_splitter = CharacterTextSplitter(
separator="\n",
chunk_size=1000,
chunk_overlap=200,
length_function=len,
is_separator_regex=False,
)
documents = []
for pdf in list_of_pdfs:
loader = PyPDFLoader(pdf)
documents += loader.load()
chunked_documents = text_splitter.split_documents(documents)
vectorstore = FAISS.from_documents(chunked_documents, EMBEDDINGS)
vectorstore.save_local(VECTOR_STORE_PATH)
return vectorstore
이 코드는 PDF 문서를 로드하고, 텍스트를 1000자 길이의 청크로 분할한 후, FAISS를 사용하여 벡터 저장소를 생성합니다.
사용자의 질문과 검색된 문서 내용을 결합하여 LLM에 전달할 프롬프트를 정의합니다.
from langchain import hub
prompt = hub.pull("rlm/rag-prompt")
Ollama를 통해 설치한 Llama 3.1 모델을 초기화합니다.
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(model="llama3.1:8b")
Retriever, 프롬프트, LLM을 연결하여 RAG 체인을 구성합니다.
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
retriever = vectorstore.as_retriever()
rag_chain_from_docs = (
RunnablePassthrough.assign(context=(lambda x: format_docs(x["context"])))
| prompt
| llm
| StrOutputParser()
)
rag_chain_with_source = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
).assign(answer=rag_chain_from_docs)
이 체인은 다음과 같이 작동합니다:
이제 사용자의 질문에 대해 RAG 시스템을 실행하고 결과를 출력합니다.
def main():
query = "연차 계산 방법을 알려주세요."
response = rag_chain_with_source.invoke(query)
print("Answer:\n", response["answer"] + "\n")
print("Sources:")
sources = [doc.metadata for doc in response["context"]]
for source in sources:
print(source)
if __name__ == "__main__":
main()
아래와 같이 사용자의 질문에 대한 답변을 생성하고, 답변의 근거가 된 문서의 출처 정보도 함께 제공합니다. 한국어도 잘되네요.
Answer:
제공된 내용에 따르면, 신입사원의 유급휴가 부여 및 사용은 다음과 같이 이루어집니다.
* 입사 후 1년 미만(1년차)까지는 1개월 개근시 1일씩 유급휴가 발생(최대 11일)
* 최초 1년의 근로가 끝날 때까지 사용 가능
* 입사 후 1년간(1년차)의 출근율이 80% 이상인 경우, 2년차에는 총 15일의 유급휴가가 발생
제60조 제4항에 따른 연차유급휴가는 다음과 같이 규정됩니다.
* 제60조제4항의 연차휴가 사용 권리는 전년도 1년간 근로를 마친 다음 날 발생하며, 법 제60조제2항의 연차휴가 사용 권리도 1개월의 근로를 마친 다음 날 발생
* 정규직·계약직 모두 1년(365일) 근로 후 퇴직하면 법 제60조제1항의 15일 연차휴가 미사용수당을 청구할 수 없고, 다음 날인 366일째 근로관계 존속 후 퇴직하면 15일 연차휴가 전부에 대해 수당 청구 가능
* 법 제60조제2항의 연차휴가도 그 1개월 근로를 마친 다음 날 근로관계 존속 후 퇴직해야 퇴직 전월의 개근에 대한 연차 미사용 수당 청구 가능
또한, 대법원 판결 (2021 다 227100)에서 규정된 바와 같이 법 제60조 제4항의 연차휴가는 근로한 년수의 365일을 초과하여 근무하고 퇴직하는 경우, 80% 이상 출근율을 달성하더라도 유급휴가 미사용수당 청구 불가능합니다.
Sources:
{'source': 'pdfs/2024 노무관리 가이드 북.pdf', 'page': 96}
{'source': 'pdfs/2024 노무관리 가이드 북.pdf', 'page': 104}
{'source': 'pdfs/2024 노무관리 가이드 북.pdf', 'page': 100}
{'source': 'pdfs/2024 노무관리 가이드 북.pdf', 'page': 103}
모델들의 저장위치와 사이즈는
intfloat/multilingual-e5-small: 약 476MB의 크기로, ~/.cache/huggingface/hub 경로에 저장됩니다.~/.ollama/models 경로에 저장됩니다.그리고, M1 맥북 에어에서 위 질문에 대한 답변은 약 4~5s 정도 소요 되었습니다.
상대적으로 작은 규모의 Llama 3.1 8B 버전으로도 RAG 시스템은 가능하지 않을까 싶습니다.
전체 코드는 GitHub 레포지토리에서 확인할 수 있습니다. 링크
Yes Yes