Chatbot 프로젝트/RAG

12. Streamlit을 활용한 RAG 챗봇 구현

조찬국 2024. 11. 26. 00:30
728x90

0. Streamlit 이란?

Streamlit은 파이썬 코드만으로 웹 앱의 프론트(화면)을 아주 쉽게 만들 수 있게 도와주는 프레임워크이다. 

👉 데이터 분석 결과나 머신러닝 모델을 웹으로 바로 보여주고 싶을 때 유용하다.
👉 복잡한 HTML/CSS/JS 없이 터미널에  "streamlit run app.py"만 입력하면 프로그램을 실행한다.

예시 코드:

# app.py

import streamlit as st

st.title("Hello, Streamlit!")
st.write("이건 정말 간단한 웹앱입니다.")
 

1. 필요한 라이브러리 설치

 pip install streamlit 

pip install langchain_openai

pip install langchain

pip install -U langchain-community

pip install PyPDF2

pip install chromadb



1. 1. vs 터미널 접속

아래 3개의 명령어를 터미널에 복붙해서 필요 라이브러리 설치

pip install streamlit

streamlit hello 

pip install langchain_openai

 

1.2. 파이썬 파일 만들기

import streamlit as st

st.write("안녕하세요")

앱 실행: streamlit run test.py

 

앱 실행

 

 

1.3. 소스 코드

import streamlit as st
import os
from langchain_openai import ChatOpenAI


os.environ["OPENAI_API_KEY"] = (
    "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"
)

st.title("CHATbot")


# session_state에 messages Key값 지정 및 Streamlit 화면 진입 시, AI의 인사말을 기록하기
if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "assistant", "content": "안녕하세요 무엇을 도와드릴까요?"}
    ]

# 사용자나 AI가 질문/답변을 주고받을 시, 이를 기록하는 session_state
for msg in st.session_state.messages:
    st.chat_message(msg["role"]).write(msg["content"])


# 챗봇으로 활용할 AI 모델 선언
chat = ChatOpenAI(model="gpt-4o", temperature=0)

# chat_input()에 입력값이 있는 경우,
if prompt := st.chat_input():
    # messages라는 session_state에 역할은 사용자, 컨텐츠는 프롬프트를 각각 저장
    st.session_state.messages.append({"role": "user", "content": prompt})
    # chat_message()함수로 사용자 채팅 버블에 prompt 메시지를 기록
    st.chat_message("user").write(prompt)

    response = chat.invoke(prompt)
    msg = response.content

    # messages라는 session_state에 역할은 AI, 컨텐츠는 API답변을 각각 저장
    st.session_state.messages.append({"role": "assistant", "content": msg})
    # chat_message()함수로 AI 채팅 버블에 API 답변을 기록
    st.chat_message("assistant").write(msg)

 

 

2. 대화 이력 관리와 메모리구현

2.1. 파일 경로 설정 및 경로에 파일 업로드 (윈도우 기준)

  file_path = r"C:\Users\PC2309\Desktop\24-2\rag인강\streamlit\test\Constitution_of_Korea.pdf"

경로 앞에 r을 붙여서 한국어의 폴더 구조에서도 오류가 안나게 할 수 있다. 이때 파일명은 영어로 작성해야 한다.

2.2. 대화 이력 구현 코드

import os
import streamlit as st

from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from chromadb.config import Settings
from chromadb import Client

## 메모리 구현을 위한 라이브러리
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories.streamlit import (
    StreamlitChatMessageHistory,
)

# Set the OpenAI API key
os.environ["OPENAI_API_KEY"] = (
    "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"
)


# Streamlit에서는 @st.cache_resource를 통해 한번 실행한 자원을 리로드 시에 재실행하지 않도록 캐시메모리에 저장할 수 있습니다.
@st.cache_resource
def load_and_split_pdf(file_path):
    loader = PyPDFLoader(file_path)
    return loader.load_and_split()  # chunking이 아니라 쪽별로 갖고옴


# Create a vector store from the document chunks
@st.cache_resource
def create_vector_store(_docs):
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
    split_docs = text_splitter.split_documents(_docs)

    # Chroma DB 설정 (사용자가 원하는 경로에 저장)
    persist_directory = r"C:\Users\PC2309\Desktop\24-2\rag인강\streamlit\test\chroma_db"

    # Chroma DB 생성
    vectorstore = Chroma.from_documents(
        documents=split_docs,
        embedding=OpenAIEmbeddings(model="text-embedding-ada-002"),
        persist_directory=persist_directory,  # 새로운 방식의 persist_directory 사용
    )
    return vectorstore


# Initialize the LangChain components
@st.cache_resource
def initialize_components(selected_model):
    file_path = (
        r"C:\Users\PC2309\Desktop\24-2\rag인강\streamlit\test\Constitution_of_Korea.pdf"
    )
    pages = load_and_split_pdf(file_path)
    vectorstore = create_vector_store(pages)
    retriever = vectorstore.as_retriever()

    # Define the contextualize question prompt
    contextualize_q_system_prompt = """Given a chat history and the latest user question \
    which might reference context in the chat history, formulate a standalone question \
    which can be understood without the chat history. Do NOT answer the question, \
    just reformulate it if needed and otherwise return it as is."""
    contextualize_q_prompt = ChatPromptTemplate.from_messages(
        [
            ("system", contextualize_q_system_prompt),
            MessagesPlaceholder("history"),
            ("human", "{input}"),
        ]
    )

    # Define the answer question prompt
    qa_system_prompt = """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. \
    Keep the answer perfect. please use imogi with the answer.
    대답은 한국어로 하고, 존댓말을 써줘.\

    {context}"""
    qa_prompt = ChatPromptTemplate.from_messages(
        [
            ("system", qa_system_prompt),
            MessagesPlaceholder("history"),
            ("human", "{input}"),
        ]
    )

    llm = ChatOpenAI(model=selected_model)
    history_aware_retriever = create_history_aware_retriever(
        llm, retriever, contextualize_q_prompt
    )
    question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
    rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)
    return rag_chain


# Streamlit UI
st.header("헌법 Q&A 챗봇 💬 📚")
option = st.selectbox("Select GPT Model", ("gpt-4o", "gpt-3.5-turbo-0125"))
rag_chain = initialize_components(option)
chat_history = StreamlitChatMessageHistory(key="chat_messages")

conversational_rag_chain = RunnableWithMessageHistory(
    rag_chain,
    lambda session_id: chat_history,
    input_messages_key="input",
    history_messages_key="history",
    output_messages_key="answer",
)


if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "assistant", "content": "헌법에 대해 무엇이든 물어보세요!"}
    ]

for msg in chat_history.messages:
    st.chat_message(msg.type).write(msg.content)


if prompt_message := st.chat_input("Your question"):
    st.chat_message("human").write(prompt_message)
    with st.chat_message("ai"):
        with st.spinner("Thinking..."):
            config = {"configurable": {"session_id": "any"}}
            response = conversational_rag_chain.invoke(
                {"input": prompt_message}, config
            )

            answer = response["answer"]
            st.write(answer)
            with st.expander("참고 문서 확인"):
                for doc in response["context"]:
                    st.markdown(doc.metadata["source"], help=doc.page_content)

 

3. 결과 

3.1. vector store에 파일들 임베딩해서 저장

3.2  rag와 메모리 구현 결과

728x90