๐ฌstreamlit๊ณผ RAG๋ฅผ ํ์ฉํ ์ฑ๋ด
์์ RAG์ ๋น ์ ธ์ ์ฌ๋ฐ๊ฒ ๊ณต๋ถ ์ค์ด๋ค.
๋ฐฐ์ด ๋ด์ฉ์ ์ค์ตํ๊ธฐ ์ํด ๋๋ง์ Agent๋ฅผ ํ๋ ๋ง๋ค์ด ๋ดค๋ค. (๋ฌผ๋ก ๊ต์ฌ ์ฐธ๊ณ ํด์)
๊ฐ์
streamlit์ ์ฌ์ฉํ์ฌ ๋ฌธ์๊ธฐ๋ฐ ์ง์์๋ต RAG ์์คํ
.
์ฌ์ฉ์๊ฐ ๋ฌธ์๋ฅผ ์
๋ก๋ํ๋ฉด ๋ด์ฉ์ Embedding ํ ํ ์ฌ์ฉ์์ ์ง์์ ์๋ตํ๋ ์ฑ๋ด.
- ํ๊ฒฝ: Python 3.12 / FAISS ๋ผ์ด๋ธ๋ฌ๋ฆฌ / Streamlit ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ๋ชจ๋ธ: gpt-4
- GtiHub url : https://github.com/Solxcero/Lang/blob/main/pdfChatApp2.py
๊ณผ์
Library
์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์๋๊ณผ ๊ฐ๋ค.
from dotenv import load_dotenv
from langchain_community.chat_models import ChatOpenAI
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.schema import ChatMessage, HumanMessage, SystemMessage
from langchain.callbacks.base import BaseCallbackHandler
from pdfminer.high_level import extract_text
import streamlit as st
import os
์ฃผ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค๋ช
- ChatOpenAI : OpenAI์ GPT ๋ชจ๋ธ์ ์ด์ํด ๋ํ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ. ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ์์ฑํ๊ธฐ ์ํด GPT-4 ๋ชจ๋ธ์ ํธ์ถํ๋ ๋ฐ ์ฌ์ฉ.
- FAISS : ๋ฒกํฐDB๋ผ์ด๋ธ๋ฌ๋ฆฌ. ์๋ฒ ๋ฉ๋ ๋ฒกํฐ๋ฅผ ์ ์ฅํ๋ DB๋ฅผ ์ ๊ณตํด์ฃผ๋ ํ์ด์ฌ ์์ฒด ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- OpenAIEmbeddings : OpenAI์ ์๋ฒ ๋ฉ(๋ฒกํฐํ)๋ชจ๋ธ์ ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ. ๋ฌธ์๋ฅผ ๋ฒกํฐํํ์ฌ ์ ์ฌ์ฑ ๊ฒ์ ์ง์. ์ด๋ฒ ํ๋ก์ ํธ์์๋ ๋ค๊ตญ์ด ์ฑ๋ฅ์ด ์ข์ text-embedding-3-large๋ฅผ ์ฌ์ฉ(but ๋น์ธ๊ฒ ์ฃ )
- CharacterTextSplitter : ๊ธด ๋ฌธ์๋ฅผ ์์ ์ฒญํฌ๋ก ๋๋๋ ๋ฐ ์ฌ์ฉ๋๋ ์ ํธ๋ฆฌํฐ.
- ChatMessage, HumanMessage, SystemMessage : ๋ํํ AI ๋ชจ๋ธ์์ ์ฌ์ฉํ ๋ฉ์์ง ์ ํ์ ์ ์ํ๋ ์คํค๋ง. ์ฌ์ฉ์ ์ง๋ฌธ๊ณผ ์์คํ ํ๋กฌํํธ๋ฅผ GPT ๋ชจ๋ธ๋ก ์ ๋ฌํ๊ธฐ ์ํด ์ฌ์ฉ.
- BaseCallbackHandler : GPT ๋ชจ๋ธ์ ์๋ต์ ์ค์๊ฐ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ์ง์ํ๋ ์ฝ๋ฐฑ ํธ๋ค๋ฌ๋ฅผ ์ ์ํ๋ ํด๋์ค. GPT ๋ชจ๋ธ์์ ์์ฑ๋ ํ ํฐ์ ์ค์๊ฐ์ผ๋ก web UI์ ํ์ํ๊ธฐ ์ํด ์ฌ์ฉ.
- extract_text : PDF ํ์ผ์์ ํ ์คํธ๋ฅผ ์ถ์ถํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๊ณ ์์ค API.
MarkDownStreamHandler
Streamlit ๋งํฌ๋ค์ด ์ปจํ ์ด๋์ ์์ฑ๋ ํ ํฐ์ ์ค์๊ฐ์ผ๋ก ์คํธ๋ฆฌ๋ฐํ๋ ์ฌ์ฉ์ ์ ์ ํธ๋ค๋ฌ
class MarkdownStreamHandler(BaseCallbackHandler):
def __init__(self, output_container, initial_content=""):
self.output_container = output_container
self.generated_content = initial_content
def on_llm_new_token(self, token: str, **kwargs) -> None:
self.generated_content += token
self.output_container.markdown(self.generated_content)
-
BaseCallbackHandler : ํน์ ์ด๋ฒคํธ ๋ฐ์ ์ ํธ์ถ๋๋ ์ฝ๋ฐฑ ๋ฉ์๋(on_llm_new_token ๋ฑ)๋ฅผ ์ ๊ณตํ๋ ํธ๋ค๋ฌ. ์ฃผ๋ก LLM๊ณผ ๊ฐ์ ์์คํ ๊ณผ ์ํธ์์ฉํจ. ๋ถ๋ชจํด๋์ค์์ ์ ๊ณตํ๋ ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ์ฌ ํน์ ๋์(์ฌ๊ธฐ์๋ ๋งํฌ๋ค์ด์คํธ๋ฆฌ๋ฐ)์ ๊ตฌํ
-
output_container : ๋งํฌ๋ค์ด ํ์ํ ๋์
-
initial_content : ์คํธ๋ฆฌ๋ฐ ์์ ์ ์ด๊ธฐํ๋ ์ํ์ ํ ์คํธ
-
self.output_container : ์ธ๋ถ์์ ์ ๋ฌ๋๋ ์ถ๋ ฅ ์ปจํ ์ด๋ ๊ฐ์ฒด๋ฅผ ์ ์ฅ
-
self.generated_content : ๋์ ๋ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ
-
on_llm_new_token ๋ฉ์๋ : ์๋ก์ด ํ ํฐ์ด ์์ฑ๋ ๋๋ง๋ค ํธ์ถ๋๋ฉฐ, ์์ฑ๋๋ ํ ํฐ์ generated_content์ ์ถ๊ฐ
PDF ์ฒ๋ฆฌ ํจ์
def extract_text_from_pdf(file):
'''pdfminer๋ฅผ ์ฌ์ฉํ์ฌ PDF ํ์ผ์์ ํ
์คํธ ์ถ์ถ.'''
try:
return extract_text(file)
except Exception as error:
st.error(f"PDF ํ
์คํธ ์ถ์ถ ์ค ์ค๋ฅ ๋ฐ์: {error}")
return ""
def handle_uploaded_file(file):
'''์
๋ก๋ ๋ PDF ํ์ผ์ ์ฒ๋ฆฌํ๊ณ ๋ฒกํฐ ์คํ ์ด ์ค๋น'''
if not file:
return None, None
# ํ์ผ ์ ํ์ ๋ฐ๋ผ ํ
์คํธ ์ถ์ถ
document_text = extract_text_from_pdf(file) if file.type == 'application/pdf' else ""
if not document_text:
st.error("ํ
์คํธ ์ถ์ถ ๋ถ๊ฐ")
return None, None
# ๋ฌธ์๋ฅผ ์์ ์ฒญํฌ๋ก ๋๋์ด ๋ฒกํฐํ ์ค๋น
text_splitter = CharacterTextSplitter(
separator="\n",
chunk_size=1000,
chunk_overlap=200
)
document_chunks = text_splitter.create_documents([document_text])
st.info(f"{len(document_chunks)} ๊ฐ์ ๋ฌธ์ ๋จ๋ฝ ์์ฑ.")
# ์ ์ฌ์ฑ ๊ฒ์์ ์ํ ๋ฒกํฐ ์คํ ์ด ์์ฑ
vectorstore = FAISS.from_documents(document_chunks, embedding_model)
return vectorstore, document_text
RAG ์๋ต ์์ฑ
def get_rag_response(user_query, vectorstore, callback_handler):
'''๊ฒ์๋ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ๋ฅ๋ก ์ฌ์ฉ์ ์ง๋ฌธ์ ๋ํ ์๋ต ์์ฑ'''
if not vectorstore:
st.error("No Vectorstore. Upload docs first.")
return ""
# ๊ฐ์ฅ ์ ์ฌํ ๋ฌธ์ 3๊ฐ ๊ฒ์
retrieved_docs = vectorstore.similarity_search(user_query, k=3)
retrieved_text = "\n".join(f"๋ฌธ์ {i+1} : {doc.page_content}" for i, doc in enumerate(retrieved_docs))
# LLM ์ค์
chat_model = ChatOpenAI(model_name = "gpt-4", temperature=0, streaming=True, callbacks=[callback_handler])
# RAG ํ๋กฌํํธ
rag_prompt = [
SystemMessage(content = "์ ๊ณต๋ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ต๋ณํ์ธ์. ์ ๋ณด๊ฐ ์์ผ๋ฉด '๋ชจ๋ฅด๊ฒ ์ต๋๋ค' ๋ผ๊ณ ๋ต๋ณํ์ธ์"),
HumanMessage(content=f"์ง๋ฌธ : {user_query}\n\n{retrieved_text}")
]
try:
response = chat_model(rag_prompt)
return response.content
except Exception as error:
st.error(f"์๋ต ์์ฑ ์ค ์ค๋ฅ ๋ฐ์: {error}")
return ""
- ์์ฑ๋ ์๋ต์ ์ผ๊ด์ฑ์ ๋์ด๊ธฐ ์ํด temperature๋ฅผ 0์ผ๋ก ์ค์ (์์ ๋๋ฅผ ๋ฐฐ์ ํ ์ด์ฑ์ ์ธ ์๋ต)
- callbacks=[callback_handler] : ๋ชจ๋ธ์ด ์ถ๋ ฅ์ด ์์ฑ๋ ๋๋ง๋ค callback_handler๋ฅผ ํตํด ์ค์๊ฐ์ผ๋ก ์ฒ๋ฆฌ
์๋ ๊ฒ ์ฃผ์ ํจ์์ด๋ฉฐ, streamlit UI ๋ฐ ์ฌ์ฉ์ ์ฟผ๋ฆฌ ์ฒ๋ฆฌ์ ๊ฐ์ ์ ์ฒด ์์ค์ฝ๋๋ ๊นํ๋ธ์์ ํ์ธํ ์ ์๋ค.
GtiHub url : https://github.com/Solxcero/Lang/blob/main/pdfChatApp2.py
TEST
๋ด๊ฐ ํ
์คํธํ ๋ฌธ์๋ 2021๋
์ ํฐ ์ด์์๋ ๊ฒ์์คํ ์ฌํ์ ๋ํ ๋ณด๊ณ ์๋ฃ๋ก, ๊ฐ์ธ ํฌ์์๋ค์ด ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ์ฆ๊ถ๊ฑฐ๋ ์ฑ ‘๋ก๋นํ๋’์ ๋ํ ๋ด์ฉ์ด ๋ด๊ฒจ์๋ค.
(๊ฐ์ธ์ ์ผ๋ก ๊ฒ์์คํ ์ฌํ๋ฅผ ๋งค์ฐ ํฅ๋ฏธ๋กญ๊ฒ ์๊ฐํจ.)
์ด์ Streamlit์ ์คํํด๋ณด์. streamlit run app.py
๋ก ์คํํ๋ฉด ๋๋ค.
์๋ ๊ฒ ์์ฃผ ์ฌํํ UI๊ฐ ๋ํ๋๋ค. ๋ด๊ฐ ์ํ๋ ๋ฌธ์๋ฅผ ์ ๋ก๋ํ๋ฉด ๋ฌธ์ ๋จ๋ฝ์ด ๋ช๊ฐ๊ฐ ์์ฑ๋์๋์ง ํ์ํด์ค๋ค. (์์ฑ ์๊ฐ์ ๋ฌธ์์ ๊ธธ์ด์ ๋ฐ๋ผ ๋ค๋ฆ)
์ด์ ์ง๋ฌธ์ ํด๋ณด์. ๋ฌธ์์ ๊ด๋ จ๋ ์ง๋ฌธ ํ๋์ ๊ด๋ จ ์๋ ์ง๋ฌธ ํ๋๋ฅผ ํ ์คํธ ํด๋ณด๊ฒ ๋ค.
๋ด๊ฐ ๋ต๋ณ์ ๊ฐ๊ฒฐํ๊ฒ ํด๋ฌ๋ผ๊ณ ์กฐ๊ฑด์ ๊ฑธ์ด๋๊ธฐ์ ๊ธธ๊ฒ ์ค๋ช
์ ํด์ฃผ์ง ์์๋ค.
๋ฌธ์์์ ๋งํ๋ ๋งค์๋ฒํผ์ ์์ค ์ด์ ๋ ๋๋ฆ ์ ์์ฝํด์ ๋ต๋ณํ ๋ฏํ๋ค. (์จํ๋ธ ๋๋ฌธ์ด์ผ ์จํ๋ธ ๋๋ฌธ์ด์ผ)
๊ทธ๋ฆฌ๊ณ ๊น์น์ฐ๊ฐ ๋ ์ํผ๋ ๋ชจ๋ฅธ๋ค๊ณ ๋ต๋ณ์ ์ ํด์ฃผ์๋ค.
๊ฒฐ๋ก
์ด๋ ๊ฒ ๋ฌธ์ ๊ธฐ๋ฐ RAG ChatBot์ ๋ง๋ค์ด๋ณด์๋ค.
๋ณต์กํ ๊ตฌํ ์์ด ๊ฐ๋จํ ์ฝ๋์ด์ง๋ง, ๊ทธ ๋ค์ ์๋ ๊ธฐ์ ์ ์ ํ ๊ฐ๋จํ์ง๊ฐ ์๋ค.
OpenAI์ ๊ณ ๊ธ LLM ๊ธฐ์ ์ ๋น๋ ค์ฐ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ตญ ๋ค ๋น์ฉ์ผ๋ก ์ง๋ถํด์ผ ํ๋ค.
์ด๋ฒ ํ ์คํธ์๋ง ์ฌ์ฉ๋ ํ ํฐ์ด๋ค.
๋น์ฉ ์ธก๋ฉด์์๋ ‘์ฒญํน’, ‘ํ๋กฌํํธ’ ์ ๋ต์ ์ ๊ตฌ์ฑํ๋ ๊ฒ ์ค์ํ ๋ฏ ํ๋ค. ๋ฌผ๋ก ์๋ฒ ๋ฉ ๋ชจ๋ธ๋ ์ํฉ์ ๋ง๊ฒ ์ ์ ํํด์ผ ํ๋ค. ์ด ๋ถ๋ถ์ ๋ํด์๋ ๋ฐ๋ก ์กฐ์ฌ๋ฅผ ํ๊ณ ์๋ค. ๊ธฐํ๊ฐ ๋๋ค๋ฉด ๋ธ๋ก๊ทธ์๋ ์ ๋ฆฌํด์ ์ฌ๋ฆฌ๋๋ก ํ๊ฒ ๋ค.
Debug Log
-
Pydantic Error
PydanticUserError: `OpenAI` is not fully defined; you should define `BaseCache`, then call `OpenAI.model_rebuild()`.
pydantic ๋ฒ์ ์ด 2.11.0 ์ด์์ด ์ค์น๋ ๊ฒฝ์ฐ์ langchain ๋ชจ๋๊ณผ ํธํ์ด ์๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์.
ํด๊ฒฐ : ๋ฒ์ ๋ง์ถฐ์ฃผ๊ธฐ
pip install langchain-core==0.3.0 langchain-openai==0.2.0 pydantic==2.10.6