# PROMPT_JEV
> Classify, score, test, and locate content in text and images with prompt_jev.
## prompt_jev function

The `prompt_jev` function sends text or named images and a question to a [TypeSafe](/integrations/data-science-ai/typesafe/) Jev model. It returns a typed decision: a probability, a choice from a fixed list, a position on an ordered scale, or a bounding box around an object. Every answer carries calibrated probabilities or confidence, so you can act on the decision and on how certain the model is about it.

`prompt_jev` answers closed questions. It never returns free text. Use [`prompt`](/sql-reference/motherduck-sql-reference/ai-functions/prompt/) when you need generated text, open-ended extraction, or a schema the model fills in itself.

Consumption is measured in [AI Units](/about-motherduck/billing/pricing#ai-function-pricing). Only input tokens are billed; output tokens are free. The following row estimates account for text, instructions, and question tokens:

| Question | Input length | Rows per AI Unit |
| :------- | :----------- | ---------------: |
| `noul` | 50 characters | ~345,000 |
| `choice` | 50 characters | ~260,000 |
| `multiple_choice` | 50 characters | ~90,000 |
| `choice`, `batch_size := 1` | 50 characters | ~60,000 |
| `choice` | 1,000 characters | ~60,000 |
| `score` | 1,000 characters | ~60,000 |
| `noul` and `box` | One 500 × 334 image | ~25,800 |

These estimates assume the default batch of 32 rows per request, with a fixed overhead of about 250 input tokens per request, shared across the rows in the batch. With `batch_size := 1`, each row carries the full overhead. They also assume 40-character instructions and four 10-character labels for `choice`, `multiple_choice`, and `score`. Batching and multiple questions change the input tokens used per source row, so text length alone doesn't determine the cost. Use billed input-token usage to estimate a workload.

Image token usage is based on image dimensions. An image uses approximately `2 × ceil(width / 28) × ceil(height / 28)` input tokens, regardless of file format, compressed size, or image contents.

## Syntax

```sql
prompt_jev(input, instructions)
prompt_jev(input, instructions, noul := criteria)
prompt_jev(input, instructions, choice := criteria)
prompt_jev(input, instructions, multiple_choice := criteria)
prompt_jev(input, instructions, score := criteria)
prompt_jev(images, box := instructions)
prompt_jev(input, questions := questions)
```

`input` and `instructions` are positional. `choice`, `score`, `noul`, `multiple_choice`, `box`, `questions`, and `batch_size` must be named:

```sql
SELECT prompt_jev(
    message,
    'Which team should handle this?',
    choice := ['billing', 'technical', 'sales']
)
FROM support_messages;
```

`choice`, `score`, `noul`, `multiple_choice`, and `box` are mutually exclusive, and none of them can be combined with `questions`. Omitting all five runs a `noul` question. `box` requires image input and supplies the instructions itself.

### Parameters

| **Parameter** | **Type** | **Required** | **Description** |
|---|---|---|---|
| `input` | `VARCHAR`, `BLOB`, `STRUCT(name VARCHAR, content BLOB[, format VARCHAR])`, or `STRUCT(name VARCHAR, image BLOB[, format VARCHAR])[]` | Yes | The text or named images to evaluate. This is the only argument that can vary per row. Must be the first argument. |
| `instructions` | `VARCHAR` | Conditional | The question the model answers. A non-empty constant. Required unless `questions` or `box` is supplied. |
| `noul` | `VARCHAR[]` or `STRUCT(label VARCHAR, description VARCHAR)[]` | No | Runs a yes/no question. The default when `choice`, `score`, `noul`, `multiple_choice`, and `box` are all omitted. If supplied, must contain exactly the labels `true` and `false`. |
| `choice` | `VARCHAR[]` or `STRUCT(label VARCHAR, description VARCHAR)[]` | Conditional | Between 2 and 255 unique, non-empty labels to pick from. |
| `multiple_choice` | `VARCHAR[]` or `STRUCT(label VARCHAR, description VARCHAR)[]` | Conditional | MotherDuck shorthand that evaluates one or more unique, non-empty labels independently. |
| `score` | `VARCHAR[]` or `STRUCT(label VARCHAR, description VARCHAR)[]` | Conditional | Between 2 and 10 unique, non-empty levels. Array position defines the order, lowest to highest. |
| `box` | `VARCHAR` | Conditional | Locates one target in an image. The value is the instructions describing the target. Requires image input. |
| `questions` | `STRUCT` or `JSON` | Conditional | Runs several questions against the same `input` in one request. See [Multiple questions](#multiple-questions). Not combined with `instructions`, `choice`, `score`, `noul`, `multiple_choice`, or `box`. |
| `batch_size` | `INTEGER` | No | How many non-`NULL` text input rows to send per request, from 1 to 64. Defaults to 32. Image requests aren't batched. See [Automatic batching](#automatic-batching). |

Every argument except `input` must be a constant for the query. MotherDuck validates them and resolves the return type while binding the query, before the first row is read.

Labels must be unique and non-empty. Descriptions may be `NULL` or a non-empty string:

```sql
-- Labels only
['low', 'medium', 'high']

-- Labels with optional descriptions
[
    {label: 'low', description: 'Can wait several days'},
    {label: 'medium', description: NULL},
    {label: 'high', description: 'Requires immediate action'}
]
```

Descriptions are input metadata that helps the model tell similar labels apart. They don't change the returned `value`, which is always the label.

### Image input

Pass a BLOB directly when a request has one JPEG image. The image gets the name `image`:

```sql
SELECT prompt_jev(
    image_blob,
    'Does `image` show a damaged package?'
) AS package_is_damaged
FROM package_photos;
```

Use a struct to choose the image name or specify its format:

```sql
-- JPEG is the default format
SELECT prompt_jev(
    {name: 'photo', content: image_blob},
    'Does `photo` show a damaged package?'
)
FROM package_photos;

-- Specify the format for PNG data
SELECT prompt_jev(
    {name: 'diagram', content: image_blob, format: 'png'},
    'Does `diagram` contain a flowchart?'
)
FROM diagrams;
```

Use a list of structs when a request has multiple images:

```sql
SELECT prompt_jev(
    [
        {name: 'photo', image: image_blob},
        {name: 'reference', image: reference_blob}
    ],
    'Does `photo` show the same product as `reference`?'
)
FROM product_photos;
```

All forms can vary per row. Each explicit `name` must be non-empty and unique within the row. The `content` and `image` fields contain raw PNG or JPEG file bytes as a BLOB, not base64-encoded text. Use DuckDB's [`read_blob`](https://duckdb.org/docs/current/guides/file_formats/read_file#read_blob) function to read image files. Use lowercase `jpg`, `jpeg`, or `png` for `format`; omitting the field defaults to `jpg`.

Refer to an image in instructions or criterion descriptions by putting its name in backticks. A named image can be referenced in any question text, for example in a choice description:

```sql
SELECT prompt_jev(
    [
        {name: 'photo', image: image_blob},
        {name: 'reference', image: reference_blob}
    ],
    'How does `photo` compare with the reference?',
    choice := [
        {
            label: 'same',
            description: 'Shows the same product as `reference`'
        },
        {
            label: 'different',
            description: 'Shows a different product from `reference`'
        }
    ]
) AS comparison
FROM product_photos;
```

#### Load an image from a URL

`read_blob` accepts HTTP and HTTPS URLs, as well as globs and S3 paths, and returns each file's bytes in its `content` column as a `BLOB`. Pass `content` as the image, with no separate download step:

```sql
SELECT prompt_jev(
    content,
    'Does `image` show a duck?'
)
FROM read_blob('https://thumb.wikimedia.org/wikipedia/commons/thumb/2/28/Duck-293474.jpg/960px-Duck-293474.jpg');
```

#### Load images from Hugging Face

The following query loads an image from the [Oxford Pet dataset](https://huggingface.co/datasets/timm/oxford-iiit-pet) directly from Hugging Face and locates the animal. It uses the [Hugging Face extension](/integrations/cloud-storage/hugging-face/), which is supported in MotherDuck.

```sql
SELECT prompt_jev(
    {name: 'pet_photo', content: image.bytes},
    box := 'the animal in `pet_photo`'
) AS animal_location
FROM read_parquet(
    'hf://datasets/timm/oxford-iiit-pet@~parquet/default/test/*.parquet'
)
WHERE image_id = 'newfoundland_31';
```

```text
{
  box: {top: 0.11, bottom: 1.0, left: 0.28, right: 0.75},
  confidence: 0.89
}
```

### Question types

Each question type has its own return type.

#### noul

Answers "is this true?" and returns a `DOUBLE` between 0 and 1: the calibrated probability that the statement in `instructions` holds for `input`.

```sql
SELECT prompt_jev(
    'The payment has failed for three days and nobody has replied.',
    'Does this describe an urgent problem?'
) AS urgency;
```

```text
0.93
```

Pass `noul` with explicit labels only to attach descriptions to `true` and `false`; the labels-only form `noul := ['true', 'false']` is equivalent to omitting `noul` entirely:

```sql
SELECT prompt_jev(
    message,
    'Does this request a refund?',
    noul := [
        {label: 'true', description: 'The customer asks for money back'},
        {label: 'false', description: 'No refund is requested'}
    ]
) AS refund_probability;
```

#### choice

Answers "which of these options?" and returns a `STRUCT`:

```text
STRUCT(
    choice        VARCHAR,
    probabilities STRUCT(value VARCHAR, probability DOUBLE)[],
    confidence    DOUBLE
)
```

- `choice` is the label with the highest probability.
- `probabilities` has one entry per label and sums to 1. The entries are sorted by label, not by the order you supplied or by probability, so match on `value` rather than on position.
- `confidence` is between 0 and 1 and describes how certain the model is about the winning option.

The labels compete for a fixed amount of probability, so adding a label takes probability away from the others. Use [`multiple_choice`](#multiple_choice) when more than one label can be true at the same time.

```sql
SELECT prompt_jev(
    'I was charged twice for last month and the invoice does not match.',
    'Which team should handle this?',
    choice := ['billing', 'technical', 'sales']
) AS routing;
```

```text
{'choice': billing, 'probabilities': [{'value': billing, 'probability': 0.94}, {'value': sales, 'probability': 0.02}, {'value': technical, 'probability': 0.04}], 'confidence': 0.91}
```

Descriptions help the model disambiguate labels that read as similar:

```sql
SELECT prompt_jev(
    message,
    'Which team should handle this?',
    choice := [
        {label: 'billing', description: 'Payments, invoices, and refunds'},
        {label: 'technical', description: 'Errors, outages, and integrations'},
        {label: 'sales', description: 'Pricing, trials, and upgrades'}
    ]
) AS routing
FROM support_messages;
```

#### score

Answers "which level?" against an ordered rubric and returns a `STRUCT`:

```text
STRUCT(
    score         DOUBLE,
    probabilities STRUCT(index UINTEGER, value VARCHAR, probability DOUBLE)[],
    confidence    DOUBLE
)
```

- `score` is a weighted position on the scale, between `0` and `length(score) - 1`. A rubric of `['low', 'medium', 'high']` returns a value between 0 and 2, and a value of `1.4` sits between `medium` and `high`.
- `probabilities` has one entry per level, ordered by `index`, with `index` the zero-based position of the level and `value` its label.
- `confidence` is between 0 and 1.

Order the levels from lowest to highest. The array position defines the scale.

```sql
SELECT prompt_jev(
    'The product is unusable and we are considering cancelling.',
    'Rate the severity of this message.',
    score := ['low', 'medium', 'high']
) AS severity;
```

```text
{'score': 1.82, 'probabilities': [{'index': 0, 'value': low, 'probability': 0.02}, {'index': 1, 'value': medium, 'probability': 0.14}, {'index': 2, 'value': high, 'probability': 0.84}], 'confidence': 0.88}
```

#### multiple_choice

Answers "which of these options apply?" and returns a `STRUCT`:

```text
STRUCT(
    choices       VARCHAR[],
    probabilities STRUCT(value VARCHAR, probability DOUBLE)[]
)
```

`multiple_choice` isn't a Jev question type. MotherDuck expands each candidate into an independent `noul` question and combines the answers:

```text
<instructions>
Does the following candidate apply independently of the other candidates?
Candidate: <label>
Description: <description>
```

Each candidate gets its own 0-to-1 probability, so unlike `choice` the probabilities don't sum to 1, every label can score high, and no label has to. `choices` contains the labels whose probability is greater than 0.5, so it can be empty. `probabilities` contains every label, in the order you supplied them. There's no `confidence` field, because a `noul` answer doesn't carry one.

Write `instructions` as the shared framing for every candidate. Pass `{label, description}` structs when two labels read alike; the description lands in that candidate's question only.

```sql
WITH classified AS MATERIALIZED (
    SELECT prompt_jev(
        product_description,
        'Which dietary traits apply to this product?',
        multiple_choice := [
            'Dairy-Free',
            'Contains Dairy',
            'Vegan',
            'Lactose-Free',
            'Contains Nuts'
        ]
    ) AS result
    FROM products
)
SELECT
    result.choices,
    [p.value FOR p IN result.probabilities IF p.probability > 0.75] AS high_probability_choices
FROM classified;
```

Use the complete `probabilities` list to apply a cutoff other than 0.5. Because `multiple_choice` is MotherDuck-provided shorthand rather than a Jev question type, it can't be used as a `type` inside [`questions`](#multiple-questions).

The label set is fixed at bind time, so the model can only return labels you supplied. To pull values you can't enumerate up front, use [`prompt`](/sql-reference/motherduck-sql-reference/ai-functions/prompt/) with a `json_schema` instead.

`instructions` is repeated once per candidate in the request, so input tokens grow with the number of labels. A six-label `multiple_choice` call sends `instructions` six times, where the `choice` call it replaces sends it once.

#### box

Locates one target in an image and returns a `STRUCT`:

```text
STRUCT(
    box STRUCT(
        top    DOUBLE,
        bottom DOUBLE,
        left   DOUBLE,
        right  DOUBLE
    ),
    confidence DOUBLE
)
```

Each edge is a value from 0 to 1, measured from the top-left corner. `left` and `right` are fractions of the image width; `top` and `bottom` are fractions of the image height. `confidence` is between 0 and 1.

Pass the target description as the named `box` argument:

```sql
SELECT prompt_jev(
    [{name: 'photo', image: image_blob}],
    box := 'the coffee mug in `photo`'
) AS mug_location
FROM kitchen_photos;
```

Each Box question returns one box. It has no "not found" result, so it returns coordinates even when the target is absent. Add a `noul` question in [`questions`](#multiple-questions) when you need to test whether the target is present before using the box.

### Which type to use

TypeSafe groups decisions into task categories. Each maps onto one of the available question types.

| **Task** | **Type** | **Example** |
|---|---|---|
| Classification | `choice` | Route a ticket to a team, label a topic, pick a risk category |
| Detection | `noul` | Flag spam, fraud, or a prompt injection attempt |
| Scoring | `score` | Rate severity, content quality, or customer frustration |
| Ranking | `score` or `noul` | Order candidates by semantic fit using the returned number |
| Verification | `noul` | Check a citation, a policy rule, or an answer for a known failure mode |
| Structured extraction | `choice` or `score` per field | One [`questions`](#multiple-questions) call per row, where the possible values are known up front |
| Multi-label classification | `multiple_choice` | Apply zero or more traits or tags |
| Object location | `box` | Locate an animal, product, signature, or other target in an image |

For extraction where the values are not known up front, use [`prompt`](/sql-reference/motherduck-sql-reference/ai-functions/prompt/) with a `struct` or `json_schema` instead.

## Multiple questions

Ask several questions about the same `input` in a single request with `questions`. Every question needs a non-empty `instructions` string, a `type` of `noul`, `choice`, `score`, or `box`, and valid `criteria` when its type requires them:

```sql
SELECT prompt_jev(
    message,
    questions := {
        refund: {
            type: 'noul',
            instructions: 'Does this request a refund?'
        },
        category: {
            type: 'choice',
            instructions: 'Classify the primary subject.',
            criteria: ['billing', 'technical', 'other']
        },
        urgency: {
            type: 'score',
            instructions: 'How urgent is this?',
            criteria: ['low', 'medium', 'high']
        }
    }
) AS answer
FROM support_messages;
```

`multiple_choice` isn't a valid question `type`. To evaluate several independent labels inside `questions`, add one `noul` question per label.

`box` requires image input and doesn't accept `criteria`. A request can mix Box questions with all other question types, as shown under [Image input](#image-input).

The result is a named `STRUCT` with one field per question, each in its question type's normal return shape:

```sql
SELECT
    answer.refund,
    answer.category.choice,
    answer.category.confidence,
    answer.urgency.score
FROM classified;
```

The request carries `input` once for the whole set, so asking twenty things at once costs far fewer input tokens than twenty single-question queries over the same table. MotherDuck can also batch several input rows into one `questions` request.

### JSON escape hatch

Pass `questions` as a `JSON` string to reach parts of the TypeSafe API that the native `STRUCT` form doesn't cover, such as structured or omitted `instructions`, or criteria described with arbitrary nested JSON:

```sql
SELECT prompt_jev(
    message,
    questions := '{
      "category": {
        "type": "choice",
        "instructions": {
          "question": "Classify the primary subject",
          "focus": ["primary intent", "requested action"]
        },
        "criteria": {
          "billing": {
            "description": "Charges and invoices",
            "examples": ["duplicate charge", "missing invoice"]
          },
          "technical": "Bugs and integrations"
        }
      }
    }'::JSON
) AS answer
FROM support_messages;
```

MotherDuck inspects the constant JSON only to determine the SQL return type; the question configuration itself is passed to TypeSafe for validation. For `score` criteria described as arbitrary JSON, `probabilities` comes back as `JSON` rather than the native `STRUCT(index, value, probability)[]` shape. Native `score` criteria — a plain array of labels or label/description structs — always return `probabilities` in the native shape with labels as `VARCHAR`.

A TypeSafe validation failure, including an HTTP 422 response, fails the query as an error rather than returning `NULL` for the row.

## Automatic batching

Calls with text input automatically batch rows into fewer requests, including `multiple_choice` and `questions` calls. The default is 32 rows per request. The optional `batch_size` argument can be used to adjust the batch size:

```sql
SELECT prompt_jev(
    message,
    'Does this request a refund?',
    batch_size := 16
)
FROM messages;
```

`batch_size` must be between 1 and 64. With the default, MotherDuck sends a smaller batch when a request would otherwise exceed [Jev's context limit](https://docs.typesafe.ai/models), which is 64k tokens for the input and questions combined. An explicit `batch_size` disables that adjustment, and a request over the limit returns `NULL` for every row it carried.

Larger batches improve throughput and lower cost. Batching can introduce extra variance from sharing a context window across multiple rows, though the effect is typically small. For strict per-row isolation, set `batch_size := 1`.

Image requests aren't batched, and the `batch_size` parameter is not supported. A request can contain several named images and several questions from the same row.

## Image requirements

Each image passed to `prompt_jev` must meet these requirements:

- Use a still PNG or JPEG image. Animated PNG images and other formats are rejected.
- Keep the combined size of all image BLOBs in a single request at or below 8 MiB. SQL constant image input must be below 4 MiB; read larger inputs from a column or with `read_blob`.
- Keep both width and height between 280 and 1,568 pixels.
- Keep the total area between 78,400 and 2,458,624 pixels.
- Rotate the image pixels upright. TypeSafe removes metadata and doesn't apply embedded orientation data.
- Resize or convert the image before sending it. The API doesn't resize images.

See [TypeSafe's image documentation](https://docs.typesafe.ai/images) for the complete limits and preparation guidance.

### Resize images before you send them

The API doesn't resize images, so an image outside the limits above returns `NULL` for its row. Phone and camera photos often run past 1,568 pixels on a side, and a few of them in one request can pass 8 MiB. Many also store their rotation as EXIF metadata, which TypeSafe ignores, so the model sees them sideways or upside down. Shrinking oversized images keeps cost down too, because an image's input tokens grow with its width and height.

A [Flight](/concepts/flights/) can prepare the images on MotherDuck compute. The following Flight reads each file with `read_blob`, uses [Pillow](https://pillow.readthedocs.io/) to rotate the pixels upright and shrink the image to fit within 1,568 pixels on each side, and stores the result as JPEG bytes in the `BLOB` column of an `images` table. It skips images whose short side is under 280 pixels after resizing instead of upscaling them.

Create an [S3 secret](/integrations/cloud-storage/amazon-s3/#configure-s3-credentials) for the source bucket before you run the Flight. Without one, reading the bucket can fail with `Missing or invalid credentials`, even when the bucket is public. Replace `my_db` and `s3://my-bucket/photos/*.jpg` with your database and image path, then pass the two files as `requirements_txt` and `source_code` to [`MD_CREATE_FLIGHT`](/sql-reference/motherduck-sql-reference/flights/md-create-flight/).

`requirements.txt`:

```text
duckdb==1.5.6
pyarrow==17.0.0
pillow==11.3.0
```

Flight source:

```python
import io

import duckdb
import pyarrow as pa
from PIL import Image, ImageOps

MAX_SIDE = 1568  # Jev's upper limit per side
MIN_SIDE = 280   # and its lower limit

def resize(data: bytes) -> bytes | None:
    img = ImageOps.exif_transpose(Image.open(io.BytesIO(data))).convert("RGB")  # rotate pixels upright
    img.thumbnail((MAX_SIDE, MAX_SIDE))  # keeps the aspect ratio, only ever shrinks
    if min(img.size) < MIN_SIDE:
        return None  # too small for Jev, skip it
    out = io.BytesIO()
    img.save(out, "JPEG", quality=85)
    return out.getvalue()

def main():
    con = duckdb.connect("md:my_db")
    con.execute("CREATE TABLE IF NOT EXISTS images (path VARCHAR, image BLOB)")
    source = con.cursor().execute("SELECT filename, content FROM read_blob('s3://my-bucket/photos/*.jpg')")
    while batch := source.fetchmany(500):
        rows = [(path, resize(data)) for path, data in batch]
        rows = [(path, img) for path, img in rows if img is not None]
        chunk = pa.table({
            "path": pa.array([path for path, _ in rows], pa.string()),
            "image": pa.array([img for _, img in rows], pa.binary()),
        })
        con.register("chunk", chunk)
        con.execute("INSERT INTO images SELECT * FROM chunk")
        con.unregister("chunk")

if __name__ == "__main__":
    main()
```

The stored images are JPEG, the default `format`, so each image struct needs only a name and the `BLOB`. A `noul` question returns one `DOUBLE` probability per row:

```sql
SELECT
    path,
    prompt_jev(
        [{name: 'photo', image: image}],
        'Is there a person in `photo`?'
    ) AS has_person
FROM images;
```

## Example usage

### Build an analytical dimension from free text

Classification turns a text column into a column you can group by. Declare the CTE `AS MATERIALIZED`: without it, DuckDB may re-evaluate the CTE, and call the model again, for every field you read from the result.

```sql
WITH classified AS MATERIALIZED (
    SELECT prompt_jev(
        resolution,
        'What outcome does this closure note document?',
        choice := ['fixed', 'no_issue_found', 'access_failed', 'referred', 'unclear']
    ) AS result
    FROM complaints
)
SELECT
    result.choice AS outcome,
    count(*) AS complaints
FROM classified
GROUP BY outcome
ORDER BY complaints DESC;
```

| **outcome** | **complaints** |
|---|---|
| fixed | 420 |
| no_issue_found | 280 |
| access_failed | 160 |
| referred | 90 |
| unclear | 50 |

### Filter rows with a probability

A `noul` question returns a plain `DOUBLE`, so it works in a `WHERE` or `QUALIFY` clause. Materialize the score first so the model runs once per row rather than once per predicate evaluation.

```sql
CREATE TABLE refund_requests AS
SELECT
    message_id,
    body,
    prompt_jev(body, 'Does this message request a refund?') AS refund_probability
FROM support_messages;

SELECT message_id, body
FROM refund_requests
WHERE refund_probability > 0.8;
```

### Read the probability distribution

Unnest `probabilities` when you need the full distribution rather than the winning answer.

```sql
WITH routed AS MATERIALIZED (
    SELECT prompt_jev(
        body,
        'Which team should handle this?',
        choice := ['billing', 'technical', 'sales']
    ) AS routing
    FROM support_messages
)
SELECT
    p.value AS team,
    avg(p.probability) AS mean_probability
FROM routed, unnest(routing.probabilities) AS t(p)
GROUP BY team
ORDER BY mean_probability DESC;
```

### Store results in a table

Write results to a table so later queries read the stored value instead of calling the model again.

```sql
ALTER TABLE support_messages ADD COLUMN severity STRUCT(
    score DOUBLE,
    probabilities STRUCT(index UINTEGER, value VARCHAR, probability DOUBLE)[],
    confidence DOUBLE
);

UPDATE support_messages
SET severity = prompt_jev(
    body,
    'Rate the severity of this message.',
    score := ['low', 'medium', 'high']
)
WHERE severity IS NULL;
```

## Composing text input

`input` is a single text value. To evaluate several fields together, concatenate them into one string with labels that tell the model what each part is.

```sql
SELECT prompt_jev(
    'Subject: ' || subject || E'\n' ||
    'Plan: ' || plan_name || E'\n' ||
    'Message: ' || body,
    'Does this customer describe a billing problem?'
) AS billing_problem
FROM support_messages;
```

Keep everything in `input` relevant to the question. Unrelated text dilutes the signal.

## Error handling

Argument problems fail the query while it's being bound, before any row is processed:

| **Error** | **Cause** |
|---|---|
| `prompt_jev requires instructions` | `instructions` was omitted and neither `questions` nor `box` was supplied |
| `prompt_jev "choice", "score", and "noul" cannot be combined` | More than one of `choice`, `score`, or `noul` was supplied |
| `prompt_jev requires at least two criteria for type "choice"` | Fewer than two labels for `choice` |
| `prompt_jev requires at least two criteria for type "score"` | Fewer than two levels for `score` |
| `prompt_jev Noul criteria require exactly the labels "true" and "false"` | `noul` was supplied with labels other than `true`/`false` |
| `prompt_jev "multiple_choice" requires at least one criterion` | No labels were supplied for `multiple_choice` |
| `prompt_jev "multiple_choice" cannot be combined with choice, score, noul, or questions` | `multiple_choice` was supplied alongside another question type |
| `prompt_jev criteria labels must be unique` | `choice`, `score`, `noul`, or `multiple_choice` contains a duplicate label |
| `prompt_jev "box" requires image input` | `box` was used with text input |
| `prompt_jev Box questions require image input` | A `box` question in `questions` was used with text input |
| `prompt_jev "batch_size" is not supported with image input` | `batch_size` was set for image input, which always sends one request per row |
| `prompt_jev "instructions" parameter must be a constant value` | An argument other than `input` references a column |
| `AI functions are not available for your organization.` | The organization is on the Free plan, has AI functions disabled, or has asked [support](/troubleshooting/support/) to disable `prompt_jev` on request |

Per-row failures do not fail the query. A row returns `NULL` when:

- `input` is `NULL`. No request is sent for that row.
- An image is invalid or outside the supported dimensions and size limits.
- The request exceeds Jev's context limit. Every row in that request returns `NULL`. See [Automatic batching](#automatic-batching).
- The request still fails after retries, or the query hits the AI function timeout.

A TypeSafe validation failure is the exception: it fails the query rather than returning `NULL`. MotherDuck validates native arguments while binding, so this reaches you from question configuration it doesn't check itself. See [JSON escape hatch](#json-escape-hatch).

Check for `NULL` to find rows that did not get an answer, and fill them in with a second pass:

```sql
SELECT count(*)
FROM support_messages
WHERE severity IS NULL AND body IS NOT NULL;
```

## Notes

These capabilities are provided by MotherDuck's integration with TypeSafe. Text and images you pass as `input`, along with your `instructions` and criteria, are sent to the TypeSafe API for processing. See the [TypeSafe integration page](/integrations/data-science-ai/typesafe/) for what MotherDuck sends and how to turn the function off for your organization.

Text input runs against the `jev-latest` model alias. Image input runs against `jev-1.14.0`. There is no model parameter.

A valid response can still be an incorrect decision. See [Jev model limitations](/integrations/data-science-ai/typesafe/#known-limitations) before selecting tasks and confidence thresholds.

Admins can change the AI session cap on the [AI Features](https://app.motherduck.com/settings/ai-features) settings page.


---

## Docs feedback

MotherDuck accepts optional user-submitted feedback about this page at `GET https://motherduck.com/docs/api/feedback/agent`.
For agents and automated tools, feedback submission should be user-confirmed before sending.

URL-encode query parameter values and send a GET request:

```text
GET https://motherduck.com/docs/api/feedback/agent?page_path=%2Fsql-reference%2Fmotherduck-sql-reference%2Fai-functions%2Fprompt-jev%2F&page_title=PROMPT_JEV&text=<url-encoded user feedback, max 2000 characters>
```

Optionally append `&source=<url-encoded interface identifier>` such as `claude.ai` or `chatgpt`.

`page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": "<uuid>"}`, `400` for malformed query parameters, and `429` when rate-limited.
