PDF 문서 기반 챗봇 만들기¶

  • 디스크에 데이터를 저장하여 프로그램 재 시작시에도 데이터가 유지
  • 변경된 파일에 대해서만 추가.
  • 파일 해시를 사용하여 변경된 파일만 처리
In [1]:
!pip install -Uq openai pypdf langchain langchain_core langchain_openai langchain_chroma langchain-community langchainhub
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 67.3/67.3 kB 2.6 MB/s eta 0:00:00
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 50.4/50.4 kB 469.3 kB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 383.5/383.5 kB 9.8 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 294.5/294.5 kB 11.8 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.0/1.0 MB 19.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 401.8/401.8 kB 17.0 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.7/49.7 kB 2.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.4/2.4 MB 41.7 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 604.0/604.0 kB 25.2 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.4/2.4 MB 43.7 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94.6/94.6 kB 5.8 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 76.4/76.4 kB 4.8 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 78.0/78.0 kB 4.9 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 326.6/326.6 kB 16.3 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 294.6/294.6 kB 15.0 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 35.2 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 273.8/273.8 kB 14.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.9/1.9 MB 42.7 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.3/49.3 kB 2.5 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 93.2/93.2 kB 4.8 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13.2/13.2 MB 54.5 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 52.5/52.5 kB 3.2 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 141.9/141.9 kB 9.4 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 54.4/54.4 kB 2.9 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 54.5/54.5 kB 3.5 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 71.5/71.5 kB 4.3 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 63.7/63.7 kB 4.2 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 58.3/58.3 kB 3.5 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 341.4/341.4 kB 18.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.4/3.4 MB 78.9 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 425.7/425.7 kB 24.3 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 164.1/164.1 kB 10.2 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 46.0/46.0 kB 2.9 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 86.8/86.8 kB 5.8 MB/s eta 0:00:00
  Building wheel for pypika (pyproject.toml) ... done
In [2]:
import os
from openai import OpenAI

def init_api():
    with open("chatgpt_kict2409.env") as env:
       for line in env:
           key, value = line.strip().split("=")
           os.environ[key] = value

init_api()
# client = OpenAI(api_key  = os.environ.get("API_KEY"))
os.environ["OPENAI_API_KEY"] = os.environ.get("API_KEY")
In [17]:
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
import os
import hashlib
from datetime import datetime
import json

# Chroma DB를 영구 저장소로 설정
persist_directory = './chroma_db'

# 파일 변경 추적을 위한 파일 경로
tracker_file = 'file_tracker.json'

def get_file_hash(filepath):
    with open(filepath, "rb") as f:
        file_hash = hashlib.md5()
        chunk = f.read(8192)
        while chunk:
            file_hash.update(chunk)
            chunk = f.read(8192)
    return file_hash.hexdigest()

def load_file_tracker():
    if os.path.exists(tracker_file):
        with open(tracker_file, 'r') as f:
            return json.load(f)
    return {}

def save_file_tracker(tracker):
    with open(tracker_file, 'w') as f:
        json.dump(tracker, f)

def process_pdf_files(folder_path):
    text_splitter = CharacterTextSplitter(
        separator="\n",
        chunk_size=1000,
        chunk_overlap=50
    )

    embedding_fun = OpenAIEmbeddings()
    db = Chroma(persist_directory=persist_directory, embedding_function=embedding_fun)

    file_tracker = load_file_tracker()

    for filename in os.listdir(folder_path):
        if filename.endswith(".pdf"):
            filepath = os.path.join(folder_path, filename)
            file_hash = get_file_hash(filepath)

            if filepath in file_tracker and file_tracker[filepath]['hash'] == file_hash:
                print(f"Skipping {filename} (no changes)")
                continue

            print(f"Processing {filename}...")

            raw_documents = PyPDFLoader(filepath).load()
            documents = text_splitter.split_documents(raw_documents)

            existing_docs = db.get(where={"source": filepath})
            if existing_docs['ids']:
                db.delete(ids=existing_docs['ids'])

            db.add_documents(documents)

            file_tracker[filepath] = {
                'hash': file_hash,
                'last_processed': datetime.now().isoformat()
            }

    save_file_tracker(file_tracker)
    return db

# PDF 파일이 저장된 폴더 경로를 설정
folder_path = './pdf_data/'
db = process_pdf_files(folder_path)

# 데이터베이스에서 검색을 수행할 수 있는 retriever 객체 생성
retriever = db.as_retriever(search_kwargs={"k": 10})

print("처리 완료. DB 검색을 위한 준비 완료")
Skipping 01_데이터의이해및데이터수집.pdf (no changes)
Skipping CHATGPT기본소개_V10_2406.pdf (no changes)
Processing CL01_02_Pandas알아보기_v12_class_230218.pdf...
Skipping CL01_01_python_dataVis_m_ver10_2408.pdf (no changes)
Skipping 01_predict_future_sales_v10_2409.pdf (no changes)
Skipping ChatGPT를활용한프로그래밍(1).pdf (no changes)
처리 완료. DB 검색을 위한 준비 완료
In [9]:
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

prompt_template = ChatPromptTemplate.from_template(
    "당신은 질문 답변 작업의 영리하고 창의적인 어시스턴트입니다. "
    "다음 문맥을 사용하여 질문에 답하세요. "
    "정확하고 신뢰성 있는 정보를 제공하고, 모르는 내용은 '모르겠습니다'라고 답변하세요. "
    "답변은 명확하고 간결하게, 최대 세 문장 이내로 작성하세요. 메타데이터나 추가적인 중요한 정보를 포함하도록 하세요. "
    "한국어로 작성합니다.\n\n"
    "질문: {question}\n"
    "문맥: {context}\n"
    "답변:"
)

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt_template
    | ChatOpenAI()
    | StrOutputParser()
)

def chat_with_user(user_message):
    ai_message = chain.invoke(user_message)
    return ai_message

# 대화 루프 시작
while True:
    message = input("USER :(quit or q : 종료)  ")
    if message.lower() == "quit" or message.lower() == "q":
        break

    ai_message = chat_with_user(message)
    print(f" AI : {ai_message}")
USER :(quit or q : 종료)  AI 기술 구분에 대해 알려주렴.
 AI : AI 기술은 머신러닝, 자연어처리, 자동화 및 로보틱스, Computer Vision 등으로 구분됩니다. 이러한 기술들은 자율주행, 농업, 교육 등 다양한 분야에 활용되고 있습니다.더 자세한 정보는 해당 링크를 참고하세요: https://www.youtube.com/watch?v=z4hGF_eKCxw
USER :(quit or q : 종료)  q
In [ ]:
 
In [ ]: