- Add maturity/ service (FastAPI + Falconsai/nsfw_image_detection ViT classifier)
- /analyze (URL) and /analyze/file (multipart upload) endpoints
- Normalized response: maturity_label, confidence, score, labels,
action_hint (safe/review/flag_high), advisory, threshold_used,
analysis_time_ms, model, source
- Configurable thresholds via MATURITY_THRESHOLD_MATURE / MATURITY_THRESHOLD_REVIEW
- Reuses common/image_io for URL validation and file-size enforcement
- Explicit 502/503 errors on failure — no silent safe fallback
- Per-request structured logging (score, label, threshold path, elapsed ms)
- Update gateway/main.py
- MATURITY_URL + MATURITY_ENABLED env vars
- POST /analyze/maturity and POST /analyze/maturity/file endpoints
- /health includes maturity service status
- _assert_maturity_enabled() guard for clean 503 when disabled
- All existing endpoints untouched (additive change)
- Update docker-compose.yml
- Add maturity service with healthcheck (start_period: 90s)
- Gateway environment: MATURITY_URL, MATURITY_ENABLED
- Gateway depends_on: maturity (service_healthy)
- Update README.md and USAGE.md
- Document maturity service, env vars, curl examples,
full response schema table, action_hint logic, failure guidance
9.3 KiB
Skinbase Vision Stack (CLIP + BLIP + YOLO + Qdrant + Card Renderer + Maturity) – Dockerized FastAPI
This repository provides six standalone vision services (CLIP / BLIP / YOLO / Qdrant / Card Renderer / Maturity) and a Gateway API that can call them individually or together.
Services & Ports
gateway(exposed):https://vision.klevze.netclip: internal onlyblip: internal onlyyolo: internal onlyqdrant: vector DB (port6333exposed for direct access)qdrant-svc: internal Qdrant API wrappercard-renderer: internal card rendering servicematurity: internal NSFW/maturity classifier service
Run
docker compose up -d --build
If you use BLIP, create a .env file first.
Required variables:
API_KEY=your_api_key_here
HUGGINGFACE_TOKEN=your_huggingface_token_here
HUGGINGFACE_TOKEN is required when the configured BLIP model is private, gated, or otherwise requires Hugging Face authentication.
Optional maturity configuration (override in .env if needed):
MATURITY_MODEL=Falconsai/nsfw_image_detection
MATURITY_THRESHOLD_MATURE=0.80
MATURITY_THRESHOLD_REVIEW=0.60
MATURITY_ENABLED=true
Service startup now waits on container healthchecks, so first boot may take longer while models finish loading.
Health
curl -H "X-API-Key: <your-api-key>" https://vision.klevze.net/health
Universal analyze (ALL)
With URL
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/all \
-H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp","limit":5}'
With file upload (multipart)
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/all/file \
-F "file=@/path/to/image.webp" \
-F "limit=5"
Individual services (via gateway)
CLIP tags
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/clip -H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp","limit":5}'
CLIP tags (file)
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/clip/file \
-F "file=@/path/to/image.webp" \
-F "limit=5"
BLIP caption
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/blip -H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp","variants":3}'
BLIP caption (file)
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/blip/file \
-F "file=@/path/to/image.webp" \
-F "variants=3" \
-F "max_length=60"
YOLO detect
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/yolo -H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp","conf":0.25}'
YOLO detect (file)
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/yolo/file \
-F "file=@/path/to/image.webp" \
-F "conf=0.25"
Maturity / NSFW analysis
Analyzes an image and returns a normalized maturity signal for Nova moderation workflows.
Analyze by URL
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/maturity \
-H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp"}'
Analyze from file upload
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/analyze/maturity/file \
-F "file=@/path/to/image.webp"
Example response:
{
"maturity_label": "mature",
"confidence": 0.94,
"score": 0.94,
"labels": ["nsfw"],
"model": "Falconsai/nsfw_image_detection",
"threshold_used": 0.80,
"analysis_time_ms": 183.0,
"source": "maturity-service",
"action_hint": "flag_high",
"advisory": "High-confidence mature content detected"
}
action_hint values: safe, review, flag_high. Nova should use these to decide blur/queue/flag behaviour.
Vector DB (Qdrant) via gateway
Qdrant point IDs must be either:
- an unsigned integer
- a UUID string
If you send another string value, the wrapper may replace it with a generated UUID. In that case the original value is stored in the payload as _original_id.
You can fetch a stored point by its preserved original application ID:
curl -H "X-API-Key: <your-api-key>" https://vision.klevze.net/vectors/points/by-original-id/img-001
Store image embedding by URL
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/vectors/upsert \
-H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp","id":"550e8400-e29b-41d4-a716-446655440000","metadata":{"category":"wallpaper"}}'
Store image embedding by file upload
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/vectors/upsert/file \
-F "file=@/path/to/image.webp" \
-F 'id=550e8400-e29b-41d4-a716-446655440001' \
-F 'metadata_json={"category":"photo"}'
Search similar images by URL
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/vectors/search \
-H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp","limit":5,"filter_metadata":{"is_public":true}}'
Optional search parameters: hnsw_ef (int), exact (bool), indexed_only (bool), score_threshold (float), filter_metadata (object).
Search similar images by file upload
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/vectors/search/file \
-F "file=@/path/to/image.webp" \
-F "limit=5" \
-F 'filter_metadata_json={"is_public":true}'
List collections
curl -H "X-API-Key: <your-api-key>" https://vision.klevze.net/vectors/collections
Get collection info
curl -H "X-API-Key: <your-api-key>" https://vision.klevze.net/vectors/collections/images
Full diagnostic inspect
curl -H "X-API-Key: <your-api-key>" https://vision.klevze.net/vectors/inspect
Returns HNSW config, optimizer config, quantization, segment count, payload index coverage, and RAM estimate for every collection.
Payload index management
# List indexes
curl -H "X-API-Key: <your-api-key>" https://vision.klevze.net/vectors/collections/images/indexes
# Create a single index
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/vectors/collections/images/indexes \
-H "Content-Type: application/json" \
-d '{"field":"is_public","type":"bool"}'
# Ensure multiple indexes (idempotent)
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/vectors/collections/images/ensure-indexes \
-H "Content-Type: application/json" \
-d '{"fields":[{"field":"is_public","type":"bool"},{"field":"category_id","type":"integer"}]}'
Supported index types: keyword, integer, float, bool, geo, datetime, text, uuid.
Collection configuration (HNSW / optimizer / quantization)
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/vectors/collections/images/configure \
-H "Content-Type: application/json" \
-d '{"hnsw_m":16,"hnsw_ef_construct":200,"indexing_threshold":20000,"quantization_type":"int8"}'
Delete points
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/vectors/delete \
-H "Content-Type: application/json" \
-d '{"ids":["550e8400-e29b-41d4-a716-446655440000","550e8400-e29b-41d4-a716-446655440001"]}'
If you let the wrapper generate a UUID, use the returned id value for later get, search, or delete operations.
Card Renderer
List available templates
curl -H "X-API-Key: <your-api-key>" https://vision.klevze.net/cards/templates
Render a card from a URL
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/cards/render \
-H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp","title":"Artwork Title","username":"@artist","template":"nova-artwork-v1"}'
Returns binary image bytes (WebP by default).
Render a card from a file upload
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/cards/render/file \
-F "file=@/path/to/image.webp" \
-F "title=Artwork Title" \
-F "username=@artist" \
-F "template=nova-artwork-v1"
Get card layout metadata (no image rendered)
curl -H "X-API-Key: <your-api-key>" -X POST https://vision.klevze.net/cards/render/meta \
-H "Content-Type: application/json" \
-d '{"url":"https://files.skinbase.org/img/aa/bb/cc/md.webp","title":"Artwork Title"}'
Notes
- Models are loaded at service startup; initial container start can take 1–2 minutes as model weights are downloaded.
- Qdrant data is persisted in the project folder at
./data/qdrant, so it survives container restarts and recreates. - Remote image URLs are restricted to public
http/httpshosts. Localhost, private IP ranges, and non-image content types are rejected. - The maturity service uses
Falconsai/nsfw_image_detection(ViT-based). Thresholds are configurable via.env. The model handles photos and stylized digital art but should be calibrated against real Skinbase content before production use. - For production: add auth, rate limits, and restrict gateway exposure (private network).
- GPU: you can add NVIDIA runtime later (compose profiles) if needed.