BerriAI/litellm · error · ValueError

Max recursion depth {max_depth} reached while reading image

Error message

Max recursion depth {max_depth} reached while reading image bytes for Vertex AI Imagen image edit.

What it means

Imagen edit's _read_all_bytes recursively unwraps structured image input — lists/tuples take the first non-None item, dicts are probed for 'data'/'bytes'/'content' (base64-decoded) then 'path'. A depth guard caps this recursion at DEFAULT_MAX_RECURSE_DEPTH (100 by default, overridable via the DEFAULT_MAX_RECURSE_DEPTH env var) to avoid pathological structures. Only inputs nested deeper than that cap raise this ValueError.

Source

Thrown at litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py:290

            mask_bytes: Final = self._read_all_bytes(mask_image)
            mask_base64: Final = base64.b64encode(mask_bytes).decode("utf-8")

            mask_reference: Final = {
                "referenceType": "REFERENCE_TYPE_MASK",
                "referenceId": len(reference_images) + 1,
                "referenceImage": {"bytesBase64Encoded": mask_base64},
                "maskImageConfig": {
                    "maskMode": "MASK_MODE_USER_PROVIDED",
                    "dilation": 0.03,  # Default dilation value (not configurable via OpenAI API)
                },
            }
            reference_images.append(mask_reference)

        return reference_images

    def _read_all_bytes(self, image: Any, depth: int = 0, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH) -> bytes:
        if depth > max_depth:
            raise ValueError(
                f"Max recursion depth {max_depth} reached while reading image bytes for Vertex AI Imagen image edit."
            )

        if isinstance(image, (list, tuple)):
            for item in image:
                if item is not None:
                    return self._read_all_bytes(item, depth=depth + 1, max_depth=max_depth)
            raise ValueError("Unsupported image type for Vertex AI Imagen image edit.")

        if isinstance(image, dict):
            for key in ("data", "bytes", "content"):
                if key in image and image[key] is not None:
                    value = image[key]
                    if isinstance(value, str):
                        try:
                            return base64.b64decode(value)
                        except Exception:
                            continue

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Flatten the input before the call — reduce it to bytes, BytesIO, or a shallow {'data': '<base64>'} dict
  2. Find and fix the wrapping loop that adds a layer per iteration (the real bug is upstream)
  3. As a last resort for legitimately deep structures, raise the cap via env var DEFAULT_MAX_RECURSE_DEPTH=200

Example fix

# before: loop adds a layer per retry
for attempt in range(retries):
    payload = [payload]  # grows nesting each pass
resp = litellm.image_edit(model='vertex_ai/imagen-...', prompt=p, image=payload)

# after: send a flat payload
payload = unwrap_to_bytes(payload)  # collapse to bytes once
resp = litellm.image_edit(model='vertex_ai/imagen-...', prompt=p, image=payload)
Defensive patterns

Strategy: validation

Validate before calling

def nesting_depth(v, d=0):
    if isinstance(v, (list, tuple)) and v:
        return nesting_depth(v[0], d + 1)
    return d

assert nesting_depth(image) < 100, 'image payload suspiciously nested; flatten to bytes'

Try / catch

try:
    resp = litellm.image_edit(model='vertex_ai/imagen-...', prompt=p, image=image)
except ValueError as e:
    if 'Max recursion depth' in str(e):
        raise ValueError('image payload nesting bug — flatten to bytes before calling') from e
    raise

Prevention

When it happens

Trigger: image=[[[[ ... ]]]] with more than 100 nesting levels; a dict chain like {'data': {'bytes': {'content': ...}}} deeper than 100; usually generated by buggy glue code that re-wraps images in a loop (e.g. image = [image] applied repeatedly).

Common situations: Loop bugs that wrap payloads one more layer per iteration; data pipelines that forward arbitrary user JSON as the image field; test fixtures generated recursively.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/db7e7d449799692b. Report an issue: GitHub.