5 ๋ถ„ ์†Œ์š”

main.py

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from routers.upload import router as upload_router
from routers.read import router as read_router
from routers.index import router as index_router

from dotenv import load_dotenv
load_dotenv()

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")

app.include_router(index_router, prefix="")
app.include_router(upload_router, prefix="/upload")
app.include_router(read_router, prefix="/read")
  • app.mount("/static", StaticFiles(directory="static"), name="static"): ์ •์  ํŒŒ์ผ ์„œ๋น™์„ ์œ„ํ•ด, static ํด๋”๋ฅผ ๋ช…์‹œํ•ด์ค€๋‹ค.
  • app.include_router(index_router, prefix=""): include_router ๋ฅผ ์‚ฌ์šฉํ•ด main.py์—์„œ ๋ชจ๋“  ๋กœ์ง์„ ๊ด€๋ฆฌํ•˜์ง€ ์•Š๊ณ , ๋ถ„๋ฆฌํ•ด์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
    • Flask์—์„œ BluePrint์™€ ๋น„์Šทํ•œ ์—ญํ• ์ด๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์„ ๋“ฏ ํ•จ!
    • prefix="" ์˜ต์…˜์„ ์„ค์ •ํ•ด, ์—”๋“œ ํฌ์ธํŠธ๋ฅผ ์„ค์ •ํ•ด์ค€๋‹ค!
      • django์˜ urls.py ์™€ ๋น„์Šทํ•œ ์—ญํ• 

routers/upload.py

from fastapi import File, UploadFile, Form, APIRouter
from fastapi.responses import JSONResponse
from utils.load_pdf import load_pdf
from utils.save_answer import save_pdf_answer, save_none_pdf_answer
from utils.gpt_config import get_answer
from dotenv import load_dotenv
from utils.save_pdf import save_pdf
from datetime import datetime

load_dotenv()

router = APIRouter()
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

@router.post("")
async def upload_file(user_id: str = Form(...), pdf: UploadFile = File(None), question: str = Form(...)):
    pdf_path = await save_pdf(pdf, user_id, now)
    context = await load_pdf(pdf_path, question)
    prompt = f"Context: {context}\n\nQuestion: {question}\n\nAnswer:"
    answer = get_answer(prompt)
    save_pdf_answer(user_id, pdf_path, answer, pdf, now)

    return JSONResponse(content={"answer": answer}) 

@router.post("/nonepdf")
async def upload_file(user_id: str = Form(...), pdf: UploadFile = File(None), question: str = Form(...)):
    prompt = f"Question: {question}\n\nAnswer:"
    answer = get_answer(prompt)
    save_none_pdf_answer(user_id, answer, pdf, now)

    return JSONResponse(content={"answer": answer})

์œ„์™€ ๊ฐ™์ด ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜๋ฉด, pdf๋ฅผ ์ฒจ๋ถ€ํ•œ ์ƒํƒœ์—์„œ์˜ ์งˆ๋ฌธ๊ณผ pdf๋ฅผ ์ฒจ๋ถ€ํ•˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ์˜ ์งˆ๋ฌธ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค!

  • pdf๋ฅผ ์ฒจ๋ถ€ํ•œ ์ƒํƒœ์˜ ์—”๋“œ ํฌ์ธํŠธ: ~/chatbot
  • pdf๋ฅผ ์ฒจ๋ถ€ํ•˜์ง€ ์•Š์€ ์ƒํƒœ์˜ ์—”๋“œ ํฌ์ธํŠธ: ~/chatbot/nonepdf

์—ฌ๊ธฐ์„œ async ์™€ await ๋Š” django, flask์—์„œ ์‚ฌ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์ด ์—†๋Š” ์˜ˆ์•ฝ์–ด๋ผ์„œ ์•„๋ž˜์— ์ •๋ฆฌํ•ด๋ณธ๋‹ค!

์ตœ์‹  ํŒŒ์ด์ฌ ๋ฒ„์ „์€ async ์™€ await ๋ฌธ๋ฒ•๊ณผ ํ•จ๊ป˜ ์ฝ”๋ฃจํ‹ด ์ด๋ผ๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ๋ฅผ ์ง€์›ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค!

๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ

๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ด๋ž€, ํŠน์ • ์ฝ”๋“œ์˜ ์‹คํ–‰ ์™„๋ฃŒ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ํŒจ๋Ÿฌ๋‹ค์ž„์ด๋‹ค.

I/O ์ž‘์—…, ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๋“ฑ์˜ ๋ธ”๋กœํ‚น ์—ฐ์‚ฐ์—์„œ ํŠนํžˆ ์œ ์šฉํ•˜๋ฉฐ, asyncio ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค!

async def & await

async def : ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋Š” ์˜ˆ์•ฝ์–ด์ž„. ์ด๋ ‡๊ฒŒ ์„ ์–ธ๋œ ํ•จ์ˆ˜๋Š” ์ฝ”๋ฃจํ‹ด ์ด๋ผ๋Š” ๊ฒƒ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

await : ์ฝ”๋ฃจํ‹ด์˜ ์‹คํ–‰์„ ์ผ์‹œ ์ค‘์ง€ํ•˜๊ณ , ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์˜ˆ์•ฝ์–ด์ž„. await๋Š” async ํ•จ์ˆ˜ ๋‚ด์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•จ!

์ฝ”๋ฃจํ‹ด

๊ทธ๋Ÿผ ์ฝ”๋ฃจํ‹ด์ด ๋ญ˜๊นŒ?

์˜ˆ๋ฅผ ๋“ค์–ด, ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ฃผ๋ฌธ์ด ๋™์‹œ์— ๋“ค์–ด์™”๋Š”๋ฐ ์š”๋ฆฌ์‚ฌ๋Š” ํ•œ ๋ช…์ด๋‹ค.

๊ทธ๋Ÿผ ์š”๋ฆฌ์‚ฌ๋Š” ์—ฌ๋Ÿฌ ์š”๋ฆฌ๋ฅผ ๋™์‹œ์— ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋Ÿฐ ๋ฐฉ์‹์„ ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค๊ณ  ๋งํ•œ๋‹ค.

์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ ์š”๋ฆฌ์‚ฌ๋Š” ์ฝ”๋ฃจํ‹ด๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค!

  • ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋ฒˆ๊ฐˆ์•„ ์ˆ˜ํ–‰: ํ•˜๋‚˜์˜ ์ž‘์—…์ด ๋๋‚˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ, ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Œ.
  • ์ž‘์—…์˜ ์ค‘๋‹จ๊ณผ ์žฌ๊ฐœ: ์ง„ํ–‰ ์ค‘์ด๋˜ ์ž‘์—…์—์„œ ๋‹ค๋ฅธ ์ž‘์—…์œผ๋กœ ์ „ํ™˜ํ•  ๋•Œ, ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊ธฐ์–ตํ•ด์•ผ ํ•œ๋‹ค. ์ด ์ฒ˜๋Ÿผ ์ฝ”๋ฃจํ‹ด์€ ์ค‘๋‹จ๋œ ์ง€์ ์„ ๊ธฐ์–ตํ•˜๊ณ , ๋‚˜์ค‘์— ๊ทธ ์ง€์ ๋ถ€ํ„ฐ ์ž‘์—…์„ ์žฌ๊ฐœํ•œ๋‹ค.
  • ํšจ์œจ์ ์ธ ์ž‘์—… ๊ด€๋ฆฌ: ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋‹ค.

ํŒŒ์ด์ฌ์€ async ์™€ await ๋ฅผ ์‚ฌ์šฉํ•ด ์ฝ”๋ฃจํ‹ด์„ ๋งŒ๋“ค๊ณ , async๋กœ ์ •์˜๋œ ํ•จ์ˆ˜๋Š” โ€™ํ•  ์ผ ๋ชฉ๋กโ€™, await ๋Š” โ€™์ด ํ•  ์ผ์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒโ€™ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค!

์ฆ‰, ์ฝ”๋ฃจํ‹ด์€ ์—ฌ๋Ÿฌ ์ผ์„ ๋™์‹œ์— ํ•˜๋ฉด์„œ, ๊ฐ๊ฐ์˜ ์ผ์ด ์„œ๋กœ ๋ฐฉํ•ด๋ฐ›์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค!

utils ํด๋”

๋‚˜๋Š” controller์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋กœ์ง์ด ๊ธธ๊ณ  ๋ณต์žกํ•œ ๊ฒฝ์šฐ, utils ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ ๋ฉ”์„œ๋“œ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•œ๋‹ค.

AOP ๋ฐฉ์‹์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์„์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ์šฐ์„  ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋กœ์ง ํ˜น์€ ํ”„๋กœ์ ํŠธ ์ „์ฒด์— ๊ฑธ์ณ ์‚ฌ์šฉ๋˜๋Š” ๋กœ์ง, ๋Ž์Šค(Depth)๊ฐ€ ๊นŠ์–ด ๊ฐ€๋…์„ฑ์„ ์ €ํ•ดํ•˜๋Š” ์ฝ”๋“œ ๋“ฑ์„ ๋”ฐ๋กœ ๋ชจ๋“ˆํ™”ํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

๋จผ์ € ChatGPT API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋ธ์„ ์„ ์ •ํ•˜๋Š” ๋“ฑ์˜ ์„ค์ •์„ ์ •์˜ํ•˜๋Š” git_config.py, pdf๋ฅผ loadํ•˜์—ฌ ์ฝ๋Š” load_pdf.py, ์ฒจ๋ถ€๋œ pdf๋ฅผ ์ง€์ •๋œ ๊ฒฝ๋กœ์— ์ €์žฅํ•˜๋Š” save_pdf.py, ChatGPT์˜ ๋‹ต๋ณ€์„ ์ง€์ •๋œ ๊ฒฝ๋กœ์— ์ €์žฅํ•˜๋Š” save_answer.py ๊ฐ€ ์žˆ๋‹ค.

gpt_config.py

import os
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

OPENAI_KEY = os.getenv("OPENAI_KEY")
MODEL = "gpt-3.5-turbo"

client = OpenAI(
    api_key = OPENAI_KEY
)

def get_answer(prompt: str):
    response = client.chat.completions.create(
            model=MODEL,
            messages=[
                {"role": "system", "content": "You are a helpful assistant."},
                {"role": "user", "content": prompt}
            ]
        )
    
    return response.choices[0].message.content

load_pdf.py

from langchain_community.document_loaders import PyPDFLoader
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from utils.gpt_config import OPENAI_KEY

async def load_pdf(pdf_path: str, question: str) -> str:
    loader = PyPDFLoader(pdf_path)
    pages = loader.load_and_split()
    embeddings = OpenAIEmbeddings(openai_api_key=OPENAI_KEY)
    faiss_index = FAISS.from_documents(pages, embeddings)
    docs = faiss_index.similarity_search(question, k=4)
    context = "\n\n".join([doc.page_content for doc in docs])

    return context

save_answer.py

import os
from fastapi import File, UploadFile

def save_pdf_answer(user_id: str, pdf_path: str, answer: str, pdf: UploadFile = File(None), time: str = None) -> bool:
    try:
        answer_filename = f"answers/pdfs/{os.path.basename(pdf_path) if pdf else 'no_pdf'}_{time}.txt"
        os.makedirs(os.path.dirname(answer_filename), exist_ok=True)
        with open(answer_filename, "w") as f:
            f.write(answer)
        return True
    except Exception as e:
        print(e)   
        return False
    
    
def save_none_pdf_answer(user_id: str, answer: str, pdf: UploadFile = File(None), time: str = None) -> bool:
    try:
        answer_filename = f"answers/nonepdfs/{user_id}_none_pdf_{time}.txt"
        os.makedirs(os.path.dirname(answer_filename), exist_ok=True)
        with open(answer_filename, "w") as f:
            f.write(answer)
        return True
    except Exception as e:
        print(e)   
        return False

save_pdf.py

from fastapi import UploadFile

async def save_pdf(pdf: UploadFile, user_id: str, time: str) -> str:
    try:
        pdf_path = f"pdfs/{user_id}_{pdf.filename}_{time}"
        with open(pdf_path, "wb") as buffer:
            buffer.write(await pdf.read())
        return pdf_path
    except Exception as e:
        print(e)
        return None

static ํด๋”

static ํด๋”์—์„œ๋Š” html, css, javascript ํŒŒ์ผ์„ ๊ด€๋ฆฌํ•œ๋‹ค.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PDF Q&A System</title>
    <link rel="stylesheet" href="/static/styles.css">
</head>
<body>
    <div class="container">
        <h1>PDF Q&A System</h1>
        <form id="uploadForm">
            <input type="text" id="userId" placeholder="User ID" required>
            <input type="file" id="pdfFile" accept=".pdf">
            <textarea id="question" placeholder="Enter your question" required></textarea>
            <button type="submit">Submit</button>
        </form>
        <div id="answer"></div>
    </div>
    <script src="/static/script.js"></script>
</body>
</html>

script.js

// seperate situation of pdf and none pdf
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const userId = document.getElementById('userId').value;
    const pdfFile = document.getElementById('pdfFile').files[0];
    const question = document.getElementById('question').value;

    const formData = new FormData();
    formData.append('user_id', userId);
    formData.append('question', question);
    if (pdfFile) {
        formData.append('pdf', pdfFile);
        try {
            const response = await fetch('/upload/', {
                method: 'POST',
                body: formData
            });
    
            const data = await response.json();
            document.getElementById('answer').innerHTML = `<h2>Answer:</h2><p>${data.answer}</p>`;
        } catch (error) {
            console.error('Error:', error);
            document.getElementById('answer').innerHTML = '<p>An error occurred. Please try again.</p>';
        }
    }
    else {
        try{
        const response = await fetch('/upload/nonepdf/', {
            method: 'POST',
            body: formData
        });
        
            const data = await response.json();
            document.getElementById('answer').innerHTML = `<h2>Answer:</h2><p>${data.answer}</p>`;
        } catch (error) {
            console.error('Error:', error);
            document.getElementById('answer').innerHTML = '<p>An error occurred. Please try again.</p>';
        }
    }
});
  • pdf๋ฅผ ์ฒจ๋ถ€ํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์„ค์ •ํ•˜์—ฌ POST ์š”์ฒญ์„๋ณด๋‚ธ๋‹ค!

styles.css

body {
    font-family: Arial, sans-serif;
    line-height: 1.6;
    margin: 0;
    padding: 0;
    background-color: #f4f4f4;
}

.container {
    width: 80%;
    margin: auto;
    overflow: hidden;
    padding: 20px;
}

form {
    background: #fff;
    padding: 20px;
    margin-bottom: 20px;
}

input[type="text"], input[type="file"], textarea {
    width: 100%;
    padding: 10px;
    margin-bottom: 10px;
}

button {
    display: block;
    width: 100%;
    padding: 10px;
    background: #333;
    color: #fff;
    border: none;
    cursor: pointer;
}

button:hover {
    background: #555;
}

#answer {
    background: #fff;
    padding: 20px;
    margin-top: 20px;
}

์ฐธ๊ณ  ์ž๋ฃŒ

๋™์‹œ์„ฑ๊ณผ async / await - FastAPI

ํŒŒ์ด์ฌ ๋น„๋™๊ธฐ(async)ํ•จ์ˆ˜์™€ ์ฝ”๋ฃจํ‹ด(coroutine) ํ๋ฆ„ ์ดํ•ดํ•˜๊ธฐ

ํƒœ๊ทธ: , ,

์นดํ…Œ๊ณ ๋ฆฌ:

์—…๋ฐ์ดํŠธ:

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ