immich-app/immich · error · HTTPException

Either image or text must be provided

Error message

Either image or text must be provided

What it means

Raised as HTTPException(400) by the /predict endpoint when neither an 'image' nor a 'text' form field is present. The endpoint requires at least one of the two as the inference payload.

Source

Thrown at machine-learning/immich_ml/main.py:180

def ping() -> PlainTextResponse:
    return PlainTextResponse("pong")


@app.post("/predict", dependencies=[Depends(update_state)])
async def predict(
    entries: InferenceEntries = Depends(get_entries),
    image: bytes | None = File(default=None),
    text: str | None = Form(default=None),
) -> Any:
    if image is not None:
        decoded = await run(lambda: decode_pil(image))
        if decoded.width == 0 or decoded.height == 0:
            raise HTTPException(400, "Image has zero width or height")
        inputs: Image | str = decoded
    elif text is not None:
        inputs = text
    else:
        raise HTTPException(400, "Either image or text must be provided")
    response = await run_inference(inputs, entries)
    return ORJSONResponse(response)


async def run_inference(payload: Image | str, entries: InferenceEntries) -> InferenceResponse:
    outputs: dict[ModelIdentity, Any] = {}
    response: InferenceResponse = {}

    async def _run_inference(entry: InferenceEntry) -> None:
        model = await model_cache.get(
            entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl, **entry["options"]
        )
        inputs = [payload]
        for dep in model.depends:
            try:
                inputs.append(outputs[dep])
            except KeyError:
                message = f"Task {entry['task']} of type {entry['type']} depends on output of {dep}"

View on GitHub (pinned to 199723261c)

Solutions

  1. Ensure the request includes either a non-null image file or a text form field.
  2. Inspect the multipart body actually sent (DevTools/curl -v) to confirm the field names 'image' and 'text'.
  3. If doing CLIP textual search, send text even if empty-looking is not allowed — send an actual non-empty string, or attach an image.

Example fix

# before
curl -F 'entries=<json' http://ml/predict   # no image/text

# after
curl -F 'entries=<json' -F 'image=@photo.jpg' http://ml/predict
# or
curl -F 'entries=<json' -F 'text=a cat' http://ml/predict
Defensive patterns

Strategy: validation

Validate before calling

if image is None and (text is None or text == ''):
    raise HTTPException(400, 'Either image or text must be provided')

Type guard

def has_payload(image, text) -> bool:
    return image is not None or (text is not None and text != '')

Try / catch

try:
    resp = await run_inference(inputs, entries)
except HTTPException as e:
    if e.status_code == 400: client_error()
    raise

Prevention

When it happens

Trigger: POST /predict with both image and text omitted (e.g. only the 'entries' field supplied), or form-encoding bug that drops both fields.

Common situations: Custom client forgets to attach the file/text; a middleware or proxy strips multipart fields; smart-search sends text but it is empty string (note: empty string is still 'not None', so this fires only when the field is truly absent).

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/4eb0696a27428b84. Report an issue: GitHub.