sentimentai-api / README.md
airzipm's picture
Create README.md
b95ed7b verified
|
Raw
History Blame Contribute Delete
10 kB
metadata
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