ML & DL
Langchain으로 LLM에 RAG(검색증강생성)을 적용해 보기
안모
2024. 2. 18. 14:16
참조 포스팅은 Langchain + Ollama + Chainlit을 이용하여 LLM 챗봇을 구성하는 것을 다루고 있다.
나도 한 번 따라서 만들어 봤는데, 구현과 에러 핸들링을 정리해 보았다. 자세한 건 포스팅 참고.
알아두면 좋은것
- Langchain: 대규모 언어모델(LLM)을 이용한 애플리케이션을 개발하기 위한 프레임워크
- Ollama: LLM을 실행, 생성 및 공유하는 오픈소스 프로젝트
- RAG: LLM에 외부 지식소스를 제공하여 이를 바탕으로 답변을 생성하는 방법
- Chainlit: 대화형 AI를 제공하기 위한 Python 패키지
필요한 pdf파일은 그냥 검색해서 나온 요리책 pdf를 이용했다.
load_data_vdb.py
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain.document_loaders.pdf import PyPDFDirectoryLoader
from langchain_community.document_loaders import UnstructuredHTMLLoader, BSHTMLLoader
from langchain_community.vectorstores.chroma import Chroma
from langchain_community.embeddings import GPT4AllEmbeddings
from langchain_community.embeddings import OllamaEmbeddings
import os
DATA_PATH="data/"
DB_PATH = "vectorstores/db/"
def create_vector_db():
loader = PyPDFDirectoryLoader(DATA_PATH)
documents = loader.load()
print(f"Processed {len(documents)} pdf files")
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=50)
texts=text_splitter.split_documents(documents)
print(texts)
vectorstore = Chroma.from_documents(documents=texts, embedding=GPT4AllEmbeddings(),persist_directory=DB_PATH)
vectorstore.persist()
if __name__=="__main__":
create_vector_db()
- load_data_vdb.py는 PDF파일을 임베딩하여 벡터DB에 저장한다. 해당 프로젝트의 data 디렉토리에 PDF파일을 저장하고 코드를 실행하면 vectorstores/db 디렉토리에 PDF파일에 대한 임베딩 정보가 저장된다.
- langchain_community: langchain을 import하는 방식이 달라졌다. 그냥 langchain을 설치해서 import하면 에러가 발생하니 변경해줘야 한다.
- Chroma: CromaDB를 이용할 때 vectorstores.chroma 처럼 vectorstores에 croma를 명시해줘야 Croma 클래스가 제대로 불러와진다.
RAG.py
from langchain import hub
from langchain_community.embeddings import GPT4AllEmbeddings
from langchain_community.vectorstores.chroma import Chroma
from langchain_community.llms.ollama import Ollama
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
import chainlit as cl
from langchain.chains import RetrievalQA,RetrievalQAWithSourcesChain
QA_CHAIN_PROMPT = hub.pull("rlm/rag-prompt-mistral")
def load_llm():
llm = Ollama(
model="dolphin2.2-mistral"
verbose=True,
callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),
)
return llm
def retrieval_qa_chain(llm,vectorstore):
qa_chain = RetrievalQA.from_chain_type(
llm,
retriever=vectorstore.as_retriever(),
chain_type_kwargs={"prompt": QA_CHAIN_PROMPT},
return_source_documents=True,
)
return qa_chain
def qa_bot():
llm=load_llm()
DB_PATH = "vectorstores/db/"
vectorstore = Chroma(persist_directory=DB_PATH, embedding_function=GPT4AllEmbeddings())
qa = retrieval_qa_chain(llm,vectorstore)
return qa
@cl.on_chat_start
async def start():
chain=qa_bot()
msg=cl.Message(content="Firing up the research info bot...")
await msg.send()
msg.content= "Hi, welcome to research info bot. What is your query?"
await msg.update()
cl.user_session.set("chain",chain)
@cl.on_message
async def main(message):
chain=cl.user_session.get("chain")
cb = cl.AsyncLangchainCallbackHandler(
stream_final_answer=True,
answer_prefix_tokens=["FINAL", "ANSWER"]
)
cb.answer_reached=True
res=await chain.ainvoke(message.content, callbacks=[cb])
print(f"response: {res}")
answer=res["result"]
answer=answer.replace(".",".\n")
sources=res["source_documents"]
if sources:
answer+=f"\nSources: "+str(str(sources))
else:
answer+=f"\nNo Sources found"
await cl.Message(content=answer).send()
- RAG.py는 Ollama를 통해 서빙되는 LLM에 QA유닛으로 벡터DB에 저장된 임베딩 정보를 제공하며, 그 결과와 소스 참조 정보를 Chainlit을 통해 챗봇형태로 제공하는 방식이다.
- Ollama: llms.ollama를 명시해줘야 Ollama 클래스가 제대로 불러와진다.
- 참조 포스팅은 Ollama에 mistral모델을 이용했는데, 나는 더 훈련되고 조정된 모델을 기대하여 dolphin2.2-mistral을 이용했다.


제대로 동작한다. 어떤 치즈가 샐러드에 어울리냐는 질문을 하자 챗봇은 요리책 PDF내용을 사용해서 답변해준다. 동일한 질문도 완전히 같은 답변이 아니라 비슷한 맥락의 답변을 해주고 있고, 답변을 생성한 소스는 무엇인지도 알 수 있다.
그러면 제공된 데이터 소스에 없는 답변은 어떻게 대답할까?

일단 컨텍스트에 없다고 알려준다. 만약 하나를 정해야 한다면 호불호가 강한 블루치즈는 어울리지 않을것 같다고 답변한다. 이 답변이 데이터 소스를 이용해서 추론한 것인지 아니면 모델 생성 당시에 학습된 내용인지는 모르겠지만 의도대로 동작하는 것 같다.
한국어로 파인튜닝된 모델도 비슷한 퀄리티가 나올까? 언제 한 번 시도 해봐야겠다.