Inspection findings:
- _ensure_collection() created collections with bare VectorParams (no HNSW/optimizer config)
- _do_search() had no SearchParams — used Qdrant defaults (ef often ~100, no indexed_only)
- No payload index management at all — filtered searches scanned unindexed fields every time
- collection_info() returned minimal data — impossible to inspect production state
- No way to create/ensure payload indexes via the API
Changes — qdrant/main.py:
- Add SEARCH_HNSW_EF env var (default 128, above Qdrant default for better recall)
- _ensure_collection(): configure HnswConfigDiff(m=16, ef_construct=200, on_disk=False)
and OptimizersConfigDiff(indexing_threshold=20000, default_segment_number=4) on creation
- _do_search(): use SearchParams(hnsw_ef, exact, indexed_only) on every query
- SearchUrlRequest + SearchVectorRequest: expose hnsw_ef, exact, indexed_only per request
- collection_info(): expand to full HNSW/optimizer/quantization/segment/payload_schema detail
- GET /collections/{name}/indexes — list all payload indexes
- POST /collections/{name}/indexes — create a single payload index
- POST /collections/{name}/ensure-indexes — idempotent bulk index creation (skip existing)
- POST /collections/{name}/configure — apply HNSW/optimizer changes to existing collections
Changes — gateway/main.py:
- Expose the 4 new qdrant-svc endpoints under /vectors/collections/{name}/...
Changes — docker-compose.yml:
- Add SEARCH_HNSW_EF=128 to qdrant-svc environment
Critical usage note for existing collections:
After deploying, call POST /vectors/collections/images/ensure-indexes with the
payload fields actually used in filter_metadata (is_public, category_id, etc.)
to add missing indexes. This is the highest-impact single action for filtered search.
Skinbase Vision Stack (CLIP + BLIP + YOLO + Qdrant) – Dockerized FastAPI
This repository provides four standalone vision services (CLIP / BLIP / YOLO / Qdrant) 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 wrapper
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.
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"
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}'
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"
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
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.
Notes
- This is a starter scaffold. Models are loaded at service startup.
- 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. - For production: add auth, rate limits, and restrict gateway exposure (private network).
- GPU: you can add NVIDIA runtime later (compose profiles) if needed.