RAG 챗봇 만들어보기

오늘은 간단한 RAG 챗봇을 만들어보도록 하겠습니다.

RAG 패키지 확인

먼저 각각의 패키지를 불러와야 하는데요. 여기서 사용하는것은 Langchain과, steamlit을 통해서 챗봇을 구현할 예정입니다.

langchain==0.3.1: 여러 자연어 처리(NLP) 작업을 위한 체인을 구성하고 관리하는 도구입니다.
langchain-openai==0.2.1: OpenAI API를 사용해 LangChain을 통해 GPT 모델과 같은 언어 모델을 쉽게 사용할 수 있도록 돕는 패키지입니다.
langchain-community==0.3.1: LangChain 커뮤니티에서 제공하는 추가 도구 및 기능을 포함한 패키지입니다.
streamlit: 웹 애플리케이션을 빠르게 개발할 수 있는 Python 기반 프레임워크입니다.
openai: OpenAI의 API와 상호작용하기 위한 패키지로, GPT, DALL-E 등의 모델을 사용할 수 있습니다.
tiktoken: OpenAI의 GPT 모델에서 사용하는 토큰화를 처리하는 패키지입니다.
chromadb: 고성능의 벡터 검색 데이터베이스를 제공하는 도구입니다.
langchain-core==0.3.7: LangChain의 핵심 기능들을 포함하고 있는 라이브러리입니다.

RAG 기본 프로그램 로직

기본 코드는 아래와 같습니다.

# 사용할 라이브러리 선언
import streamlit as st
import openai
import os
os.environ["OPENAI_API_KEY"] = "개인 OPENAI 키"
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from pprint import pprint
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
import json
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.schema import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
import logging

# 문제 진단: 프로그램이 복잡할수록 버그나 오류를 찾기 어려워지는데, 로그는 코드 실행 흐름을 기록하여 문제 발생 지점을 쉽게 찾아낼 수 있게 도와줍니다.
# 로그 설정: 기본 로그 설정을 초기화합니다.
# 특정 로거 레벨 설정: "langchain.retrievers.multi_query" 로거의 로그 레벨을 INFO로 설정하여 해당 모듈의 로그를 표시합니다.
# 파일에 로그 저장: 로그 메시지를 'info.log' 파일에 시간, 로거 이름, 로그 레벨, 메시지 형식으로 저장합니다. 'a' 모드는 기존 파일에 내용을 추가합니다.
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
logging.basicConfig(filename='info.log', level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', filemode='a')

# 사용할 챗봇 LLM 모델 설정해주기
# temperature는 0에 가까워질수록 전달해준 설명 및 질문 내에서만 답변을 내뱉고, 1에 가까워질수록 창의적인 답변을 내뱉음
chat = ChatOpenAI(model="gpt-4o-mini-2024-07-18", temperature=0.1)

# # 챗봇에게 질문하고 답변 출력
response = chat.invoke("오늘 날짜가 뭐야? ")
print(response.content)

이런 형태로 간단하게 RAG 챗봇을 만들 수 있습니다.

%%writefile app.py
# 사용할 라이브러리 선언
import streamlit as st
from dotenv import load_dotenv
load_dotenv()
import openai
import os
openai.api_key= os.environ.get("OPENAI_API_KEY")
os.environ["OPENAI_API_KEY"] = OPEN_API_Key"
import time
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import WebBaseLoader
os.environ["USER_AGENT"] = "TEST"
from langchain_text_splitters import RecursiveCharacterTextSplitter
from pprint import pprint
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_community.document_loaders import PDFPlumberLoader, PyMuPDFLoader
#import fitz
from langchain_core.messages import HumanMessage
from glob import glob
from langchain_core.prompts import ChatPromptTemplate
import base64
import json
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.schema import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel, RunnablePassthrough

chat = ChatOpenAI(model="gpt-4o-mini-2024-07-18", temperature=0.1)

embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vectordb = Chroma(persist_directory="./embedding_db/openai_large", embedding_function=embeddings)

retriever = MultiQueryRetriever.from_llm(
    retriever=vectordb.as_retriever(
        search_type="mmr",
        search_kwargs={'k': 5, 'fetch_k': 50}
    ), llm=chat
)

# 8) 데이터를 검색할 수 있는 리트리버 생성하기
# 리트리버는 쿼리에 맞는 문서를 검색하는 역할을 함
# 멀티쿼리 리트리버는 쿼리를 여러 가지 방식으로 변형하여 더 다양한 문서를 검색할 수 있도록 함
# MMR(Minimal Marginal Relevance)은 유사도뿐만 아니라 다양성을 고려하여 문서를 검색하는 방식
mmr_retriever = MultiQueryRetriever.from_llm(
    retriever=vectordb.as_retriever(
        search_type="mmr",
        search_kwargs={'k': 5, 'fetch_k': 50}
    ), llm=chat
)

# 9) LLM에게 역할을 주어주는 프롬프트 작성
# LLM에게 특정 역할을 부여하여 원하는 답변을 유도함
# 역할을 상세하게 적어줄수록 일관된 답변을 얻을 수 있음
template = """From now on, you are an expert who finds and provides information that fits the user's question in the provided context.

1. When you want information that is not in the context, answer that you do not have the information you want.
2. Never answer with information that is not in the context.
3. If you do not know the content, say that you do not know.
4. Make the explanation as concise as possible.
5. Please translate the answer into Korean.

context: {context}
user input: {input}
"""

prompt = ChatPromptTemplate.from_template(template)

# 역할 기반 프롬프트를 이용한 리트리버와 함께 체인 생성 및 병렬 실행 설정
retrieval = RunnableParallel({"context": mmr_retriever, "input": RunnablePassthrough()})

# 전체 체인을 연결하여 입력에 따라 정보를 검색하고 답변 생성
chain = retrieval | prompt | chat | StrOutputParser()

st.title("AI Chatbot")

if "openai_model" not in st.session_state:
    st.session_state["openai_model"] = "gpt-4o-mini-2024-07-18"

# 만약 이전 사용자 대화 내용 기록이 없다면 기록할 새로운 리스트 생성
if "messages" not in st.session_state:
    st.session_state.messages = []

# 리스트에 메세지 역할과 내용을 기록하도록 함
for message in st.session_state.messages:
  # 특정 블록의 시작과 끝에서 자동으로 설정 및 정리 작업을 처리하는 역할
  # st.chat_message() 블록 내에서 채팅 메시지를 생성하고, 그 안에서 st.markdown()으로 콘텐츠를 표시
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

MAX_MESSAGES_BEFORE_DELETION = 4

if prompt := st.chat_input("Explain elon musk's career"):

    # 4개의 메세지만 화면에 나오도록 표시. 4개 이상의 메세지는 토큰값이 많이 나오기 때문에 2개씩 삭제한다.
    if len(st.session_state.messages) >= MAX_MESSAGES_BEFORE_DELETION:
        del st.session_state.messages[0]
        del st.session_state.messages[0]

    # 대화 기록하기. 역할은 사용자로, 사용자가 날린 질문을 프롬프트에 저장해서 기록한다.
    st.session_state.messages.append({"role": "user", "content": prompt})

    # 만약 메세지 기록에서 역할이 "유저"로 되어있으면 프롬프트를 유저 아이콘으로 표기하기
    with st.chat_message("user"):
        st.markdown(prompt)

    # 만약 AI가 답변하면 RAG chain에서 받아온 답변 기록하기
    with st.chat_message("assistant"):
        message_placeholder = st.empty()
        full_response = ""

        # 위에서 만든 RAG 체인을 불러온다.
        result = chain.invoke({"input": prompt, "chat_history": st.session_state.messages})

        for chunk in result.split(" "):
            full_response += chunk + " "
            time.sleep(0.2)
            message_placeholder.markdown(full_response + "▌")
            message_placeholder.markdown(full_response)

    st.session_state.messages.append({"role": "assistant", "content": full_response})

print("_______________________")
print(st.session_state.messages)

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다