Structured Data & Multimodal Functions
Most Nyckel examples classify one thing: a string, or an image. A Tabular function classifies a row — several named fields evaluated together.
This matters for two reasons. First, some decisions can’t be made from a single field: a listing might depend on its title and its price and its photo, with no one of them sufficient on its own. When that’s the case, Tabular is the right shape — and when it isn’t, a plain Text or Image function is simpler. Second, a Tabular field can be an Image, which means one function can reason over pictures and structured attributes at the same time. That’s what “multimodal” means here — it isn’t a separate function type, it’s a Tabular function with an Image field in it.
Everything else is unchanged. Tabular is an input modality; the output is still
Classification, and the invoke → annotate → retrain loop works exactly as it does for text and
images.
Defining the schema
Set input to Tabular when you create the function, then declare fields.
curl -X POST 'https://www.nyckel.com/v1/functions' \
-H "Authorization: Bearer $NYCKEL_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "listing-quality",
"input": "Tabular",
"output": "Classification"
}'
Each field has a name and a type. There are three field types:
| Field type | Accepts | Sent as |
|---|---|---|
Text |
Any string — titles, descriptions, free-form notes, categorical values | JSON string |
Number |
Numeric attributes — price, count, rating, age, elapsed time | JSON number |
Image |
An internet-accessible image URL, or a data URI | JSON string |
curl -X POST "https://www.nyckel.com/v1/functions/$NYCKEL_FN/fields" \
-H "Authorization: Bearer $NYCKEL_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "name": "title", "type": "Text" }'
{ "id": "<fieldId>", "name": "title", "type": "Text" }
Repeat for each field. List Fields returns them with their ids.
Text fields as their literal strings —
"condition": "refurbished". Don't one-hot encode them yourself; that's the model's job.
Invoking
The data payload is a JSON object rather than a scalar. Keys may be either field ids or field
names.
curl -X POST "https://www.nyckel.com/v1/functions/$NYCKEL_FN/invoke?externalId=listing-8814" \
-H "Authorization: Bearer $NYCKEL_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"data": {
"title": "Vintage leather armchair, mid-century",
"price": 420,
"condition": "used - good",
"photo": "https://example.com/listings/8814.jpg"
}
}'
{
"labelName": "Ready to publish",
"labelId": "<labelId>",
"confidence": 0.88
}
Field names are convenient while you’re prototyping; field ids are stable if you later rename a field. See Id conventions.
Adding a field later
Unlike input and output modality, the field set is not frozen — you can POST a new field to an
existing function. Samples collected before the field existed simply don’t carry a value for it, and
the model works with what each sample has.
Adding a genuinely informative field can lift accuracy on a function that has plateaued, and it’s worth trying before you go collect more samples. Adding an uninformative field mostly just adds noise, so add them one at a time and watch the accuracy reported by Get Function Summary.
Pattern: scoring and ranking
A classifier returns one label plus a confidence. That confidence is a calibrated probability, which means a Tabular classifier can be used as a scorer — not just a sorter.
The pattern: model the thing you want to score as one row, classify it against an outcome you can observe, and rank by the confidence of the positive label.
row: { user attributes… , item attributes… , context attributes… }
labels: "engaged" | "not engaged"
score: confidence of "engaged"
To rank a candidate set, invoke once per candidate and sort by that confidence. Then close the loop: when you find out what the user actually did, annotate that sample accordingly. The function’s notion of “engaged” tracks your users’ actual behaviour instead of a snapshot from whenever the model was last built — which is how a hand-rolled scoring model goes stale.
Two things to weigh before you build this way:
- One invoke per candidate. Ranking 200 candidates is 200 calls. Narrow the candidate set with cheaper logic first, and use Nyckel to rank the shortlist. See Prediction endpoints for latency characteristics.
- The label has to be observable. The loop only improves if you can eventually tell the function what the right answer was. If the outcome you care about is never measured, annotations dry up and the function stops improving.
If what you actually need is “find the items most similar to this one,” a Search function is the better fit — it’s built for nearest-neighbour retrieval and doesn’t need a labelled outcome.
When to reach for Tabular
Good fit:
- The decision genuinely depends on several fields, and no single one is sufficient.
- You have a mix of types — a photo and a price and a description.
- You want a score that stays current as behaviour shifts.
Use a plain Text or Image function instead when:
- One field carries essentially all the signal. A Tabular function with a single
Textfield is just a text classifier with extra setup. - Your fields are all numeric and the relationship is one you could write down. A rule or a regression is cheaper, faster, and easier to debug.
Related
- API Quickstart — the five-call loop, in
curl. - Function types — classification, detection, OCR, and search.
- Training & accuracy — how retraining and promotion work.
- Create a Field — full field API reference.