Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- typescript
- 프로그래머스
- Two Pointer
- BFS
- 알고리즘
- AI
- turbo
- dfs
- 스택/큐
- javascirpt
- VectoreStore
- 코딩테스트
- LLM
- React
- modbus
- OpenAI
- heapq
- DP
- 파이썬
- 완전탐색
- monorepo
- Algorithm
- frontend
- javascript
- retriever
- queue
- python
- backend
- InfluxDB
- rag
Archives
- Today
- Total
DM Log
[AI 프로젝트 #1] PDF Q&A #2: LangChain + MCP / FastAPI 기반 백엔드 서버 구현 (OpenAPI) 본문
PJT/AI PJT
[AI 프로젝트 #1] PDF Q&A #2: LangChain + MCP / FastAPI 기반 백엔드 서버 구현 (OpenAPI)
Dev. Dong 2025. 10. 19. 20:45서론
- PDF Q&A 서비스의 백엔드 핵심 구조를 구현
- LangChain을 이용해 RAG(Retrieval-Augmented Generation) 을 구성하고, IDE용 MCP 서버와 웹용 FastAPI 서버 두 가지 형태로 동시에 제공
즉, IDE(CURSOR, Claude 등)에서도, React 프론트엔드에서도 동일한 모델과 벡터스토어를 사용하는 통합 AI 백엔드 구조 설계
벡엔드 디렉토리 구조
backend/
└── pdf_server/
├── mcp_server.py # MCP 프로토콜 서버 (IDE용)
├── api_server.py # FastAPI REST 서버 (웹용)
└── app.py # 두 서버 동시 실행 (thread 병렬 실행)
환경 설정
필수 패키지 설치
cd backend
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux
pip install fastapi uvicorn langchain langchain-openai langchain-community chromadb pypdf mcp python-dotenv
OpenAPI API 키 설정
- backend/.env 파일 생성 (환경변수 자동 로드)
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
MCP 서버 (mcp_server.py)
- IDE(CURSOR, Claude 등) 내에서 직접 호출 가능한 MCP Protocol 서버
import os
import logging
from mcp.server.fastmcp import FastMCP
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from dotenv import load_dotenv
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
logging.basicConfig(level=logging.INFO)
mcp = FastMCP("PDF-RAG")
PDF_PATH = "./data/sample.pdf"
qa_chain = None
if os.path.exists(PDF_PATH):
loader = PyPDFLoader(PDF_PATH)
pages = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
docs = splitter.split_documents(pages)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small", api_key=OPENAI_API_KEY)
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key=OPENAI_API_KEY)
vectorstore = Chroma.from_documents(docs, embeddings)
qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=vectorstore.as_retriever())
@mcp.tool()
def ask_pdf(query: str) -> str:
"""PDF 내용을 기반으로 질문에 답변합니다."""
if qa_chain is None:
return "PDF가 로드되지 않았습니다."
return qa_chain.run(query)
def run_mcp():
mcp.run(transport="stdio")
Fast API 서버 (api_server.py)
- React 프론트엔드가 axios로 호출하는 REST API 서
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.middleware.cores import CORSMiddleware
from lanchain_community.vectorstores import Chroma
from lanchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
import os
from dotenv import load_dotenv
# .env 로드
load_dotenv()
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
app = FastAPI()
# CORS 허용 (React 서버와 통신)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
# 글로벌 변수 (PDF 내용 저장)
vectorstore = None
@app.post('/upload')
async def upload_pdf(file: UploadFile = File(...)):
global vectorstore
file_path = f"./data/{file.filename}"
os.makedirs("./data", exist_ok=True)
with open(file_path, "wb") as f:
f.write(await file.read())
loader = PyPDFLoader(file_path)
pages = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
docs = splitter.split_documents(pages)
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(docs, embeddings)
return {"status": "ok", "filename": file.filename}
@app.post("/ask")
async def ask_question(question: str = Form(...)):
if vectorstore in None:
return {"error": "먼저 PDF를 업로드해주세요"}
# OPENAI_API_KEY 명칭 사용시 명시 안해도 자동 적용 (api_key 인자 전달 필요)
# 예시 api_key=os.getenv("OPENAI_API_KEY") or "fallback-key"
llm = ChatOpenAI(model="gpt-4o")
qa_chain = RetrievalQA.from_chain_type(
llm=llm, retriever=vectorstore.as_retriever()
)
answer = qa_chain.run(question)
return {"answer": answer}
def run_fastapi():
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=5000)
두 서버 동시 실행 (app.py)
import threading
from mcp_server import run_mcp
from api_server import run_fastapi
if __name__ == "__main__":
# FastAPI 서버를 별도 스레드에서 실행
api_thread = threading.Thread(target=run_fastapi)
api_thread.start()
# MCP 서버 실행
run_mcp()
실행 방법
cd backend/pdf_server
venv\Scripts\activate
python app.py
저비용 운영 전략
- 아래의 조합을 통해 OpenAI RAG 구조의 최소 비용 실행 셋업으로, 월 수 달러 수준의 요금으로도 개발·운영이 가능
항목 | 설정 |
임베딩 모델 | text-embedding-3-small |
LLM 모델 | gpt-3.5-turbo |
PDF 분할 | chunk_size = 500, overlap = 50 |
비용 절감 팁 | 임베딩 크기 축소 / 캐시된 Chroma Vectorstore 재사용 / 필요 시 로컬 오픈웨이트 모델(GPT-OSS 20B) 대체 |
- 2025년 8월 기준 Open AI에서 오픈웨이트 모델을 제공, API 비용없이 자체 RAG 서버를 구축 가능 단, GPU 자원 필요
'PJT > AI PJT' 카테고리의 다른 글
[AI 프로젝트 #1] PDF Q&A #3: React + Vite 프론트엔드 UI 구축 (0) | 2025.10.19 |
---|---|
[AI 프로젝트 #1] PDF Q&A #1: 프로젝트 개요와 서비스 구조 (0) | 2025.10.15 |
[AI 프로젝트 #0] 초기 세팅 #4: 공용 패키지(UI, 스타일, 유틸) 구조 설계 (0) | 2025.10.13 |
[AI 프로젝트 #0] 초기 세팅 #3: Turborepo 실행 환경 구성 방법 (0) | 2025.10.13 |
[AI 프로젝트 #0] 초기 세팅 #2: Node, Python, Vite 기반 모노레포 환경 구축 및 실행 (0) | 2025.09.27 |