Why did we open-source our inference engine? Read the post

Tag product photos twice as accurately as Amazon Rekognition, for 98% less

SIE reads each product photo and fills in its type, colour and material, choosing from your own lists. There’s no model to train, and you pay only for the photos you tag.

SIE SigLIP so400m

All three right
A round, button-tufted ottoman in blue velvet

Picked from your own lists

Type51 choices
ottoman then stool, sofa
Colour14 choices
blue then brown, purple
Material11 choices
velvet then suede, fabric

Rekognition and GPT-⁠6 Luna mislabel what you sell

A black metal platform bed frame with a grid of slats and folding legs

Amazon Rekognition

Filed as a table
Its own labelsConfidence
  • Coffee Table100.0
  • Furniture100.0
  • Table100.0
  • Dining Table57.2
  • Roof Rack55.0
  • Aluminium53.0
  • Bench51.3

No “Bed” among its 7 labels. Even a label mapping fitted to the test photos themselves gets only 35% of them fully right.

SIE SigLIP so400m

Right type: bed
Type51 choices
bed then headboard, bench
A black metal platform bed frame, staged with a white mattress and white bedding on a patterned rug

GPT-6 Luna, reasoning high

Tagged the bedding
Type
bed
Colour
white
Material
fabric

Same answer in 3 of 3 runs

SIE SigLIP so400m

All three right
Type
bed
Colour
black
Material
metal

Twice Amazon Rekognition’s accuracy, for 98% less

Accuracy against cost
938 photos
All three fields right ↑ Cost per million photos →
Share of 938 product photos with type, colour and material all right, against list-price cost per million photos
ModelAll three fields rightCost per million photos
SIE SigLIP so400m72.3%$24
GPT-6 Luna84.4%$183
Claude Haiku 5.590.6%$271
Amazon Rekognition26.8%$1,750
GPT-6.1 Sol86.9%$3,311
Claude Sonnet 5.592.3%$4,883
Photo-label evaluation

938 product photos from Amazon’s public furniture and home catalogue, each tagged from the same 51 types, 14 colours and 11 materials

Right
Type, colour and material all match the product’s own listing
Tuning
Prompts and Rekognition’s label map were chosen on 311 separate photos
LLMs
Each at its best-scoring setting
Prices
Real-time list prices on each model’s recorded usage

Rank your own labels from a photo

from pathlib import Path
from urllib.request import urlopen
import numpy as np
from sie_sdk import SIEClient
​
client = SIEClient(api_key="API keysk-sie-…")
​
banks = {
"type": ["type labelottoman", "type labelstool", "type labelsofa"],
"colour": ["colour labelblue", "colour labelbrown", "colour labelpurple"],
"material": ["material labelvelvet", "material labelsuede", "material labelfabric"]
}
templates = {
"type": ["a photo of {article} {label}.", "{label}", "a product photo of {article} {label}."],
"colour": ["a {label} object."],
"material": ["a photo of a product made of {label}."]
}
​
image_url = imageblue-velvet-ottoman.jpgbrowse
with urlopen(image_url) as source:
image_bytes = source.read()
image = {"data": image_bytes, "format": "jpeg"}
prompts = [
(bank, label, template.format(label=label, article="an" if label[0].lower() in "aeiou" else "a"))
for bank, labels in banks.items()
for label in labels
for template in templates[bank]
]
​
···
​
# Native output is dense vectors; these rankings are computed by the caller.
vecs = client.encode("modelgoogle/siglip-so400m-patch14-384", [
{"images": [image]},
*[{"text": text} for _, _, text in prompts],
])
print(rank_banks([vector["dense"] for vector in vecs]))
import { readFile } from 'node:fs/promises';
import { SIEClient } from '@superlinked/sie-sdk';

const client = new SIEClient(undefined, { apiKey: 'sk-sie-…' });

const banks = {
  "type": ["ottoman", "stool", "sofa"],
  "colour": ["blue", "brown", "purple"],
  "material": ["velvet", "suede", "fabric"]
};
const templates = {
  "type": ["a photo of {article} {label}.", "{label}", "a product photo of {article} {label}."],
  "colour": ["a {label} object."],
  "material": ["a photo of a product made of {label}."]
};
const imageUrl = "https://superlinked.com/reference/image-classify/blue-velvet-ottoman.jpg";
const source = await fetch(imageUrl);
if (!source.ok) throw new Error('Could not download the original image.');
const image = new Uint8Array(await source.arrayBuffer());
const names = ['type', 'colour', 'material'] as const;
const prompts = names.flatMap((bank) => banks[bank].flatMap((label) =>
  templates[bank].map((template) => ({ bank, label, text: template
    .replaceAll('{article}', /^[aeiou]/i.test(label) ? 'an' : 'a')
    .replaceAll('{label}', label) })),
));
const vecs = await client.encode('google/siglip-so400m-patch14-384', [
  { images: [image] },
  ...prompts.map(({ text }) => ({ text })),
]);
···
console.log(result);
image_bytes=$(curl --fail --silent --show-error --location 'https://superlinked.com/reference/image-classify/blue-velvet-ottoman.jpg' | base64 | tr -d '\n')
curl https://api.superlinked.com/v1/encode/google%2Fsiglip-so400m-patch14-384 \
  -H "Authorization: Bearer sk-sie-…" \
  -H "Content-Type: application/json" \
  -d "{\"items\":[{\"images\":[{\"data\":\"$image_bytes\",\"format\":\"jpeg\"}]},{\"text\":\"a photo of an ottoman.\"},{\"text\":\"ottoman\"},{\"text\":\"a product photo of an ottoman.\"},{\"text\":\"a photo of a stool.\"},{\"text\":\"stool\"},{\"text\":\"a product photo of a stool.\"},{\"text\":\"a photo of a sofa.\"},{\"text\":\"sofa\"},{\"text\":\"a product photo of a sofa.\"},{\"text\":\"a blue object.\"},{\"text\":\"a brown object.\"},{\"text\":\"a purple object.\"},{\"text\":\"a photo of a product made of velvet.\"},{\"text\":\"a photo of a product made of suede.\"},{\"text\":\"a photo of a product made of fabric.\"}],\"params\":{\"output_types\":[\"dense\"]}}" > image-vectors.json

···
Build the "Image classify" capability into my app using the Superlinked Inference Engine (SIE).

Context
- SIE is an OpenAI-style inference API. Python SDK: `from sie_sdk import SIEClient`; TypeScript: `@superlinked/sie-sdk`.
- Base URL: https://api.superlinked.com (or my regional endpoint). Auth: Bearer key from env `SIE_API_KEY` (never hard-code it).
- Model: google/siglip-so400m-patch14-384 (SIE primitive: /encode). Keep the model id configurable.

Task
- Input: an uploaded image plus candidate labels.
- Behaviour: return the candidate image labels ranked by similarity
- Encode the original image and all type, colour and material prompts in one batched /encode call. Use the visible recipe's three type templates and its colour/material templates. Normalize each vector, compute cosine per prompt, average the three type prompt scores per label, and select each bank's independent argmax in caller code. Native output is dense vectors, not classification probabilities.

Deliverables
- A typed client wrapper, an application-level function for this task, error handling for timeouts/empty input, and unit tests with a stubbed client.
- Wire it into my existing stack (ask me which framework if unclear) and add a short usage example.
Output

Best match from each of your lists

A round, button-tufted ottoman in blue velvet

Type

3 choices
  1. ottoman 0.136
  2. stool 0.106
  3. sofa 0.084

Colour

3 choices
  1. blue 0.065
  2. brown 0.026
  3. purple 0.026

Material

3 choices
  1. velvet 0.109
  2. suede 0.073
  3. fabric 0.067

SIE returns one vector for the photo and one per label prompt. Your code compares them by cosine and keeps the best value in each list.

Tag a million photos a month for $24 instead of $1,750 on Amazon Rekognition

Self-hosted (Kubernetes)

SIE in your clusters, backed by our engineers.

  • Data stays in your own cloud account
  • Same models and API as Serverless
  • Terraform to your cloud in minutes

Run locally

The open-source server on your own machine.

pip install "sie-server[local]" && sie-server serve

Switch to SIE in 5 minutes

import boto3

client = boto3.client("rekognition")
response = client.detect_labels(
    Image={"Bytes": image_bytes},
    Features=["GENERAL_LABELS", "IMAGE_PROPERTIES"],
)
labels = map_labels(response, banks)  # your table from Rekognition's labels
import json
from openai import OpenAI

client = OpenAI()
response = client.responses.create(
    model="gpt-6-luna",
    instructions=f"Choose one type, colour and material from {banks}.",
    input=[{"role": "user", "content": [
        {"type": "input_image", "image_url": image_url},
    ]}],
    text={"format": label_schema},
)
labels = json.loads(response.output_text)
import json
from anthropic import Anthropic

client = Anthropic()
response = client.messages.create(
    model="claude-haiku-5-5",
    max_tokens=1024,  # adaptive thinking counts toward this
    system=f"Choose one type, colour and material from {banks}. Return JSON.",
    messages=[{"role": "user", "content": [
        {"type": "image", "source": {"type": "url", "url": image_url}},
    ]}],
)
text = next(block.text for block in response.content if block.type == "text")
labels = json.loads(text)
SIE
from sie_sdk import SIEClient

client = SIEClient(api_key="sk-sie-…")

vecs = client.encode("google/siglip-so400m-patch14-384", [
    {"images": [image]},
    *[{"text": text} for _, _, text in prompts],
])
ranked = rank_banks([vector["dense"] for vector in vecs])  # from the API example
labels = {field: result["selected"] for field, result in ranked.items()}
from sie_sdk import SIEClient

client = SIEClient(api_key="sk-sie-…")

vecs = client.encode("google/siglip-so400m-patch14-384", [
    {"images": [image]},
    *[{"text": text} for _, _, text in prompts],
])
ranked = rank_banks([vector["dense"] for vector in vecs])  # from the API example
labels = {field: result["selected"] for field, result in ranked.items()}
from sie_sdk import SIEClient

client = SIEClient(api_key="sk-sie-…")

vecs = client.encode("google/siglip-so400m-patch14-384", [
    {"images": [image]},
    *[{"text": text} for _, _, text in prompts],
])
ranked = rank_banks([vector["dense"] for vector in vecs])  # from the API example
labels = {field: result["selected"] for field, result in ranked.items()}

Contact us

Tell us about your use case and we'll get back to you shortly.

Apply for an inference grant

Free capacity on our hosted cluster for selected projects. Tell us what you run and we reply by email.