FastAPI #2 논문 업로드 및 벡터화 API

FastAPI #2 논문 업로드 및 벡터화 API #

#2025-09-17


1. 실행 #

$ pwd
/Users/yshmbid/Documents/home/github/MLops
$ ls
mariadb_tmplt           pjt-main.py             skala-fastapi-rpt.zip
mariadb_tmplt.zip       skala-fastapi-rpt       template.zip

$ uvicorn pjt-main:app --host 127.0.0.1 --port 8002 --reload

INFO:     Will watch for changes in these directories: ['/Users/yshmbid/Documents/home/github/MLops']
INFO:     Uvicorn running on http://127.0.0.1:8002 (Press CTRL+C to quit)
INFO:     Started reloader process [75232] using WatchFiles
INFO:     Started server process [75234]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
image

#

제대로 떴으니깐 pdf 처리 해보기

image image

질의응답: attention is all you need가 무엇인가?

image image

답변 잘 나온다.

#

RAG가 뭐냐고 물어보면?

image image

논문에 없는건 답변하지말라고 햇기때문에 답안해줌

#

2. 코드 #

# pip install -r pjt-requirements.txt

# pip install langchain-core langchain-community langchain-openai faiss-cpu pypdf python-multipart

# pjt-main.py

from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse
from pypdf import PdfReader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
import os
import tempfile

# 환경변수 설정 또는 .env로 관리 권장
os.environ["OPENAI_API_KEY"] = "*" # *: api 블라인드 처리

app = FastAPI(
    title="논문 업로드 및 벡터화 API",
    description="PDF 파일을 업로드하면 텍스트를 추출하고 FAISS 벡터 DB로 저장합니다.",
    version="1.0.0",
)

# 전역 벡터스토어 저장소 (데모용, 실제에선 외부 저장소 권장)
VECTORSTORE = None

def load_paper(file_path: str) -> str:
    reader = PdfReader(file_path)
    return "\n".join([p.extract_text() for p in reader.pages if p.extract_text()])

@app.post("/upload-paper", summary="논문 업로드 및 벡터화", tags=["PDF 처리"])
async def upload_pdf(file: UploadFile = File(...)):
    if not file.filename.endswith(".pdf"):
        raise HTTPException(status_code=400, detail="PDF 파일만 허용됩니다.")
    
    try:
        # 임시 파일 저장
        with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
            tmp.write(await file.read())
            tmp_path = tmp.name

        # 1단계: 텍스트 추출
        paper_text = load_paper(tmp_path)

        # 2단계: 문서 분할
        splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
        docs = splitter.create_documents([paper_text])

        # 3단계: 벡터 저장 (전역 변수에 저장)
        global VECTORSTORE
        VECTORSTORE = FAISS.from_documents(docs, OpenAIEmbeddings())

        return JSONResponse(content={
            "message": f"총 {len(docs)}개의 청크가 벡터로 저장되었습니다."
        })

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"처리 중 오류 발생: {str(e)}")


from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

# ✅ 요청 스키마
class PromptRequest(BaseModel):
    prompt: str
    top_k: int = 3  # 관련 문서 수, 기본값 3

# ✅ 응답 스키마
class AnswerResponse(BaseModel):
    prompt: str
    response: str

@app.post("/ask", response_model=AnswerResponse, summary="프롬프트 기반 질문 처리", tags=["질의 응답"])
async def ask_question(request: PromptRequest):
    global VECTORSTORE
    if VECTORSTORE is None:
        raise HTTPException(status_code=400, detail="벡터스토어가 초기화되지 않았습니다. 먼저 PDF를 업로드하세요.")
    
    try:
        # 관련 문서 검색
        related_docs = VECTORSTORE.similarity_search(request.prompt, k=request.top_k)
        context = "\n\n".join([doc.page_content for doc in related_docs])
        
        # 프롬프트 생성
        full_prompt = f"""너는 논문 기반 연구 보조 AI이다. 논문에서 발췌한 내용에 대해서만 답변하고 마무리하라. 논문에서 발췌한 내용에서 답변이 없으면 벡터 DB에서 답변을 찾을 수 없다고 하라. 다음은 논문에서 발췌한 내용이다:\n\n{context}\n\n이제 아래 복합 질문에 단계적으로 답변하라:\n{request.prompt}"""

        # LLM 호출
        llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
        response = llm.invoke([HumanMessage(content=full_prompt)]).content
        
        return AnswerResponse(prompt=request.prompt, response=response)

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"응답 생성 중 오류 발생: {str(e)}")