Today, I will

RAG 기반 Chat PDF 챗봇 만들기 | LangChain, Cohere, OpenAI API로 나만의 문서 질문 시스템 만들기 본문

Computer Science/인공지능,딥러닝

RAG 기반 Chat PDF 챗봇 만들기 | LangChain, Cohere, OpenAI API로 나만의 문서 질문 시스템 만들기

Lv.Forest 2025. 5. 1. 00:01

 

안녕하세요! 오늘은 업로드한 PDF 파일 내용을 바탕으로 실시간으로 질문에 답변해주는 AI 챗봇을 직접 만들어봅니다! LangChain + Cohere Rerank + OpenAI Embedding의 조합으로 RAG 기반의 스마트한 검색 시스템을 구현해보도록 합니다.

 

 

이 글에서 다룰 내용

  • RAG란 무엇인가?
  • PDF 기반 질문-응답 시스템의 구조
  • LangChain으로 벡터 검색 + LLM 연결하기
  • 전체 코드 분석과 주석
  • 실전 활용 팁 & 트러블슈팅

RAG란?

RAG는 Retrieval-Augmented Generation의 줄임말로,
"검색 기반 생성"을 의미합니다.

보통 LLM은 사전학습된 정보만을 기반으로 답변하지만,
RAG는 외부 문서를 검색하여 관련 정보를 추출하고,
그걸 바탕으로 답변을 생성
합니다.

📦 RAG의 구조
🔹 유저 질문
🔹 → 벡터 DB에서 관련 문서 검색
🔹 → 선택된 문서를 LLM에게 전달
🔹 → LLM이 문서 기반으로 답변 생성

업로드한 PDF에 대해 실시간 질문하는 챗봇 만들기

  • PDF를 업로드하면 AI가 청크 단위로 분석
  • 질문을 하면 유사한 부분을 검색해서 답변 생성
  • Streamlit으로 UI 구성
  • 핵심 기술:
    • LangChain (문서 로더, 벡터 검색)
    • OpenAI (embedding + LLM)
    • Cohere (reranker)

전체 아키텍처

flowchart TD
    A[PDF 업로드] --> B[문서 청크로 분할]
    B --> C[벡터화 (Embedding)]
    C --> D[FAISS에 저장]
    E[유저 질문] --> F[FAISS에서 관련 문서 검색]
    F --> G[Cohere Reranker로 정제]
    G --> H[GPT가 문서 기반으로 응답 생성]
    H --> I[Streamlit 채팅 UI에 출력]

코드 작성

1. 기본 구성

import streamlit as st
from streamlit_chat import message
  • Streamlit을 사용해서 웹 UI를 구성
  • streamlit_chat으로 챗봇 스타일 채팅 인터페이스 생성

2. PDF 로딩 + 분할

from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
  • PDF 문서를 500자 단위로 나누고, 20자씩 겹치게 분할

3. 문서 임베딩 및 벡터 DB 저장

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
  • OpenAI의 'text-embedding-ada-002'로 문서 벡터화
  • FAISS로 검색 가능한 벡터 데이터베이스 구축

4. 문서 검색 + 재정렬 (Reranking)

from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
  • FAISS로 문서 검색
  • Cohere Reranker로 가장 관련 있는 top 5 문서만 추출
    → 더 정확한 답변 유도 가능

5. GPT 연결 및 QA 체인 구성

from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
  • RetrievalQA 체인을 통해 문서를 기반으로 GPT가 답변 생성

6. 채팅 UI 구성 (Streamlit)

with st.form('rag_chat'):
    user_input = st.text_input(...)
  • 질문 입력 → 전송 → 응답 → 히스토리 저장
  • 아바타 스타일로 유저/봇 메시지를 구분

💡 Tips

  • OpenAI 키는 .env 파일 또는 환경변수로 관리하세요
  • Cohere API 키도 환경변수로 설정해야 합니다

💙 전체 코드

이 챗봇은 코드를 통해
논문, 수업자료, 혹은 각종 자료 PDF를 AI가 설명해주는 과외선생님이 될 수 있습니다.

누구에게나 유용한 지식 개인화 도구입니다.

import os
import streamlit as st
from streamlit_chat import message  # Import the message function for chat UI
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
from langchain.chains import RetrievalQA

# Title for the app
st.title("RAG 기반 Chat PDF")

# Initialize session state for chat history
if 'history' not in st.session_state:
    st.session_state['history'] = []

# File uploader for document processing
uploaded_file = st.file_uploader("파일을 업로드하세요 (PDF)", type=["pdf", "txt", "docx"])

if uploaded_file is not None:
    st.write("파일 업로드 성공!")

    # Save the uploaded file to a temporary location
    temp_file_path = os.path.join("temp", uploaded_file.name)
    os.makedirs("temp", exist_ok=True)
    with open(temp_file_path, "wb") as f:
        f.write(uploaded_file.getbuffer())

    # Show a loading spinner while processing the document
    with st.spinner("문서를 처리 중입니다. 잠시만 기다려주세요..."):
        # Load and process the uploaded file
        pdfLoad = PyPDFLoader(temp_file_path)
        documents = pdfLoad.load()
        txt_split = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=20)
        docs = txt_split.split_documents(documents)
        st.write(f"문서가 {len(docs)}개의 청크로 분할되었습니다.")

        # Create embeddings and vector store
        embedding = OpenAIEmbeddings(model='text-embedding-ada-002')
        vdb = FAISS.from_documents(docs, embedding)

        # Set up retriever with reranker
        base_retriever = vdb.as_retriever(search_kwargs={"k": 10})
        reranker = CohereRerank(model="rerank-multilingual-v2.0", top_n=5)
        retriever = ContextualCompressionRetriever(
            base_compressor=reranker,
            base_retriever=base_retriever,
        )

        # Initialize the LLM and QA chain
        gpt = ChatOpenAI(model='gpt-3.5-turbo', temperature=0)
        if "qa" not in st.session_state:
            qa = RetrievalQA.from_chain_type(
                llm=gpt,
                chain_type='stuff',
                retriever=retriever,
            )
            st.session_state.qa = qa

# Chat interface
response_container = st.container()

# Check if the document is processed
is_document_ready = 'docs' in locals() or 'docs' in globals()

# Input form for user questions
with st.form('rag_chat', clear_on_submit=True):
    user_input = st.text_input(
        '질문을 입력하세요:',
        key='input',
        disabled=not is_document_ready  # Disable input if the document is not ready
    )
    submit = st.form_submit_button('전송', disabled=not is_document_ready)  # Disable submit button as well

    if submit and user_input:
        # Show a loading spinner while processing the question
        with st.spinner("질문을 처리 중입니다. 잠시만 기다려주세요..."):
            # Generate a response using the QA chain
            response = st.session_state.qa.invoke({'query': user_input})['result']

        # Update chat history
        st.session_state['history'].append((user_input, response))

# Display chat history with custom avatars
with response_container:
    for i, (user_msg, bot_msg) in enumerate(st.session_state['history']):
        # User message with "fun-emoji" avatar
        message(user_msg, avatar_style="fun-emoji", is_user=True, key=f"user_{i}")
        # Bot message with "bottts" avatar
        message(bot_msg, avatar_style="bottts", is_user=False, key=f"bot_{i}")