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.

Categorical values go in 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:

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:

Use a plain Text or Image function instead when: