sentiment-api / app.py
Rameen191's picture
Upload 3 files
97ed999 verified
Raw
History Blame Contribute Delete
4.26 kB
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from transformers import pipeline
from typing import Dict, List
from contextlib import asynccontextmanager
import torch
import logging
import time
from datetime import datetime, timezone
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("sentiment-api")
MODEL_ID = "Rameen191/sentiment-tutorial" # <-- apna HF username dalen
ml_models: Dict[str, object] = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info(f"Loading model: {MODEL_ID} ...")
try:
ml_models["classifier"] = pipeline(
"sentiment-analysis",
model=MODEL_ID,
device=0 if torch.cuda.is_available() else -1,
)
logger.info("✅ Model loaded successfully.")
except Exception as e:
logger.error(f"❌ Failed to load model: {e}")
ml_models["classifier"] = None
yield
ml_models.clear()
app = FastAPI(
title="Sentiment Analysis API",
description="A DistilBERT-based sentiment classifier.",
version="1.0.0",
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class TextRequest(BaseModel):
text: str = Field(..., min_length=1, max_length=5000)
class BatchRequest(BaseModel):
texts: List[str] = Field(..., min_length=1, max_length=50)
class SentimentResponse(BaseModel):
text: str
label: str
confidence: float
probabilities: Dict[str, float]
timestamp: str
@app.middleware("http")
async def log_requests(request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
logger.info(f'{request.method} {request.url.path} -> {response.status_code} ({duration:.3f}s)')
return response
@app.get("/")
async def root():
return {"message": "Sentiment Analysis API", "docs": "/docs", "health": "/health"}
@app.get("/health")
async def health_check():
return {
"status": "healthy" if ml_models.get("classifier") is not None else "degraded",
"model_loaded": ml_models.get("classifier") is not None,
"model_id": MODEL_ID,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
@app.post("/predict", response_model=SentimentResponse)
async def predict_sentiment(request: TextRequest):
classifier = ml_models.get("classifier")
if classifier is None:
raise HTTPException(status_code=503, detail="Model not loaded.")
try:
result = classifier(request.text)[0]
probs = {result['label']: round(result['score'], 4)}
other_label = 'NEGATIVE' if result['label'] == 'POSITIVE' else 'POSITIVE'
probs[other_label] = round(1 - result['score'], 4)
return SentimentResponse(
text=request.text[:200],
label=result['label'],
confidence=round(result['score'], 4),
probabilities=probs,
timestamp=datetime.now(timezone.utc).isoformat(),
)
except Exception as e:
logger.error(f"Prediction error: {e}")
raise HTTPException(status_code=500, detail="Internal error during prediction.")
@app.post("/predict/batch")
async def predict_batch(request: BatchRequest):
classifier = ml_models.get("classifier")
if classifier is None:
raise HTTPException(status_code=503, detail="Model not loaded.")
try:
results = classifier(request.texts)
return {
"results": [
{"text": text[:100], "label": r['label'], "confidence": round(r['score'], 4)}
for text, r in zip(request.texts, results)
],
"total": len(results),
}
except Exception as e:
logger.error(f"Batch prediction error: {e}")
raise HTTPException(status_code=500, detail="Internal error during batch prediction.")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)