Spaces:
Sleeping
Sleeping
Upload 9 files
Browse files- .gitattributes +1 -35
- .gitignore +25 -0
- DEPLOY.md +122 -0
- Dockerfile +34 -0
- README.md +132 -10
- app.py +270 -0
- contextClassifier.onnx +3 -0
- requirements.txt +6 -0
- test_api.py +139 -0
.gitattributes
CHANGED
|
@@ -1,35 +1 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
pip-log.txt
|
| 9 |
+
pip-delete-this-directory.txt
|
| 10 |
+
.tox/
|
| 11 |
+
.coverage
|
| 12 |
+
.coverage.*
|
| 13 |
+
.pytest_cache/
|
| 14 |
+
.DS_Store
|
| 15 |
+
.vscode/settings.json
|
| 16 |
+
*.egg-info/
|
| 17 |
+
dist/
|
| 18 |
+
build/
|
| 19 |
+
.idea/
|
| 20 |
+
.env
|
| 21 |
+
.env.local
|
| 22 |
+
.env.*.local
|
| 23 |
+
npm-debug.log*
|
| 24 |
+
yarn-debug.log*
|
| 25 |
+
yarn-error.log*
|
DEPLOY.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π Hugging Face Spaces Deployment Guide
|
| 2 |
+
|
| 3 |
+
## Quick Deploy Steps
|
| 4 |
+
|
| 5 |
+
### 1. Create Your Space
|
| 6 |
+
1. Go to [huggingface.co/new-space](https://huggingface.co/new-space)
|
| 7 |
+
2. Name: `content-classifier` (or your preferred name)
|
| 8 |
+
3. SDK: **Docker**
|
| 9 |
+
4. Visibility: Public/Private (your choice)
|
| 10 |
+
5. Click **Create Space**
|
| 11 |
+
|
| 12 |
+
### 2. Upload Files
|
| 13 |
+
Upload these files to your Space:
|
| 14 |
+
|
| 15 |
+
**Required Files:**
|
| 16 |
+
- `contextClassifier.onnx` (your model file)
|
| 17 |
+
- `app.py`
|
| 18 |
+
- `requirements.txt`
|
| 19 |
+
- `Dockerfile`
|
| 20 |
+
- `README.md`
|
| 21 |
+
|
| 22 |
+
**Optional Files:**
|
| 23 |
+
- `test_api.py` (for testing)
|
| 24 |
+
|
| 25 |
+
### 3. Model File
|
| 26 |
+
β οΈ **Important**: Make sure your `contextClassifier.onnx` file is in the same directory as these files before uploading.
|
| 27 |
+
|
| 28 |
+
### 4. Git Method (Recommended)
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
# Clone your space
|
| 32 |
+
git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
|
| 33 |
+
cd YOUR_SPACE_NAME
|
| 34 |
+
|
| 35 |
+
# Copy your model file
|
| 36 |
+
copy path\to\your\contextClassifier.onnx .
|
| 37 |
+
|
| 38 |
+
# Copy all project files
|
| 39 |
+
copy app.py .
|
| 40 |
+
copy requirements.txt .
|
| 41 |
+
copy Dockerfile .
|
| 42 |
+
copy README.md .
|
| 43 |
+
|
| 44 |
+
# Add and commit
|
| 45 |
+
git add .
|
| 46 |
+
git commit -m "π Add content classifier ONNX model"
|
| 47 |
+
git push
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### 5. Monitor Deployment
|
| 51 |
+
|
| 52 |
+
1. **Check Build Logs**: Go to your Space > Logs tab
|
| 53 |
+
2. **Wait for Build**: Usually takes 2-3 minutes
|
| 54 |
+
3. **Check Status**: Space will show "Building" β "Running"
|
| 55 |
+
|
| 56 |
+
### 6. Test Your Space
|
| 57 |
+
|
| 58 |
+
Once deployed, your API will be available at:
|
| 59 |
+
```
|
| 60 |
+
https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
**API Endpoints:**
|
| 64 |
+
- `/docs` - Interactive documentation
|
| 65 |
+
- `/predict` - Main prediction endpoint
|
| 66 |
+
- `/health` - Health check
|
| 67 |
+
- `/model-info` - Model information
|
| 68 |
+
|
| 69 |
+
### 7. Example Usage
|
| 70 |
+
|
| 71 |
+
```python
|
| 72 |
+
import requests
|
| 73 |
+
|
| 74 |
+
# Replace with your actual Space URL
|
| 75 |
+
api_url = "https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space"
|
| 76 |
+
|
| 77 |
+
response = requests.post(
|
| 78 |
+
f"{api_url}/predict",
|
| 79 |
+
json={"text": "This is a test message for classification"}
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
print(response.json())
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
## Troubleshooting
|
| 86 |
+
|
| 87 |
+
### Common Issues:
|
| 88 |
+
|
| 89 |
+
**Build Fails:**
|
| 90 |
+
- Check Logs tab for error details
|
| 91 |
+
- Verify all required files are uploaded
|
| 92 |
+
- Ensure `contextClassifier.onnx` is present
|
| 93 |
+
|
| 94 |
+
**Model Not Found:**
|
| 95 |
+
- Verify `contextClassifier.onnx` is in root directory
|
| 96 |
+
- Check file name matches exactly (case-sensitive)
|
| 97 |
+
|
| 98 |
+
**API Not Responding:**
|
| 99 |
+
- Check if Space is "Running" (not "Building")
|
| 100 |
+
- Try accessing `/health` endpoint first
|
| 101 |
+
- Check Logs for runtime errors
|
| 102 |
+
|
| 103 |
+
**Memory Issues:**
|
| 104 |
+
- ONNX model might be too large
|
| 105 |
+
- Consider model optimization
|
| 106 |
+
- Check Space hardware limits
|
| 107 |
+
|
| 108 |
+
### Success Indicators:
|
| 109 |
+
|
| 110 |
+
β
Space shows "Running" status
|
| 111 |
+
β
`/health` endpoint returns `{"status": "healthy"}`
|
| 112 |
+
β
`/docs` shows interactive API documentation
|
| 113 |
+
β
`/predict` accepts POST requests and returns expected format
|
| 114 |
+
|
| 115 |
+
## Next Steps
|
| 116 |
+
|
| 117 |
+
1. **Test thoroughly** with various text inputs
|
| 118 |
+
2. **Share your Space** with the community
|
| 119 |
+
3. **Monitor usage** in Space analytics
|
| 120 |
+
4. **Update model** by pushing new `contextClassifier.onnx`
|
| 121 |
+
|
| 122 |
+
Your Content Classifier is now live and ready to use! π
|
Dockerfile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
| 2 |
+
|
| 3 |
+
# Set working directory
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
gcc \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
# Copy requirements first for better caching
|
| 12 |
+
COPY requirements.txt .
|
| 13 |
+
|
| 14 |
+
# Install Python dependencies
|
| 15 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 16 |
+
pip install --no-cache-dir -r requirements.txt
|
| 17 |
+
|
| 18 |
+
# Copy application files
|
| 19 |
+
COPY . .
|
| 20 |
+
|
| 21 |
+
# Create non-root user for security
|
| 22 |
+
RUN useradd --create-home --shell /bin/bash app && \
|
| 23 |
+
chown -R app:app /app
|
| 24 |
+
USER app
|
| 25 |
+
|
| 26 |
+
# Expose port
|
| 27 |
+
EXPOSE 7860
|
| 28 |
+
|
| 29 |
+
# Health check
|
| 30 |
+
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
| 31 |
+
CMD python -c "import requests; requests.get('http://localhost:7860/health')"
|
| 32 |
+
|
| 33 |
+
# Run the application
|
| 34 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,10 +1,132 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 13 |
+
|
| 14 |
+
A powerful FastAPI-based content classification service using ONNX for threat detection and sentiment analysis.
|
| 15 |
+
|
| 16 |
+
## π Features
|
| 17 |
+
|
| 18 |
+
- **Threat Detection**: Classify content for potential threats
|
| 19 |
+
- **Sentiment Analysis**: Analyze text sentiment (positive/negative)
|
| 20 |
+
- **ONNX Runtime**: High-performance model inference
|
| 21 |
+
- **REST API**: Easy-to-use HTTP endpoints
|
| 22 |
+
- **Auto Documentation**: Interactive Swagger UI at `/docs`
|
| 23 |
+
- **Health Monitoring**: Built-in health checks
|
| 24 |
+
|
| 25 |
+
## π‘ API Endpoints
|
| 26 |
+
|
| 27 |
+
| Endpoint | Method | Description |
|
| 28 |
+
|----------|--------|-------------|
|
| 29 |
+
| `/predict` | POST | Classify text content |
|
| 30 |
+
| `/health` | GET | Check API health status |
|
| 31 |
+
| `/model-info` | GET | Get model information |
|
| 32 |
+
| `/docs` | GET | Interactive API documentation |
|
| 33 |
+
|
| 34 |
+
## π§ Usage
|
| 35 |
+
|
| 36 |
+
### Example Request
|
| 37 |
+
|
| 38 |
+
```bash
|
| 39 |
+
curl -X POST "https://YOUR-SPACE-NAME.hf.space/predict" \
|
| 40 |
+
-H "Content-Type: application/json" \
|
| 41 |
+
-d '{"text": "This is a sample text to classify"}'
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
### Example Response
|
| 45 |
+
|
| 46 |
+
```json
|
| 47 |
+
{
|
| 48 |
+
"is_threat": false,
|
| 49 |
+
"final_confidence": 0.75,
|
| 50 |
+
"threat_prediction": 0.25,
|
| 51 |
+
"sentiment_analysis": {
|
| 52 |
+
"label": "POSITIVE",
|
| 53 |
+
"score": 0.5
|
| 54 |
+
},
|
| 55 |
+
"onnx_prediction": {
|
| 56 |
+
"threat_probability": 0.25,
|
| 57 |
+
"raw_output": [[0.75, 0.25]],
|
| 58 |
+
"output_shape": [1, 2]
|
| 59 |
+
},
|
| 60 |
+
"models_used": ["contextClassifier.onnx"],
|
| 61 |
+
"raw_predictions": {
|
| 62 |
+
"onnx": {
|
| 63 |
+
"threat_probability": 0.25,
|
| 64 |
+
"raw_output": [[0.75, 0.25]]
|
| 65 |
+
},
|
| 66 |
+
"sentiment": {
|
| 67 |
+
"label": "POSITIVE",
|
| 68 |
+
"score": 0.5
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## π Response Format
|
| 75 |
+
|
| 76 |
+
The API returns a structured response with:
|
| 77 |
+
|
| 78 |
+
- **`is_threat`**: Boolean indicating if content is threatening
|
| 79 |
+
- **`final_confidence`**: Confidence score (0.0 to 1.0)
|
| 80 |
+
- **`threat_prediction`**: Raw threat probability
|
| 81 |
+
- **`sentiment_analysis`**: Sentiment classification and score
|
| 82 |
+
- **`onnx_prediction`**: Raw ONNX model output
|
| 83 |
+
- **`models_used`**: List of models used for prediction
|
| 84 |
+
- **`raw_predictions`**: Complete prediction data
|
| 85 |
+
|
| 86 |
+
## π οΈ Local Development
|
| 87 |
+
|
| 88 |
+
1. **Install dependencies:**
|
| 89 |
+
```bash
|
| 90 |
+
pip install -r requirements.txt
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
2. **Place your model:**
|
| 94 |
+
Ensure `contextClassifier.onnx` is in the project root
|
| 95 |
+
|
| 96 |
+
3. **Run the API:**
|
| 97 |
+
```bash
|
| 98 |
+
python app.py
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
4. **Visit:** `http://localhost:7860/docs`
|
| 102 |
+
|
| 103 |
+
## π³ Docker
|
| 104 |
+
|
| 105 |
+
```bash
|
| 106 |
+
# Build
|
| 107 |
+
docker build -t content-classifier .
|
| 108 |
+
|
| 109 |
+
# Run
|
| 110 |
+
docker run -p 7860:7860 content-classifier
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
## π Model Requirements
|
| 114 |
+
|
| 115 |
+
Your `contextClassifier.onnx` model should:
|
| 116 |
+
- Accept text-based inputs
|
| 117 |
+
- Return classification predictions
|
| 118 |
+
- Be compatible with ONNX Runtime
|
| 119 |
+
|
| 120 |
+
## βοΈ Configuration
|
| 121 |
+
|
| 122 |
+
Customize the preprocessing and postprocessing functions in `app.py` based on your specific model requirements.
|
| 123 |
+
|
| 124 |
+
## π Monitoring
|
| 125 |
+
|
| 126 |
+
- **Health Check**: `/health` - Monitor API status
|
| 127 |
+
- **Model Info**: `/model-info` - View model details
|
| 128 |
+
- **Logs**: Check application logs for debugging
|
| 129 |
+
|
| 130 |
+
## π License
|
| 131 |
+
|
| 132 |
+
MIT License - feel free to use and modify!
|
app.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 11 |
+
title="Content Classifier API",
|
| 12 |
+
description="ONNX-based content classification for threat detection and sentiment analysis",
|
| 13 |
+
version="1.0.0"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# Model configuration
|
| 17 |
+
MODEL_PATH = "contextClassifier.onnx"
|
| 18 |
+
session = None
|
| 19 |
+
|
| 20 |
+
class TextInput(BaseModel):
|
| 21 |
+
text: str
|
| 22 |
+
max_length: Optional[int] = 512
|
| 23 |
+
|
| 24 |
+
class PredictionResponse(BaseModel):
|
| 25 |
+
is_threat: bool
|
| 26 |
+
final_confidence: float
|
| 27 |
+
threat_prediction: float
|
| 28 |
+
sentiment_analysis: Optional[Dict[str, Any]]
|
| 29 |
+
onnx_prediction: Optional[Dict[str, Any]]
|
| 30 |
+
models_used: List[str]
|
| 31 |
+
raw_predictions: Dict[str, Any]
|
| 32 |
+
|
| 33 |
+
def load_model():
|
| 34 |
+
"""Load the ONNX model"""
|
| 35 |
+
global session
|
| 36 |
+
try:
|
| 37 |
+
if not os.path.exists(MODEL_PATH):
|
| 38 |
+
raise FileNotFoundError(f"Model file not found: {MODEL_PATH}")
|
| 39 |
+
|
| 40 |
+
session = ort.InferenceSession(MODEL_PATH)
|
| 41 |
+
print(f"β
Model loaded successfully from {MODEL_PATH}")
|
| 42 |
+
|
| 43 |
+
# Print model info
|
| 44 |
+
inputs = [input.name for input in session.get_inputs()]
|
| 45 |
+
outputs = [output.name for output in session.get_outputs()]
|
| 46 |
+
print(f"π₯ Model inputs: {inputs}")
|
| 47 |
+
print(f"π€ Model outputs: {outputs}")
|
| 48 |
+
|
| 49 |
+
except Exception as e:
|
| 50 |
+
print(f"β Error loading model: {e}")
|
| 51 |
+
raise e
|
| 52 |
+
|
| 53 |
+
def preprocess_text(text: str, max_length: int = 512) -> Dict[str, np.ndarray]:
|
| 54 |
+
"""
|
| 55 |
+
Preprocess text for the ONNX model
|
| 56 |
+
NOTE: Adjust this function based on your model's specific requirements
|
| 57 |
+
"""
|
| 58 |
+
try:
|
| 59 |
+
# Basic text preprocessing (customize based on your model)
|
| 60 |
+
text = text.strip().lower()
|
| 61 |
+
|
| 62 |
+
# Simple tokenization (replace with actual tokenizer if needed)
|
| 63 |
+
tokens = text.split()[:max_length]
|
| 64 |
+
|
| 65 |
+
# Pad or truncate to fixed length
|
| 66 |
+
if len(tokens) < max_length:
|
| 67 |
+
tokens.extend(['[PAD]'] * (max_length - len(tokens)))
|
| 68 |
+
|
| 69 |
+
# Convert to numerical representation
|
| 70 |
+
# NOTE: This is a placeholder - replace with your actual preprocessing
|
| 71 |
+
input_ids = np.array([hash(token) % 30000 for token in tokens], dtype=np.int64).reshape(1, -1)
|
| 72 |
+
attention_mask = np.array([1 if token != '[PAD]' else 0 for token in tokens], dtype=np.int64).reshape(1, -1)
|
| 73 |
+
|
| 74 |
+
return {
|
| 75 |
+
"input_ids": input_ids,
|
| 76 |
+
"attention_mask": attention_mask
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
except Exception as e:
|
| 80 |
+
print(f"β Preprocessing error: {e}")
|
| 81 |
+
raise HTTPException(status_code=500, detail=f"Text preprocessing failed: {str(e)}")
|
| 82 |
+
|
| 83 |
+
def postprocess_predictions(outputs: List[np.ndarray]) -> Dict[str, Any]:
|
| 84 |
+
"""
|
| 85 |
+
Process ONNX model outputs into the required format
|
| 86 |
+
"""
|
| 87 |
+
try:
|
| 88 |
+
predictions = {}
|
| 89 |
+
|
| 90 |
+
if not outputs or len(outputs) == 0:
|
| 91 |
+
raise ValueError("No outputs received from model")
|
| 92 |
+
|
| 93 |
+
# Get the main output (adjust index based on your model)
|
| 94 |
+
main_output = outputs[0]
|
| 95 |
+
|
| 96 |
+
# Extract threat prediction
|
| 97 |
+
if len(main_output.shape) == 2 and main_output.shape[1] >= 2:
|
| 98 |
+
# Binary classification: [non_threat_prob, threat_prob]
|
| 99 |
+
threat_prediction = float(main_output[0][1])
|
| 100 |
+
elif len(main_output.shape) == 2 and main_output.shape[1] == 1:
|
| 101 |
+
# Single output probability
|
| 102 |
+
threat_prediction = float(main_output[0][0])
|
| 103 |
+
else:
|
| 104 |
+
# Fallback for other output shapes
|
| 105 |
+
threat_prediction = float(main_output.flatten()[0])
|
| 106 |
+
|
| 107 |
+
# Calculate confidence and threat classification
|
| 108 |
+
final_confidence = abs(threat_prediction - 0.5) * 2 # Scale to 0-1
|
| 109 |
+
is_threat = threat_prediction > 0.5
|
| 110 |
+
|
| 111 |
+
# Store ONNX predictions
|
| 112 |
+
predictions["onnx"] = {
|
| 113 |
+
"threat_probability": threat_prediction,
|
| 114 |
+
"raw_output": main_output.tolist(),
|
| 115 |
+
"output_shape": main_output.shape
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
# Generate sentiment analysis (inverse relationship with threat)
|
| 119 |
+
sentiment_score = (0.5 - threat_prediction) * 2 # Convert to sentiment scale
|
| 120 |
+
predictions["sentiment"] = {
|
| 121 |
+
"label": "POSITIVE" if sentiment_score > 0 else "NEGATIVE",
|
| 122 |
+
"score": abs(sentiment_score)
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
models_used = ["contextClassifier.onnx"]
|
| 126 |
+
|
| 127 |
+
# Return the exact format specified
|
| 128 |
+
return {
|
| 129 |
+
"is_threat": is_threat,
|
| 130 |
+
"final_confidence": final_confidence,
|
| 131 |
+
"threat_prediction": threat_prediction,
|
| 132 |
+
"sentiment_analysis": predictions.get("sentiment"),
|
| 133 |
+
"onnx_prediction": predictions.get("onnx"),
|
| 134 |
+
"models_used": models_used,
|
| 135 |
+
"raw_predictions": predictions
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
print(f"β Postprocessing error: {e}")
|
| 140 |
+
# Return safe fallback response
|
| 141 |
+
return {
|
| 142 |
+
"is_threat": False,
|
| 143 |
+
"final_confidence": 0.0,
|
| 144 |
+
"threat_prediction": 0.0,
|
| 145 |
+
"sentiment_analysis": {"label": "NEUTRAL", "score": 0.0},
|
| 146 |
+
"onnx_prediction": {"error": str(e)},
|
| 147 |
+
"models_used": ["contextClassifier.onnx"],
|
| 148 |
+
"raw_predictions": {"error": str(e)}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
@app.on_event("startup")
|
| 152 |
+
async def startup_event():
|
| 153 |
+
"""Load model on application startup"""
|
| 154 |
+
load_model()
|
| 155 |
+
|
| 156 |
+
@app.get("/")
|
| 157 |
+
async def root():
|
| 158 |
+
"""Root endpoint with API information"""
|
| 159 |
+
return {
|
| 160 |
+
"message": "π Content Classifier API",
|
| 161 |
+
"description": "ONNX-based content classification for threat detection and sentiment analysis",
|
| 162 |
+
"model": MODEL_PATH,
|
| 163 |
+
"status": "running",
|
| 164 |
+
"endpoints": {
|
| 165 |
+
"predict": "/predict",
|
| 166 |
+
"health": "/health",
|
| 167 |
+
"model_info": "/model-info",
|
| 168 |
+
"docs": "/docs"
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
@app.post("/predict", response_model=PredictionResponse)
|
| 173 |
+
async def predict(input_data: TextInput):
|
| 174 |
+
"""
|
| 175 |
+
Classify content for threat detection and sentiment analysis
|
| 176 |
+
|
| 177 |
+
Returns the exact format:
|
| 178 |
+
{
|
| 179 |
+
"is_threat": bool,
|
| 180 |
+
"final_confidence": float,
|
| 181 |
+
"threat_prediction": float,
|
| 182 |
+
"sentiment_analysis": dict,
|
| 183 |
+
"onnx_prediction": dict,
|
| 184 |
+
"models_used": list,
|
| 185 |
+
"raw_predictions": dict
|
| 186 |
+
}
|
| 187 |
+
"""
|
| 188 |
+
if session is None:
|
| 189 |
+
raise HTTPException(status_code=500, detail="Model not loaded. Please check server logs.")
|
| 190 |
+
|
| 191 |
+
if not input_data.text.strip():
|
| 192 |
+
raise HTTPException(status_code=400, detail="Text input cannot be empty")
|
| 193 |
+
|
| 194 |
+
try:
|
| 195 |
+
# Preprocess the input text
|
| 196 |
+
model_inputs = preprocess_text(input_data.text, input_data.max_length)
|
| 197 |
+
|
| 198 |
+
# Prepare inputs for ONNX Runtime
|
| 199 |
+
input_names = [input.name for input in session.get_inputs()]
|
| 200 |
+
ort_inputs = {}
|
| 201 |
+
|
| 202 |
+
for name in input_names:
|
| 203 |
+
if name in model_inputs:
|
| 204 |
+
ort_inputs[name] = model_inputs[name]
|
| 205 |
+
else:
|
| 206 |
+
print(f"β οΈ Warning: Expected input '{name}' not found in processed inputs")
|
| 207 |
+
|
| 208 |
+
if not ort_inputs:
|
| 209 |
+
raise HTTPException(status_code=500, detail="No valid inputs prepared for model")
|
| 210 |
+
|
| 211 |
+
# Run inference
|
| 212 |
+
outputs = session.run(None, ort_inputs)
|
| 213 |
+
|
| 214 |
+
# Process and return results
|
| 215 |
+
result = postprocess_predictions(outputs)
|
| 216 |
+
return result
|
| 217 |
+
|
| 218 |
+
except Exception as e:
|
| 219 |
+
print(f"β Prediction error: {e}")
|
| 220 |
+
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")
|
| 221 |
+
|
| 222 |
+
@app.get("/health")
|
| 223 |
+
async def health_check():
|
| 224 |
+
"""Health check endpoint"""
|
| 225 |
+
return {
|
| 226 |
+
"status": "healthy" if session is not None else "unhealthy",
|
| 227 |
+
"model_loaded": session is not None,
|
| 228 |
+
"model_path": MODEL_PATH,
|
| 229 |
+
"model_exists": os.path.exists(MODEL_PATH)
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
@app.get("/model-info")
|
| 233 |
+
async def model_info():
|
| 234 |
+
"""Get detailed model information"""
|
| 235 |
+
if session is None:
|
| 236 |
+
raise HTTPException(status_code=500, detail="Model not loaded")
|
| 237 |
+
|
| 238 |
+
try:
|
| 239 |
+
inputs = []
|
| 240 |
+
for input_meta in session.get_inputs():
|
| 241 |
+
inputs.append({
|
| 242 |
+
"name": input_meta.name,
|
| 243 |
+
"type": str(input_meta.type),
|
| 244 |
+
"shape": list(input_meta.shape) if input_meta.shape else None
|
| 245 |
+
})
|
| 246 |
+
|
| 247 |
+
outputs = []
|
| 248 |
+
for output_meta in session.get_outputs():
|
| 249 |
+
outputs.append({
|
| 250 |
+
"name": output_meta.name,
|
| 251 |
+
"type": str(output_meta.type),
|
| 252 |
+
"shape": list(output_meta.shape) if output_meta.shape else None
|
| 253 |
+
})
|
| 254 |
+
|
| 255 |
+
return {
|
| 256 |
+
"model_path": MODEL_PATH,
|
| 257 |
+
"model_size": f"{os.path.getsize(MODEL_PATH) / (1024*1024):.2f} MB",
|
| 258 |
+
"inputs": inputs,
|
| 259 |
+
"outputs": outputs,
|
| 260 |
+
"runtime_info": {
|
| 261 |
+
"providers": session.get_providers(),
|
| 262 |
+
"device": "CPU"
|
| 263 |
+
}
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
except Exception as e:
|
| 267 |
+
raise HTTPException(status_code=500, detail=f"Failed to get model info: {str(e)}")
|
| 268 |
+
|
| 269 |
+
if __name__ == "__main__":
|
| 270 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
contextClassifier.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:11e8c5314dfcec3f5c06b74655961b3211a4f4509ff8e7026e066ac14251d979
|
| 3 |
+
size 267958108
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn[standard]==0.24.0
|
| 3 |
+
onnxruntime==1.16.3
|
| 4 |
+
numpy==1.24.3
|
| 5 |
+
pydantic==2.5.0
|
| 6 |
+
python-multipart==0.0.6
|
test_api.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
import time
|
| 4 |
+
|
| 5 |
+
# Configuration
|
| 6 |
+
BASE_URL = "http://localhost:7860"
|
| 7 |
+
|
| 8 |
+
def test_api():
|
| 9 |
+
"""Test all API endpoints"""
|
| 10 |
+
print("π§ͺ Testing Content Classifier API")
|
| 11 |
+
print("=" * 50)
|
| 12 |
+
|
| 13 |
+
# Test 1: Root endpoint
|
| 14 |
+
print("\nπ Testing root endpoint...")
|
| 15 |
+
try:
|
| 16 |
+
response = requests.get(f"{BASE_URL}/")
|
| 17 |
+
print(f"β
Status: {response.status_code}")
|
| 18 |
+
print(f"π Response: {json.dumps(response.json(), indent=2)}")
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print(f"β Error: {e}")
|
| 21 |
+
|
| 22 |
+
# Test 2: Health check
|
| 23 |
+
print("\nπ₯ Testing health endpoint...")
|
| 24 |
+
try:
|
| 25 |
+
response = requests.get(f"{BASE_URL}/health")
|
| 26 |
+
print(f"β
Status: {response.status_code}")
|
| 27 |
+
print(f"π Response: {json.dumps(response.json(), indent=2)}")
|
| 28 |
+
except Exception as e:
|
| 29 |
+
print(f"β Error: {e}")
|
| 30 |
+
|
| 31 |
+
# Test 3: Model info
|
| 32 |
+
print("\nπ€ Testing model info endpoint...")
|
| 33 |
+
try:
|
| 34 |
+
response = requests.get(f"{BASE_URL}/model-info")
|
| 35 |
+
print(f"β
Status: {response.status_code}")
|
| 36 |
+
if response.status_code == 200:
|
| 37 |
+
print(f"π Response: {json.dumps(response.json(), indent=2)}")
|
| 38 |
+
else:
|
| 39 |
+
print(f"π Response: {response.text}")
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"β Error: {e}")
|
| 42 |
+
|
| 43 |
+
# Test 4: Prediction endpoint
|
| 44 |
+
print("\nπ Testing prediction endpoint...")
|
| 45 |
+
|
| 46 |
+
test_cases = [
|
| 47 |
+
{
|
| 48 |
+
"text": "Hello, how are you today? I hope you're doing well!",
|
| 49 |
+
"expected": "positive sentiment"
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"text": "I will destroy everything and cause harm!",
|
| 53 |
+
"expected": "threat detection"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"text": "This product is amazing, I love it so much!",
|
| 57 |
+
"expected": "positive sentiment"
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"text": "I hate this, it's terrible and awful!",
|
| 61 |
+
"expected": "negative sentiment"
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"text": "Neutral statement about weather conditions today.",
|
| 65 |
+
"expected": "neutral/low confidence"
|
| 66 |
+
}
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
for i, case in enumerate(test_cases, 1):
|
| 70 |
+
print(f"\nπ Test Case {i}: {case['expected']}")
|
| 71 |
+
print(f"π¬ Text: '{case['text'][:50]}{'...' if len(case['text']) > 50 else ''}'")
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
payload = {
|
| 75 |
+
"text": case["text"],
|
| 76 |
+
"max_length": 512
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
response = requests.post(f"{BASE_URL}/predict", json=payload)
|
| 80 |
+
|
| 81 |
+
if response.status_code == 200:
|
| 82 |
+
result = response.json()
|
| 83 |
+
print(f"β
Status: {response.status_code}")
|
| 84 |
+
print(f"π― Is Threat: {result['is_threat']}")
|
| 85 |
+
print(f"π Confidence: {result['final_confidence']:.3f}")
|
| 86 |
+
print(f"β οΈ Threat Prediction: {result['threat_prediction']:.3f}")
|
| 87 |
+
print(f"π Sentiment: {result['sentiment_analysis']['label']} ({result['sentiment_analysis']['score']:.3f})")
|
| 88 |
+
print(f"π§ Models Used: {result['models_used']}")
|
| 89 |
+
else:
|
| 90 |
+
print(f"β Status: {response.status_code}")
|
| 91 |
+
print(f"π Response: {response.text}")
|
| 92 |
+
|
| 93 |
+
except Exception as e:
|
| 94 |
+
print(f"β Error: {e}")
|
| 95 |
+
|
| 96 |
+
print("\n" + "=" * 50)
|
| 97 |
+
print("π API Testing Complete!")
|
| 98 |
+
|
| 99 |
+
def test_edge_cases():
|
| 100 |
+
"""Test edge cases and error conditions"""
|
| 101 |
+
print("\nπ¨ Testing Edge Cases")
|
| 102 |
+
print("=" * 30)
|
| 103 |
+
|
| 104 |
+
edge_cases = [
|
| 105 |
+
{"text": "", "description": "Empty string"},
|
| 106 |
+
{"text": " ", "description": "Whitespace only"},
|
| 107 |
+
{"text": "A" * 1000, "description": "Very long text"},
|
| 108 |
+
{"text": "π₯π―π", "description": "Emoji only"},
|
| 109 |
+
{"text": "12345 67890", "description": "Numbers only"}
|
| 110 |
+
]
|
| 111 |
+
|
| 112 |
+
for case in edge_cases:
|
| 113 |
+
print(f"\nπ Testing: {case['description']}")
|
| 114 |
+
try:
|
| 115 |
+
payload = {"text": case["text"]}
|
| 116 |
+
response = requests.post(f"{BASE_URL}/predict", json=payload)
|
| 117 |
+
|
| 118 |
+
if response.status_code == 200:
|
| 119 |
+
result = response.json()
|
| 120 |
+
print(f"β
Success: Threat={result['is_threat']}, Confidence={result['final_confidence']:.3f}")
|
| 121 |
+
else:
|
| 122 |
+
print(f"β οΈ Status {response.status_code}: {response.json().get('detail', 'Unknown error')}")
|
| 123 |
+
|
| 124 |
+
except Exception as e:
|
| 125 |
+
print(f"β Error: {e}")
|
| 126 |
+
|
| 127 |
+
if __name__ == "__main__":
|
| 128 |
+
print("π Content Classifier API Test Suite")
|
| 129 |
+
print(f"π Base URL: {BASE_URL}")
|
| 130 |
+
|
| 131 |
+
# Wait a moment for server to be ready
|
| 132 |
+
print("\nβ±οΈ Waiting for server to be ready...")
|
| 133 |
+
time.sleep(2)
|
| 134 |
+
|
| 135 |
+
# Run tests
|
| 136 |
+
test_api()
|
| 137 |
+
test_edge_cases()
|
| 138 |
+
|
| 139 |
+
print(f"\nπ Visit {BASE_URL}/docs for interactive API documentation")
|