ๆผธๅ…ฅไฝณๅขƒ

๐Ÿ’ฌstreamlit๊ณผ RAG๋ฅผ ํ™œ์šฉํ•œ ์ฑ—๋ด‡

์š”์ƒˆ RAG์— ๋น ์ ธ์„œ ์žฌ๋ฐŒ๊ฒŒ ๊ณต๋ถ€ ์ค‘์ด๋‹ค.
๋ฐฐ์šด ๋‚ด์šฉ์„ ์‹ค์Šตํ•˜๊ธฐ ์œ„ํ•ด ๋‚˜๋งŒ์˜ Agent๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด ๋ดค๋‹ค. (๋ฌผ๋ก  ๊ต์žฌ ์ฐธ๊ณ ํ•ด์„œ)

๊ฐœ์š”

streamlit์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œ๊ธฐ๋ฐ˜ ์งˆ์˜์‘๋‹ต RAG ์‹œ์Šคํ…œ.
์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์„œ๋ฅผ ์—…๋กœ๋“œํ•˜๋ฉด ๋‚ด์šฉ์„ Embedding ํ•œ ํ›„ ์‚ฌ์šฉ์ž์˜ ์งˆ์˜์— ์‘๋‹ตํ•˜๋Š” ์ฑ—๋ด‡.

  1. ํ™˜๊ฒฝ: Python 3.12 / FAISS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ / Streamlit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  2. ๋ชจ๋ธ: gpt-4
  3. 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

์ฃผ์š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค๋ช…

  1. ChatOpenAI : OpenAI์˜ GPT ๋ชจ๋ธ์„ ์ด์š”ํ•ด ๋Œ€ํ™”๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด GPT-4 ๋ชจ๋ธ์„ ํ˜ธ์ถœํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ.
  2. FAISS : ๋ฒกํ„ฐDB๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. ์ž„๋ฒ ๋”ฉ๋œ ๋ฒกํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” DB๋ฅผ ์ œ๊ณตํ•ด์ฃผ๋Š” ํŒŒ์ด์ฌ ์ž์ฒด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  3. OpenAIEmbeddings : OpenAI์˜ ์ž„๋ฒ ๋”ฉ(๋ฒกํ„ฐํ™”)๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. ๋ฌธ์„œ๋ฅผ ๋ฒกํ„ฐํ™”ํ•˜์—ฌ ์œ ์‚ฌ์„ฑ ๊ฒ€์ƒ‰ ์ง€์›. ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋‹ค๊ตญ์–ด ์„ฑ๋Šฅ์ด ์ข‹์€ text-embedding-3-large๋ฅผ ์‚ฌ์šฉ(but ๋น„์‹ธ๊ฒ ์ฃ )
  4. CharacterTextSplitter : ๊ธด ๋ฌธ์„œ๋ฅผ ์ž‘์€ ์ฒญํฌ๋กœ ๋‚˜๋ˆ„๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ.
  5. ChatMessage, HumanMessage, SystemMessage : ๋Œ€ํ™”ํ˜• AI ๋ชจ๋ธ์—์„œ ์‚ฌ์šฉํ•  ๋ฉ”์‹œ์ง€ ์œ ํ˜•์„ ์ •์˜ํ•˜๋Š” ์Šคํ‚ค๋งˆ. ์‚ฌ์šฉ์ž ์งˆ๋ฌธ๊ณผ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋ฅผ GPT ๋ชจ๋ธ๋กœ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ.
  6. BaseCallbackHandler : GPT ๋ชจ๋ธ์˜ ์‘๋‹ต์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ์ฝœ๋ฐฑ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ •์˜ํ•˜๋Š” ํด๋ž˜์Šค. GPT ๋ชจ๋ธ์—์„œ ์ƒ์„ฑ๋œ ํ† ํฐ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ web UI์— ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ.
  7. 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๋…„์— ํฐ ์ด์Šˆ์˜€๋˜ ๊ฒŒ์ž„์Šคํƒ‘ ์‚ฌํƒœ์— ๋Œ€ํ•œ ๋ณด๊ณ ์ž๋ฃŒ๋กœ, ๊ฐœ์ธ ํˆฌ์ž์ž๋“ค์ด ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ–ˆ๋˜ ์ฆ๊ถŒ๊ฑฐ๋ž˜ ์•ฑ ‘๋กœ๋นˆํ›„๋“œ’์— ๋Œ€ํ•œ ๋‚ด์šฉ์ด ๋‹ด๊ฒจ์žˆ๋‹ค.
(๊ฐœ์ธ์ ์œผ๋กœ ๊ฒŒ์ž„์Šคํƒ‘ ์‚ฌํƒœ๋ฅผ ๋งค์šฐ ํฅ๋ฏธ๋กญ๊ฒŒ ์ƒ๊ฐํ•จ.)

Your Alt Text

์ด์ œ Streamlit์„ ์‹คํ–‰ํ•ด๋ณด์ž. streamlit run app.py ๋กœ ์‹คํ–‰ํ•˜๋ฉด ๋œ๋‹ค.

Your Alt Text

์š”๋ ‡๊ฒŒ ์•„์ฃผ ์‹ฌํ”Œํ•œ UI๊ฐ€ ๋‚˜ํƒ€๋‚œ๋‹ค. ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋ฌธ์„œ๋ฅผ ์—…๋กœ๋“œํ•˜๋ฉด ๋ฌธ์„œ ๋‹จ๋ฝ์ด ๋ช‡๊ฐœ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ ํ‘œ์‹œํ•ด์ค€๋‹ค. (์ƒ์„ฑ ์‹œ๊ฐ„์€ ๋ฌธ์„œ์˜ ๊ธธ์ด์— ๋”ฐ๋ผ ๋‹ค๋ฆ„)

์ด์ œ ์งˆ๋ฌธ์„ ํ•ด๋ณด์ž. ๋ฌธ์„œ์™€ ๊ด€๋ จ๋œ ์งˆ๋ฌธ ํ•˜๋‚˜์™€ ๊ด€๋ จ ์—†๋Š” ์งˆ๋ฌธ ํ•˜๋‚˜๋ฅผ ํ…Œ์ŠคํŠธ ํ•ด๋ณด๊ฒ ๋‹ค.

Your Alt Text

๋‚ด๊ฐ€ ๋‹ต๋ณ€์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•ด๋‹ฌ๋ผ๊ณ  ์กฐ๊ฑด์„ ๊ฑธ์–ด๋’€๊ธฐ์— ๊ธธ๊ฒŒ ์„ค๋ช…์„ ํ•ด์ฃผ์ง„ ์•Š์•˜๋‹ค.
๋ฌธ์„œ์—์„œ ๋งํ•˜๋Š” ๋งค์ˆ˜๋ฒ„ํŠผ์„ ์—†์•ค ์ด์œ ๋Š” ๋‚˜๋ฆ„ ์ž˜ ์š”์•ฝํ•ด์„œ ๋‹ต๋ณ€ํ•œ ๋“ฏํ•˜๋‹ค. (์”จํƒ€๋ธ ๋•Œ๋ฌธ์ด์•ผ ์”จํƒ€๋ธ ๋•Œ๋ฌธ์ด์•ผ)

๊ทธ๋ฆฌ๊ณ  ๊น€์น˜์ฐŒ๊ฐœ ๋ ˆ์‹œํ”ผ๋Š” ๋ชจ๋ฅธ๋‹ค๊ณ  ๋‹ต๋ณ€์„ ์ž˜ ํ•ด์ฃผ์—ˆ๋‹ค.

๊ฒฐ๋ก 

์ด๋ ‡๊ฒŒ ๋ฌธ์„œ ๊ธฐ๋ฐ˜ RAG ChatBot์„ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.
๋ณต์žกํ•œ ๊ตฌํ˜„ ์—†์ด ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ์ด์ง€๋งŒ, ๊ทธ ๋’ค์— ์žˆ๋Š” ๊ธฐ์ˆ ์€ ์ „ํ˜€ ๊ฐ„๋‹จํ•˜์ง€๊ฐ€ ์•Š๋‹ค.
OpenAI์˜ ๊ณ ๊ธ‰ LLM ๊ธฐ์ˆ ์„ ๋นŒ๋ ค์“ฐ๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฒฐ๊ตญ ๋‹ค ๋น„์šฉ์œผ๋กœ ์ง€๋ถˆํ•ด์•ผ ํ•œ๋‹ค.

Your Alt Text

์ด๋ฒˆ ํ…Œ์ŠคํŠธ์—๋งŒ ์‚ฌ์šฉ๋œ ํ† ํฐ์ด๋‹ค.

๋น„์šฉ ์ธก๋ฉด์—์„œ๋Š” ‘์ฒญํ‚น’, ‘ํ”„๋กฌํ”„ํŠธ’ ์ „๋žต์„ ์ž˜ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•  ๋“ฏ ํ•˜๋‹ค. ๋ฌผ๋ก  ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ๋„ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์ž˜ ์„ ํƒํ•ด์•ผ ํ•œ๋‹ค. ์ด ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋Š” ๋”ฐ๋กœ ์กฐ์‚ฌ๋ฅผ ํ•˜๊ณ  ์žˆ๋‹ค. ๊ธฐํšŒ๊ฐ€ ๋œ๋‹ค๋ฉด ๋ธ”๋กœ๊ทธ์—๋„ ์ •๋ฆฌํ•ด์„œ ์˜ฌ๋ฆฌ๋„๋ก ํ•˜๊ฒ ๋‹ค.

Debug Log

  1. 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