---
title: "prompt_jev() now works on images"
canonical: "https://motherduck.com/blog/motherduck-announce-jev-image/"
---

# prompt_jev() now works on images

Three weeks ago we brought [prompt_jev()](https://motherduck.com/blog/motherduck-supports-jev/) to MotherDuck, a week after TypeSafe announced Jev. You ask a closed question in plain English, pick the shape of the answer (yes/no, one choice from a list, a score), and get a typed result with a confidence score, straight from SQL. We showed [five use cases](https://motherduck.com/blog/jev-analytics-use-cases/) on text columns.

Today, with TypeSafe's Jev 1.14, `prompt_jev` also **takes images**. Store them in a `BLOB` column, ask your questions, and the answers come back as columns you can filter, join and GROUP BY with the rest of your data.

And yes, we also asked seven models the only question that really matters: hot dog or not hot dog? On [hotdogbench.com](https://hotdogbench.com/#leaderboard), Jev isn't the top dog, but it's the first one to the bun.

## Try it in one query

Hugging Face hosts lots of public image datasets as Parquet, with each image stored as bytes. Read them with `hf://` and hand the bytes straight to Jev:

```sql
SELECT image.path AS file,
       prompt_jev([{name: 'dish', image: image.bytes}], 'Is `dish` a dessert?') AS is_dessert
FROM 'hf://datasets/ethz/food101/data/validation-00000-of-00003.parquet'
LIMIT 5;
```

| file        | is_dessert |
| ----------- | ---------: |
| 1827892.jpg |       0.72 |
| 2802124.jpg |       0.90 |
| 2192983.jpg |       0.90 |
| 1062952.jpg |       0.95 |
| 3157161.jpg |       0.93 |

Each row gets a probability between 0 and 1. These five photos are beignets, so they score high.

## How fast, how accurate, how cheap?

We used the same dataset: [Food-101](https://huggingface.co/datasets/ethz/food101), labeled photos of 101 dishes. One question per photo, "Which dish is shown?", with all 101 dishes as the allowed answers, asked to Jev, Gemini, GPT and Claude on the same 1,010 photos (10 per dish).

| Model                              | Rows/s | Accuracy | Cost for the 1,010 photos |
| ---------------------------------- | -----: | -------: | ------------------------: |
| **Jev (prompt_jev in MotherDuck)** | **44** |    90.2% |                **~$0.06** |
| Gemini 3.8 Flash, low thinking     |     10 |    96.8% |                     $1.14 |
| Gemini 3.8 Flash, default thinking |      7 |    97.0% |                     $1.43 |
| GPT 6.1 Sol                        |      7 |    93.4% |                     $1.89 |
| Claude Opus 5.5                    |      9 |    95.7% |                     $6.60 |

We can score accuracy here because Food-101 has the right answer for every photo. On your own data you won't have that, so the real question is which answers you can trust.

The big difference between Jev and a standard LLM is the **confidence score**. Every answer comes with one, and on this benchmark it reflects how often Jev is actually right. If you keep only the answers at 0.8 or more (`WHERE jev.confidence >= 0.8`), Jev is right **97.7%** of the time, on 80% of the photos.

| Min. confidence threshold | Photos kept | Accuracy on kept photos |
|---|---:|---:|
| (keep everything) | 100% | 90.2% |
| 0.5 | 94% | 92.7% |
| 0.7 | 86% | 95.8% |
| 0.8 | 81% | 97.7% |
| 0.9 | 73% | 98.5% |
| 0.95 | 65% | 98.9% |
| 0.99 | 50% | 99.2% |

This of course begs the question - what if we combine Jev with another model to a cheap first pass and then a secondary cleanup loop?

| Models to use | Accuracy | Cost per 1,000 photos |
|---|---:|---:|
| Gemini 3.8 Flash alone | 97.0% | $1.40 |
| **Jev + Gemini 3.8 Flash** | **96.5%** | **~$0.45** |
| Jev + Gemini Flash-Lite | 95.3% | ~$0.18 |
| Jev alone | 90.2% | $0.08 |

Notice how we can achieve big gains in accuracy for relatively small increases cost compared to using a System Two LLM for the entire job.

## Getting your images in

If your images sit in S3, one query loads them into a `BLOB` column (with an S3 secret in place):

```sql
CREATE TABLE images AS
SELECT filename AS path, content AS image
FROM read_blob('s3://my-bucket/photos/*.jpg');
```

Jev needs each side of an image between 280 and 1,568 pixels. Bigger images need a resize first: a [Flight](https://motherduck.com/docs/concepts/flights/) (Python that runs on MotherDuck compute) with [Pillow](https://pillow.readthedocs.io/en/stable/reference/Image.html) does it. The docs have a [ready-to-use example](https://motherduck.com/docs/sql-reference/motherduck-sql-reference/ai-functions/prompt-jev/#resize-images-before-you-send-them) that reads from S3, shrinks anything too big and writes the result back as a BLOB.

Or skip the code and ask the MotherDuck MCP server from Claude or your agent of choice:

> Load the JPEGs under s3://my-bucket/photos/ into a new table `photos.main.images`, one row per file with the image as a BLOB. Resize anything longer than 1568px on its longest side, skip images under 280px on their shortest side, and run it as a MotherDuck Flight. Tell me how many images landed and how many were skipped.

## Five centuries of art in one GROUP BY

We took the [Metropolitan Museum of Art's open access collection](https://github.com/metmuseum/openaccess), half a million CC0 catalog records, and kept every public-domain painting, print, drawing and photograph with an image: about 77k works, loaded as 600px BLOBs. Then we asked **14 questions about every image in one SQL query**. Here are 4 of them:

```sql
SELECT object_id,
       prompt_jev(
         [{name: 'art', image: image}],
         questions := {
           smile: {type: 'choice',
                   instructions: 'If a human face is clearly visible in `art`, what is the expression of the most prominent face?',
                   criteria: ['no_clear_face', 'neutral_or_serious', 'closed_mouth_smile', 'smile_showing_teeth', 'laughing']},
           dog: {type: 'noul', instructions: 'Is there a dog anywhere in `art`?'},
           dog_box: {type: 'box', instructions: 'the dog in `art`'},
           skull: {type: 'noul', instructions: 'Does `art` show a human skull or skeleton?'}
           -- plus 10 more: main subject, weather, sunset, drink, food, cat, ...
         }) AS jev
FROM images;
```

Once the answers are stored as columns, they join with the Met's catalog like any other data. When did people start smiling in art? One `GROUP BY`:

```sql
SELECT (floor(o.begin_year / 50) * 50)::INT AS half_century,
       round(100 * count(*) FILTER (WHERE f.smile IN ('closed_mouth_smile', 'smile_showing_teeth', 'laughing'))
                 / count(*) FILTER (WHERE f.smile <> 'no_clear_face')) AS pct_smiling
FROM features f  -- Jev's answers, one column per question
JOIN objects o USING (object_id)
WHERE o.begin_year BETWEEN 1400 AND 1949
  AND o.end_year - o.begin_year <= 25  -- only works the Met dates within 25 years
  AND NOT f.rejected
GROUP BY ALL
ORDER BY half_century;
```

![Share of visible faces that smile, by half-century](https://motherduck-com-web-prod.s3.us-east-1.amazonaws.com/assets/img/articles/motherduck-announce-jev-image/images/smiles_by_half_century_fd12512a88.webp)

Almost nobody smiles before 1600! Many reasons for [that](https://history.nebraska.gov/why-so-serious-3-reasons-why-you-never-see-a-smile-in-old-photographs/), but that's for another blog.

Explore the full dive below, or [open it in the dive gallery](https://motherduck.com/dive-gallery/dives/the-met-measured-five-centuries-of-art-one-group-by):

<iframe src="https://motherduck.com/dive-gallery/embed/the-met-measured-five-centuries-of-art-one-group-by"
  width="100%"
  height="900"
  style="border: none;"
  title="The Met, Measured: Five Centuries of Art, One GROUP BY"
  loading="lazy"
></iframe>

## Get started

Images are now just another column in MotherDuck. Load your product photos, scans, screenshots or camera-trap pictures as BLOBs, ask your questions, and join the answers with the rest of your data. 

Everything you need is in the [prompt_jev() docs](https://motherduck.com/docs/sql-reference/motherduck-sql-reference/ai-functions/prompt-jev/).
