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