Spaces:
Configuration error
Configuration error
| import time, os | |
| from contextlib import asynccontextmanager | |
| from fastapi import FastAPI, Request, HTTPException, Depends | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| import redis.asyncio as aioredis | |
| from dotenv import load_dotenv | |
| from model.inference import AdversarialShieldClassifier | |
| from api.schemas import ( | |
| ClassifyRequest, ClassifyResponse, | |
| BatchClassifyRequest, BatchClassifyResponse, HealthResponse, | |
| ) | |
| load_dotenv() | |
| MODEL_PATH = os.getenv("CHECKPOINT_PATH", "./model/checkpoints/best_model") | |
| THRESHOLD = float(os.getenv("CONFIDENCE_THRESHOLD", "0.75")) | |
| REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379") | |
| RATE_LIMIT = int(os.getenv("RATE_LIMIT_RPM", "60")) | |
| # ββ Global state βββββββββββββββββββββββββββββββββββββββββββββββ | |
| _classifier: AdversarialShieldClassifier | None = None | |
| _redis: aioredis.Redis | None = None | |
| async def lifespan(app: FastAPI): | |
| global _classifier, _redis | |
| # Startup | |
| _classifier = AdversarialShieldClassifier(MODEL_PATH, threshold=THRESHOLD) | |
| _redis = aioredis.from_url(REDIS_URL, decode_responses=True) | |
| yield | |
| # Shutdown | |
| await _redis.close() | |
| app = FastAPI( | |
| title="AdversarialShield", | |
| description="Real-time LLM adversarial prompt detection API", | |
| version="1.0.0", | |
| lifespan=lifespan, | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ββ Rate limiter dependency βββββββββββββββββββββββββββββββββββββ | |
| async def rate_limit(request: Request): | |
| if _redis is None: | |
| return | |
| client_ip = request.client.host | |
| key = f"rl:{client_ip}" | |
| count = await _redis.incr(key) | |
| if count == 1: | |
| await _redis.expire(key, 60) # 60-second window | |
| if count > RATE_LIMIT: | |
| raise HTTPException(status_code=429, detail="Rate limit exceeded. Max 60 req/min.") | |
| # ββ Endpoints ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def health(): | |
| import torch | |
| return HealthResponse( | |
| status="ok", | |
| model=MODEL_PATH, | |
| device="cuda" if torch.cuda.is_available() else "cpu", | |
| ) | |
| async def classify( | |
| body: ClassifyRequest, | |
| _: None = Depends(rate_limit), | |
| ): | |
| if _classifier is None: | |
| raise HTTPException(503, "Classifier not initialized") | |
| if body.threshold: | |
| _classifier.threshold = body.threshold | |
| result = _classifier.classify(body.text) | |
| return result | |
| async def batch_classify( | |
| body: BatchClassifyRequest, | |
| _: None = Depends(rate_limit), | |
| ): | |
| if _classifier is None: | |
| raise HTTPException(503, "Classifier not initialized") | |
| t0 = time.perf_counter() | |
| results = _classifier.batch_classify(body.texts) | |
| total_ms = round((time.perf_counter() - t0) * 1000, 2) | |
| return BatchClassifyResponse( | |
| results=results, | |
| total=len(results), | |
| threats_found=sum(1 for r in results if r.is_threat), | |
| total_ms=total_ms, | |
| ) | |
| # Run: uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload |