| |
| from fastapi import FastAPI, HTTPException, Request, Depends, Security |
| from fastapi.security import APIKeyHeader |
| from fastapi.responses import JSONResponse |
| from pydantic import BaseModel, Field |
| from typing import Optional |
| import uvicorn |
| import time |
| import os |
| from moderator import get_moderator |
|
|
| |
| app = FastAPI( |
| title="Text Moderation API", |
| description="API for moderating user-generated text content", |
| version="1.0.0" |
| ) |
|
|
| |
| |
| |
|
|
| |
| API_KEY = os.getenv("MODERATION_API_KEY", "your-default-secret-key-here") |
| API_KEY_NAME = "X-API-Key" |
|
|
| |
| api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=True) |
|
|
| async def verify_api_key(api_key: str = Security(api_key_header)): |
| """Verify the API key from request header""" |
| if api_key != API_KEY: |
| raise HTTPException( |
| status_code=403, |
| detail="Invalid API Key. Please provide a valid X-API-Key header." |
| ) |
| return api_key |
|
|
| |
| |
| |
|
|
| class ModerationRequest(BaseModel): |
| user_id: str = Field(..., description="User identifier") |
| text: str = Field(..., description="Text content to moderate") |
| |
| class Config: |
| json_schema_extra = { |
| "example": { |
| "user_id": "user123", |
| "text": "I love this community!" |
| } |
| } |
|
|
| class ModerationResponse(BaseModel): |
| user_id: str |
| action: str = Field(..., description="Action: allow, flag, or delete") |
| reason: Optional[str] = Field(None, description="Reason for the action") |
| toxic_score: Optional[float] = Field(None, description="Toxicity score (0-1)") |
| hate_score: Optional[float] = Field(None, description="Hate speech score (0-1)") |
| processing_time_ms: Optional[float] = Field(None, description="Processing time in milliseconds") |
|
|
| |
| |
| |
|
|
| @app.on_event("startup") |
| async def startup_event(): |
| print("Initializing moderator...") |
| get_moderator() |
| print("Moderator ready!") |
| print(f"API Key authentication enabled (key length: {len(API_KEY)} characters)") |
|
|
| |
| |
| |
|
|
| @app.get("/") |
| async def root(): |
| return { |
| "message": "Text Moderation API", |
| "authentication": "Required - X-API-Key header", |
| "endpoints": { |
| "/moderate": "POST - Moderate text content (Protected)", |
| "/health": "GET - Health check (Public)" |
| } |
| } |
|
|
| @app.get("/health") |
| async def health_check(): |
| """Public health check endpoint - No API key required""" |
| try: |
| moderator = get_moderator() |
| return { |
| "status": "healthy", |
| "models_loaded": moderator.toxic_model is not None and moderator.hate_model is not None, |
| "device": moderator.device, |
| "auth_enabled": True |
| } |
| except Exception as e: |
| return JSONResponse( |
| status_code=503, |
| content={"status": "unhealthy", "error": str(e)} |
| ) |
|
|
| @app.post("/moderate", response_model=ModerationResponse) |
| async def moderate_text( |
| request: ModerationRequest, |
| api_key: str = Depends(verify_api_key) |
| ): |
| """ |
| Moderate text content and return action recommendation (Protected - Requires API Key) |
| |
| - **user_id**: Identifier for the user who posted the content |
| - **text**: The text content to moderate |
| - **X-API-Key**: API key in request header |
| """ |
| if not request.text or not request.text.strip(): |
| raise HTTPException(status_code=400, detail="Text cannot be empty") |
| |
| if len(request.text) > 2000: |
| raise HTTPException(status_code=400, detail="Text too long (max 2000 characters)") |
| |
| try: |
| start_time = time.time() |
| |
| moderator = get_moderator() |
| result = moderator.moderate(request.text) |
| |
| processing_time = (time.time() - start_time) * 1000 |
| |
| return ModerationResponse( |
| user_id=request.user_id, |
| action=result["action"], |
| reason=result.get("reason"), |
| toxic_score=result.get("toxic_score"), |
| hate_score=result.get("hate_score"), |
| processing_time_ms=round(processing_time, 2) |
| ) |
| |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=f"Moderation error: {str(e)}") |
|
|
| @app.post("/moderate/batch") |
| async def moderate_batch( |
| requests: list[ModerationRequest], |
| api_key: str = Depends(verify_api_key) |
| ): |
| """Moderate multiple texts in batch (Protected - Requires API Key)""" |
| if len(requests) > 100: |
| raise HTTPException(status_code=400, detail="Batch size too large (max 100)") |
| |
| moderator = get_moderator() |
| responses = [] |
| |
| for req in requests: |
| try: |
| result = moderator.moderate(req.text) |
| responses.append({ |
| "user_id": req.user_id, |
| "action": result["action"], |
| "reason": result.get("reason"), |
| "toxic_score": result.get("toxic_score"), |
| "hate_score": result.get("hate_score") |
| }) |
| except Exception as e: |
| responses.append({ |
| "user_id": req.user_id, |
| "action": "error", |
| "reason": str(e) |
| }) |
| |
| return {"results": responses} |
|
|
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |