Spaces:
Sleeping
title: SentimentAI
emoji: π
colorFrom: purple
colorTo: blue
sdk: docker
app_file: app.py
pinned: false
license: apache-2.0
short_description: 3-class sentiment analysis API β airzipm
π SentimentAI β RoBERTa Sentiment Analysis API
A production-grade, high-concurrency sentiment analysis API built on airzipm/sentiment-analysis-roberta.
Classifies text into Positive, Neutral, or Negative with confidence scores.
π Quick Start
Live Demo
Open the Space URL in your browser β the frontend (index.html) loads automatically.
Base URL
https://airzipm-sentimentai.hf.space
π‘ API Endpoints
| Method | Path | Rate Limit | Description |
|---|---|---|---|
GET |
/ |
β | Serve frontend index.html |
GET |
/health |
β | Model readiness & server stats |
GET |
/ping |
β | Lightweight liveness check |
POST |
/analyze |
30/min per IP | Single text sentiment analysis |
POST |
/batch |
10/min per IP | Batch analysis (up to 10 texts) |
GET |
/docs |
β | Interactive Swagger UI |
GET |
/redoc |
β | ReDoc API documentation |
π Endpoint Reference
GET /ping
Ultra-lightweight liveness probe. No inference. Always < 5ms. Frontend pings this every 30 seconds to prevent the Space from sleeping.
Response:
{
"pong": true,
"model_ready": true,
"t": 1717500000.123
}
GET /health
Model readiness check with server statistics.
Always returns HTTP 200 β read the status field to check readiness.
Response:
{
"status": "ok",
"model_loaded": true,
"device": "cpu",
"model_name": "airzipm/sentiment-analysis-roberta",
"uptime_s": 342.7,
"requests_served": 1284,
"version": "1.0.0"
}
status value |
Meaning |
|---|---|
"ok" |
Model loaded, ready to serve |
"loading" |
Model is still initializing (cold start) |
"error" |
Model failed to load (check logs) |
POST /analyze
Analyze sentiment of a single text. HuggingFace Inference API compatible.
Rate limit: 30 requests per IP per minute
Request body:
{
"inputs": "This movie was absolutely amazing!"
}
Successful response (200):
{
"label": "Positive",
"score": 0.973241,
"all_scores": [
{"label": "Positive", "score": 0.973241},
{"label": "Neutral", "score": 0.021034},
{"label": "Negative", "score": 0.005725}
],
"response_ms": 87
}
Error responses:
| Status | When | Body |
|---|---|---|
| 400 | Text empty or > 2000 chars | {"detail": "validation error..."} |
| 429 | Rate limit hit | {"detail": "Rate limit exceeded"} |
| 503 | Model still loading | {"detail": "Model is still loading..."} |
| 500 | Unexpected error | {"detail": "Inference failed..."} |
POST /batch
Analyze up to 10 texts in a single efficient request. All texts are processed in one RoBERTa forward pass (padded batch).
Rate limit: 10 requests per IP per minute (batch is more expensive)
Request body:
{
"inputs": [
"I loved this product!",
"The service was average.",
"Absolutely terrible experience."
]
}
Successful response (200):
{
"results": [
{
"label": "Positive",
"score": 0.971203,
"all_scores": [
{"label": "Positive", "score": 0.971203},
{"label": "Neutral", "score": 0.022411},
{"label": "Negative", "score": 0.006386}
],
"response_ms": 134
},
{
"label": "Neutral",
"score": 0.683441,
"all_scores": [
{"label": "Neutral", "score": 0.683441},
{"label": "Positive", "score": 0.201233},
{"label": "Negative", "score": 0.115326}
],
"response_ms": 134
},
{
"label": "Negative",
"score": 0.941872,
"all_scores": [
{"label": "Negative", "score": 0.941872},
{"label": "Neutral", "score": 0.042311},
{"label": "Positive", "score": 0.015817}
],
"response_ms": 134
}
],
"batch_size": 3,
"total_ms": 148
}
π₯οΈ Code Examples
JavaScript (Fetch API)
const API = "https://airzipm-sentimentai.hf.space";
// ββ Single analysis ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function analyze(text) {
const response = await fetch(`${API}/analyze`, {
method : "POST",
headers: { "Content-Type": "application/json" },
body : JSON.stringify({ inputs: text }),
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.detail || "Request failed");
}
return response.json();
// β { label: "Positive", score: 0.973, all_scores: [...], response_ms: 87 }
}
// ββ Batch analysis βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function analyzeBatch(texts) {
const response = await fetch(`${API}/batch`, {
method : "POST",
headers: { "Content-Type": "application/json" },
body : JSON.stringify({ inputs: texts }),
});
return response.json();
// β { results: [...], batch_size: 3, total_ms: 148 }
}
// ββ Keep-alive ping (prevents HF Space from sleeping) βββββββββββββββββββββ
setInterval(async () => {
const { model_ready } = await fetch(`${API}/ping`).then(r => r.json());
console.log("Model ready:", model_ready);
}, 30_000);
Python (httpx)
import httpx
API = "https://airzipm-sentimentai.hf.space"
# Single text
response = httpx.post(f"{API}/analyze", json={"inputs": "This is great!"})
result = response.json()
print(result["label"], result["score"]) # β Positive 0.973
# Batch
batch_resp = httpx.post(f"{API}/batch", json={
"inputs": ["Loved it!", "Meh.", "Terrible."]
})
for r in batch_resp.json()["results"]:
print(r["label"], r["score"])
cURL
# Single analysis
curl -X POST "https://airzipm-sentimentai.hf.space/analyze" \
-H "Content-Type: application/json" \
-d '{"inputs": "This product completely exceeded my expectations!"}'
# Batch analysis
curl -X POST "https://airzipm-sentimentai.hf.space/batch" \
-H "Content-Type: application/json" \
-d '{"inputs": ["Amazing!", "It was okay.", "Terrible experience."]}'
# Health check
curl "https://airzipm-sentimentai.hf.space/health"
# Ping
curl "https://airzipm-sentimentai.hf.space/ping"
β‘ Architecture & Concurrency
How it handles multiple simultaneous users
User 1 β acquires semaphore slot 1 β running inference (~100ms)
User 2 β acquires semaphore slot 2 β running inference
User 3 β acquires semaphore slot 3 β running inference
User 4 β acquires semaphore slot 4 β running inference
User 5 β WAITS in asyncio queue (non-blocking β event loop stays free)
User 6 β WAITS in asyncio queue
...
User 20 β WAITS in asyncio queue
When User 1 finishes:
β releases slot
β User 5 immediately acquires it and begins inference
No requests are dropped or rejected. The asyncio event loop stays free to
accept new connections and serve /ping responses while inferences run.
Component Breakdown
| Component | Role |
|---|---|
| FastAPI | Async request routing, Pydantic validation |
| Gunicorn + 4 UvicornWorkers | Multi-process concurrency (~100+ connections) |
| asyncio.Semaphore(4) | Caps simultaneous inferences to prevent OOM |
| loop.run_in_executor | Runs CPU-bound inference off the event loop |
| slowapi | Per-IP rate limiting (in-memory, no Redis) |
| RoBERTa (loaded once) | Weights in RAM at startup β never reloaded per request |
Response Headers
Every response includes:
| Header | Value | Use |
|---|---|---|
X-Response-Time |
87ms |
Frontend displays this |
X-Model-Ready |
true / false |
Frontend status bar |
Access-Control-Allow-Origin |
* |
CORS β any frontend domain |
π§ Local Development
# Clone and install
git clone https://huggingface.co/spaces/airzipm/sentimentai
cd sentimentai
pip install -r requirements.txt
# Run locally (single worker, port 7860)
python app.py
# Or with gunicorn (production-like, 4 workers)
gunicorn app:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:7860 \
--timeout 120
# Visit: http://localhost:7860
# Swagger: http://localhost:7860/docs
β οΈ Cold Start Note
This Space runs on the free tier, which means:
- The Space sleeps after ~5 minutes of inactivity
- The first request after sleeping triggers a cold start (20β50 seconds)
- The model is downloaded (~500MB) and loaded into RAM on first start
- The frontend handles this gracefully with a "warming up" status bar
π License
Apache 2.0 β see LICENSE
Model: airzipm/sentiment-analysis-roberta