Upload 8 files
Browse files- .gitignore +1 -0
- DEPLOYMENT_GUIDE.md +116 -0
- Dockerfile +19 -0
- README.md +112 -0
- README_HF.md +8 -0
- app.py +207 -0
- requirements.txt +7 -0
- test_api.py +57 -0
.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
venv/
|
DEPLOYMENT_GUIDE.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces Deployment Guide
|
| 2 |
+
|
| 3 |
+
## Steps to Deploy on Hugging Face Spaces
|
| 4 |
+
|
| 5 |
+
### 1. Create a New Space
|
| 6 |
+
1. Go to [Hugging Face Spaces](https://huggingface.co/new-space)
|
| 7 |
+
2. Choose a name for your space (e.g., `content-classifier`)
|
| 8 |
+
3. Select **Docker** as the SDK
|
| 9 |
+
4. Set the space to **Public** or **Private** as needed
|
| 10 |
+
5. Click **Create Space**
|
| 11 |
+
|
| 12 |
+
### 2. Upload Files to Your Space
|
| 13 |
+
|
| 14 |
+
You need to upload these files to your Space repository:
|
| 15 |
+
|
| 16 |
+
```
|
| 17 |
+
contextClassifier.onnx # Your ONNX model
|
| 18 |
+
app.py # FastAPI application
|
| 19 |
+
requirements.txt # Python dependencies
|
| 20 |
+
Dockerfile # Docker configuration
|
| 21 |
+
README.md # This will become the Space's README
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### 3. Required Files Content
|
| 25 |
+
|
| 26 |
+
**For the Space's README.md header, add this at the top:**
|
| 27 |
+
```yaml
|
| 28 |
+
---
|
| 29 |
+
title: Content Classifier
|
| 30 |
+
emoji: 🔍
|
| 31 |
+
colorFrom: blue
|
| 32 |
+
colorTo: purple
|
| 33 |
+
sdk: docker
|
| 34 |
+
pinned: false
|
| 35 |
+
license: mit
|
| 36 |
+
app_port: 7860
|
| 37 |
+
---
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### 4. Deployment Process
|
| 41 |
+
|
| 42 |
+
1. **Via Git (Recommended):**
|
| 43 |
+
```bash
|
| 44 |
+
git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
|
| 45 |
+
cd YOUR_SPACE_NAME
|
| 46 |
+
|
| 47 |
+
# Copy your files
|
| 48 |
+
copy contextClassifier.onnx .
|
| 49 |
+
copy app.py .
|
| 50 |
+
copy requirements.txt .
|
| 51 |
+
copy Dockerfile .
|
| 52 |
+
|
| 53 |
+
# Commit and push
|
| 54 |
+
git add .
|
| 55 |
+
git commit -m "Add content classifier API"
|
| 56 |
+
git push
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
2. **Via Web Interface:**
|
| 60 |
+
- Use the **Files** tab in your Space
|
| 61 |
+
- Upload each file individually
|
| 62 |
+
- Or drag and drop all files at once
|
| 63 |
+
|
| 64 |
+
### 5. Monitor Deployment
|
| 65 |
+
|
| 66 |
+
1. Go to your Space URL: `https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME`
|
| 67 |
+
2. Check the **Logs** tab to monitor the build process
|
| 68 |
+
3. The Space will show "Building" status during deployment
|
| 69 |
+
4. Once ready, you'll see the API documentation interface
|
| 70 |
+
|
| 71 |
+
### 6. Access Your API
|
| 72 |
+
|
| 73 |
+
Once deployed, your API will be available at:
|
| 74 |
+
- **Swagger UI:** `https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/docs`
|
| 75 |
+
- **API Endpoints:**
|
| 76 |
+
- `POST /predict` - Main prediction endpoint
|
| 77 |
+
- `GET /health` - Health check
|
| 78 |
+
- `GET /model-info` - Model information
|
| 79 |
+
|
| 80 |
+
### 7. Example Usage
|
| 81 |
+
|
| 82 |
+
```python
|
| 83 |
+
import requests
|
| 84 |
+
|
| 85 |
+
# Replace with your actual Space URL
|
| 86 |
+
api_url = "https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space"
|
| 87 |
+
|
| 88 |
+
# Make a prediction
|
| 89 |
+
response = requests.post(
|
| 90 |
+
f"{api_url}/predict",
|
| 91 |
+
json={"text": "This is a test message"}
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
print(response.json())
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### 8. Important Notes
|
| 98 |
+
|
| 99 |
+
- **Model Size:** Make sure your `contextClassifier.onnx` file is under the Space's size limit
|
| 100 |
+
- **Cold Start:** The first request might take longer as the Space wakes up
|
| 101 |
+
- **Logs:** Monitor the logs for any runtime errors
|
| 102 |
+
- **Updates:** Any push to the repository will trigger a rebuild
|
| 103 |
+
|
| 104 |
+
### 9. Troubleshooting
|
| 105 |
+
|
| 106 |
+
**Common Issues:**
|
| 107 |
+
- **Build Fails:** Check logs for dependency issues
|
| 108 |
+
- **Model Not Found:** Ensure `contextClassifier.onnx` is in the root directory
|
| 109 |
+
- **Port Issues:** Make sure the app uses port 7860
|
| 110 |
+
- **Memory Issues:** Large models might exceed memory limits
|
| 111 |
+
|
| 112 |
+
**Solutions:**
|
| 113 |
+
- Review requirements.txt for compatible versions
|
| 114 |
+
- Check model file path in app.py
|
| 115 |
+
- Verify Dockerfile exposes port 7860
|
| 116 |
+
- Consider model optimization for deployment
|
Dockerfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Copy requirements first for better caching
|
| 6 |
+
COPY requirements.txt .
|
| 7 |
+
|
| 8 |
+
# Install dependencies
|
| 9 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 10 |
+
|
| 11 |
+
# Copy application files
|
| 12 |
+
COPY app.py .
|
| 13 |
+
COPY contextClassifier.onnx .
|
| 14 |
+
|
| 15 |
+
# Expose port
|
| 16 |
+
EXPOSE 7860
|
| 17 |
+
|
| 18 |
+
# Run the application
|
| 19 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,3 +1,115 @@
|
|
| 1 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
license: mit
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Content Classifier
|
| 3 |
+
emoji: 🔍
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
license: mit
|
| 9 |
+
app_port: 7860
|
| 10 |
---
|
| 11 |
+
|
| 12 |
+
# Content Classifier API
|
| 13 |
+
|
| 14 |
+
A FastAPI-based content classification service using an ONNX model for threat detection and sentiment analysis.
|
| 15 |
+
|
| 16 |
+
## Features
|
| 17 |
+
|
| 18 |
+
- Content threat classification
|
| 19 |
+
- Sentiment analysis
|
| 20 |
+
- RESTful API with automatic documentation
|
| 21 |
+
- Health check endpoints
|
| 22 |
+
- Model information endpoints
|
| 23 |
+
- Docker support for easy deployment
|
| 24 |
+
|
| 25 |
+
## API Endpoints
|
| 26 |
+
|
| 27 |
+
- `POST /predict` - Classify text content
|
| 28 |
+
- `GET /` - API status
|
| 29 |
+
- `GET /health` - Health check
|
| 30 |
+
- `GET /model-info` - Model information
|
| 31 |
+
- `GET /docs` - Interactive API documentation (Swagger)
|
| 32 |
+
|
| 33 |
+
## Installation
|
| 34 |
+
|
| 35 |
+
1. Install dependencies:
|
| 36 |
+
```bash
|
| 37 |
+
pip install -r requirements.txt
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
2. Run the application:
|
| 41 |
+
```bash
|
| 42 |
+
python app.py
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
The API will be available at `http://localhost:8000`
|
| 46 |
+
|
| 47 |
+
## Usage
|
| 48 |
+
|
| 49 |
+
### Example Request
|
| 50 |
+
|
| 51 |
+
```bash
|
| 52 |
+
curl -X POST "http://localhost:8000/predict" \
|
| 53 |
+
-H "Content-Type: application/json" \
|
| 54 |
+
-d '{"text": "This is a sample text to classify"}'
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### Example Response
|
| 58 |
+
|
| 59 |
+
```json
|
| 60 |
+
{
|
| 61 |
+
"is_threat": false,
|
| 62 |
+
"final_confidence": 0.75,
|
| 63 |
+
"threat_prediction": 0.25,
|
| 64 |
+
"sentiment_analysis": {
|
| 65 |
+
"label": "POSITIVE",
|
| 66 |
+
"score": 0.5
|
| 67 |
+
},
|
| 68 |
+
"onnx_prediction": {
|
| 69 |
+
"threat_probability": 0.25,
|
| 70 |
+
"raw_output": [[0.75, 0.25]]
|
| 71 |
+
},
|
| 72 |
+
"models_used": ["contextClassifier.onnx"],
|
| 73 |
+
"raw_predictions": {
|
| 74 |
+
"onnx": {
|
| 75 |
+
"threat_probability": 0.25,
|
| 76 |
+
"raw_output": [[0.75, 0.25]]
|
| 77 |
+
},
|
| 78 |
+
"sentiment": {
|
| 79 |
+
"label": "POSITIVE",
|
| 80 |
+
"score": 0.5
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
## Docker Deployment
|
| 87 |
+
|
| 88 |
+
1. Build the Docker image:
|
| 89 |
+
```bash
|
| 90 |
+
docker build -t content-classifier .
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
2. Run the container:
|
| 94 |
+
```bash
|
| 95 |
+
docker run -p 8000:8000 content-classifier
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
## Hugging Face Spaces Deployment
|
| 99 |
+
|
| 100 |
+
To deploy on Hugging Face Spaces:
|
| 101 |
+
|
| 102 |
+
1. Create a new Space on Hugging Face
|
| 103 |
+
2. Upload all files to your Space repository
|
| 104 |
+
3. The Space will automatically build and deploy
|
| 105 |
+
|
| 106 |
+
## Model Requirements
|
| 107 |
+
|
| 108 |
+
The ONNX model should accept text inputs and return classification predictions. You may need to adjust the preprocessing and postprocessing functions in `app.py` based on your specific model requirements.
|
| 109 |
+
|
| 110 |
+
## Configuration
|
| 111 |
+
|
| 112 |
+
You can modify the following in `app.py`:
|
| 113 |
+
- `MODEL_PATH`: Path to your ONNX model file
|
| 114 |
+
- `max_length`: Maximum text length for processing
|
| 115 |
+
- Preprocessing and postprocessing logic based on your model's requirements
|
README_HF.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
title: Content Classifier
|
| 2 |
+
emoji: 🔍
|
| 3 |
+
colorFrom: blue
|
| 4 |
+
colorTo: purple
|
| 5 |
+
sdk: docker
|
| 6 |
+
pinned: false
|
| 7 |
+
license: mit
|
| 8 |
+
app_port: 7860
|
app.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import onnxruntime as ort
|
| 4 |
+
from fastapi import FastAPI, HTTPException
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
import uvicorn
|
| 7 |
+
import json
|
| 8 |
+
from typing import Dict, Any, List, Optional
|
| 9 |
+
|
| 10 |
+
app = FastAPI(title="Content Classifier API", description="Content classification using ONNX model")
|
| 11 |
+
|
| 12 |
+
# Model configuration
|
| 13 |
+
MODEL_PATH = "contextClassifier.onnx"
|
| 14 |
+
session = None
|
| 15 |
+
|
| 16 |
+
class TextInput(BaseModel):
|
| 17 |
+
text: str
|
| 18 |
+
max_length: Optional[int] = 512
|
| 19 |
+
|
| 20 |
+
class PredictionResponse(BaseModel):
|
| 21 |
+
is_threat: bool
|
| 22 |
+
final_confidence: float
|
| 23 |
+
threat_prediction: float
|
| 24 |
+
sentiment_analysis: Optional[Dict[str, Any]]
|
| 25 |
+
onnx_prediction: Optional[Dict[str, Any]]
|
| 26 |
+
models_used: List[str]
|
| 27 |
+
raw_predictions: Dict[str, Any]
|
| 28 |
+
|
| 29 |
+
def load_model():
|
| 30 |
+
"""Load the ONNX model"""
|
| 31 |
+
global session
|
| 32 |
+
try:
|
| 33 |
+
session = ort.InferenceSession(MODEL_PATH)
|
| 34 |
+
print(f"Model loaded successfully from {MODEL_PATH}")
|
| 35 |
+
print(f"Model inputs: {[input.name for input in session.get_inputs()]}")
|
| 36 |
+
print(f"Model outputs: {[output.name for output in session.get_outputs()]}")
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"Error loading model: {e}")
|
| 39 |
+
raise e
|
| 40 |
+
|
| 41 |
+
def preprocess_text(text: str, max_length: int = 512):
|
| 42 |
+
"""
|
| 43 |
+
Preprocess text for the model
|
| 44 |
+
This is a placeholder - you'll need to adjust this based on your model's requirements
|
| 45 |
+
"""
|
| 46 |
+
# This is a simple tokenization example
|
| 47 |
+
# You may need to use a specific tokenizer depending on your model
|
| 48 |
+
|
| 49 |
+
# Convert text to token IDs (this is just an example)
|
| 50 |
+
# You might need to use transformers tokenizer or similar
|
| 51 |
+
tokens = text.lower().split()[:max_length]
|
| 52 |
+
|
| 53 |
+
# Pad or truncate to fixed length
|
| 54 |
+
if len(tokens) < max_length:
|
| 55 |
+
tokens.extend(['[PAD]'] * (max_length - len(tokens)))
|
| 56 |
+
|
| 57 |
+
# Convert to input format expected by your model
|
| 58 |
+
# This is a placeholder - adjust based on your model's input requirements
|
| 59 |
+
input_ids = np.array([hash(token) % 30000 for token in tokens], dtype=np.int64).reshape(1, -1)
|
| 60 |
+
attention_mask = np.array([1 if token != '[PAD]' else 0 for token in tokens], dtype=np.int64).reshape(1, -1)
|
| 61 |
+
|
| 62 |
+
return {
|
| 63 |
+
"input_ids": input_ids,
|
| 64 |
+
"attention_mask": attention_mask
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
def postprocess_predictions(outputs, predictions_dict):
|
| 68 |
+
"""
|
| 69 |
+
Process model outputs into the expected format
|
| 70 |
+
Adjust this based on your model's actual outputs
|
| 71 |
+
"""
|
| 72 |
+
# This is a placeholder implementation
|
| 73 |
+
# Adjust based on your actual model outputs
|
| 74 |
+
|
| 75 |
+
# Assuming the model outputs probabilities or logits
|
| 76 |
+
if len(outputs) > 0:
|
| 77 |
+
raw_output = outputs[0]
|
| 78 |
+
|
| 79 |
+
# Calculate threat prediction (adjust logic as needed)
|
| 80 |
+
threat_prediction = float(raw_output[0][1]) if len(raw_output[0]) > 1 else 0.5
|
| 81 |
+
final_confidence = abs(threat_prediction - 0.5) * 2 # Scale to 0-1
|
| 82 |
+
is_threat = threat_prediction > 0.5
|
| 83 |
+
|
| 84 |
+
predictions_dict.update({
|
| 85 |
+
"onnx": {
|
| 86 |
+
"threat_probability": threat_prediction,
|
| 87 |
+
"raw_output": raw_output.tolist()
|
| 88 |
+
}
|
| 89 |
+
})
|
| 90 |
+
|
| 91 |
+
# Mock sentiment analysis (replace with actual logic if available)
|
| 92 |
+
sentiment_score = (threat_prediction - 0.5) * -2 # Inverse relationship
|
| 93 |
+
predictions_dict["sentiment"] = {
|
| 94 |
+
"label": "NEGATIVE" if sentiment_score < 0 else "POSITIVE",
|
| 95 |
+
"score": abs(sentiment_score)
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
models_used = ["contextClassifier.onnx"]
|
| 99 |
+
|
| 100 |
+
return {
|
| 101 |
+
"is_threat": is_threat,
|
| 102 |
+
"final_confidence": final_confidence,
|
| 103 |
+
"threat_prediction": threat_prediction,
|
| 104 |
+
"sentiment_analysis": predictions_dict.get("sentiment"),
|
| 105 |
+
"onnx_prediction": predictions_dict.get("onnx"),
|
| 106 |
+
"models_used": models_used,
|
| 107 |
+
"raw_predictions": predictions_dict
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
# Fallback response
|
| 111 |
+
return {
|
| 112 |
+
"is_threat": False,
|
| 113 |
+
"final_confidence": 0.0,
|
| 114 |
+
"threat_prediction": 0.0,
|
| 115 |
+
"sentiment_analysis": None,
|
| 116 |
+
"onnx_prediction": None,
|
| 117 |
+
"models_used": [],
|
| 118 |
+
"raw_predictions": predictions_dict
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
@app.on_event("startup")
|
| 122 |
+
async def startup_event():
|
| 123 |
+
"""Load model on startup"""
|
| 124 |
+
load_model()
|
| 125 |
+
|
| 126 |
+
@app.get("/")
|
| 127 |
+
async def root():
|
| 128 |
+
return {"message": "Content Classifier API is running", "model": MODEL_PATH}
|
| 129 |
+
|
| 130 |
+
@app.post("/predict", response_model=PredictionResponse)
|
| 131 |
+
async def predict(input_data: TextInput):
|
| 132 |
+
"""
|
| 133 |
+
Predict content classification for the given text
|
| 134 |
+
"""
|
| 135 |
+
if session is None:
|
| 136 |
+
raise HTTPException(status_code=500, detail="Model not loaded")
|
| 137 |
+
|
| 138 |
+
try:
|
| 139 |
+
# Preprocess the text
|
| 140 |
+
model_inputs = preprocess_text(input_data.text, input_data.max_length)
|
| 141 |
+
|
| 142 |
+
# Get input names from the model
|
| 143 |
+
input_names = [input.name for input in session.get_inputs()]
|
| 144 |
+
|
| 145 |
+
# Prepare inputs for ONNX Runtime
|
| 146 |
+
ort_inputs = {}
|
| 147 |
+
for name in input_names:
|
| 148 |
+
if name in model_inputs:
|
| 149 |
+
ort_inputs[name] = model_inputs[name]
|
| 150 |
+
else:
|
| 151 |
+
# Handle case where expected input is not provided
|
| 152 |
+
print(f"Warning: Expected input '{name}' not found in processed inputs")
|
| 153 |
+
|
| 154 |
+
# Run inference
|
| 155 |
+
outputs = session.run(None, ort_inputs)
|
| 156 |
+
|
| 157 |
+
# Initialize predictions dictionary
|
| 158 |
+
predictions = {}
|
| 159 |
+
|
| 160 |
+
# Process outputs
|
| 161 |
+
result = postprocess_predictions(outputs, predictions)
|
| 162 |
+
|
| 163 |
+
return result
|
| 164 |
+
|
| 165 |
+
except Exception as e:
|
| 166 |
+
print(f"Prediction error: {e}")
|
| 167 |
+
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")
|
| 168 |
+
|
| 169 |
+
@app.get("/health")
|
| 170 |
+
async def health_check():
|
| 171 |
+
"""Health check endpoint"""
|
| 172 |
+
return {
|
| 173 |
+
"status": "healthy",
|
| 174 |
+
"model_loaded": session is not None,
|
| 175 |
+
"model_path": MODEL_PATH
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
@app.get("/model-info")
|
| 179 |
+
async def model_info():
|
| 180 |
+
"""Get model information"""
|
| 181 |
+
if session is None:
|
| 182 |
+
raise HTTPException(status_code=500, detail="Model not loaded")
|
| 183 |
+
|
| 184 |
+
inputs = []
|
| 185 |
+
for input_meta in session.get_inputs():
|
| 186 |
+
inputs.append({
|
| 187 |
+
"name": input_meta.name,
|
| 188 |
+
"type": str(input_meta.type),
|
| 189 |
+
"shape": input_meta.shape
|
| 190 |
+
})
|
| 191 |
+
|
| 192 |
+
outputs = []
|
| 193 |
+
for output_meta in session.get_outputs():
|
| 194 |
+
outputs.append({
|
| 195 |
+
"name": output_meta.name,
|
| 196 |
+
"type": str(output_meta.type),
|
| 197 |
+
"shape": output_meta.shape
|
| 198 |
+
})
|
| 199 |
+
|
| 200 |
+
return {
|
| 201 |
+
"model_path": MODEL_PATH,
|
| 202 |
+
"inputs": inputs,
|
| 203 |
+
"outputs": outputs
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
if __name__ == "__main__":
|
| 207 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn==0.24.0
|
| 3 |
+
onnxruntime==1.16.3
|
| 4 |
+
numpy==1.24.3
|
| 5 |
+
pydantic==2.5.0
|
| 6 |
+
python-multipart==0.0.6
|
| 7 |
+
requests==2.31.0
|
test_api.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
# Test the API
|
| 5 |
+
base_url = "http://localhost:7860"
|
| 6 |
+
|
| 7 |
+
def test_api():
|
| 8 |
+
# Test root endpoint
|
| 9 |
+
print("Testing root endpoint...")
|
| 10 |
+
try:
|
| 11 |
+
response = requests.get(f"{base_url}/")
|
| 12 |
+
print(f"Root: {response.status_code} - {response.json()}")
|
| 13 |
+
except Exception as e:
|
| 14 |
+
print(f"Root endpoint error: {e}")
|
| 15 |
+
|
| 16 |
+
# Test health endpoint
|
| 17 |
+
print("\nTesting health endpoint...")
|
| 18 |
+
try:
|
| 19 |
+
response = requests.get(f"{base_url}/health")
|
| 20 |
+
print(f"Health: {response.status_code} - {response.json()}")
|
| 21 |
+
except Exception as e:
|
| 22 |
+
print(f"Health endpoint error: {e}")
|
| 23 |
+
|
| 24 |
+
# Test model info endpoint
|
| 25 |
+
print("\nTesting model info endpoint...")
|
| 26 |
+
try:
|
| 27 |
+
response = requests.get(f"{base_url}/model-info")
|
| 28 |
+
print(f"Model Info: {response.status_code} - {response.json()}")
|
| 29 |
+
except Exception as e:
|
| 30 |
+
print(f"Model info endpoint error: {e}")
|
| 31 |
+
|
| 32 |
+
# Test prediction endpoint
|
| 33 |
+
print("\nTesting prediction endpoint...")
|
| 34 |
+
test_texts = [
|
| 35 |
+
"This is a normal, safe message.",
|
| 36 |
+
"I will harm you and your family!",
|
| 37 |
+
"Hello, how are you doing today?",
|
| 38 |
+
"This product is amazing, I love it!"
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
for text in test_texts:
|
| 42 |
+
try:
|
| 43 |
+
payload = {"text": text}
|
| 44 |
+
response = requests.post(f"{base_url}/predict", json=payload)
|
| 45 |
+
if response.status_code == 200:
|
| 46 |
+
result = response.json()
|
| 47 |
+
print(f"\nText: '{text}'")
|
| 48 |
+
print(f"Is Threat: {result['is_threat']}")
|
| 49 |
+
print(f"Confidence: {result['final_confidence']:.3f}")
|
| 50 |
+
print(f"Threat Prediction: {result['threat_prediction']:.3f}")
|
| 51 |
+
else:
|
| 52 |
+
print(f"Error {response.status_code}: {response.text}")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f"Prediction error for '{text}': {e}")
|
| 55 |
+
|
| 56 |
+
if __name__ == "__main__":
|
| 57 |
+
test_api()
|