BerriAI/litellm · error · ValueError

Max recursion depth reached while reading image bytes for…

Error message

Max recursion depth {max_depth} reached while reading image bytes for Black Forest Labs image edit.

What it means

LiteLLM's Black Forest Labs (BFL) image-edit transformer normalizes whatever you pass as `image` into raw bytes via a recursive helper `_read_image_bytes`. When the input is a list it recurses into the first element, and a depth counter (max_depth, default litellm DEFAULT_MAX_RECURSE_DEPTH = 100, overridable via the DEFAULT_MAX_RECURSE_DEPTH env var) guards against pathological nesting. This ValueError means the image argument was a list (or nested structure) nested more than 100 levels deep, so the library refuses to keep unwrapping it.

Solutions

  1. Log `type(image)` and its nesting depth before calling LiteLLM; anything beyond 1-2 list levels is a bug in your payload builder.
  2. Unwrap the image yourself and pass the innermost element directly: bytes, a file-like object, or an http(s) URL string.
  3. If a generic payload builder keeps re-wrapping in `[...]`, fix that loop/wrapper so it stores the image once instead of accumulating wrappers.
  4. As a last resort only, raise the guard by setting the env var DEFAULT_MAX_RECURSE_DEPTH above your real nesting depth (default 100) - but deep nesting is almost never legitimate.

Example fix

# before
image = [image]  # inside a retry loop -> img = [img]  # inside a retry loop -> img = [img]  # inside a retry loop -> nested 100+ deep
resp = litellm.image_edit(model="flux-kontext-pro", image=image, prompt="...")

# after
resp = litellm.image_edit(model="flux-kontext-pro", image=image, prompt="...")
Defensive patterns

Strategy: validation

Validate before calling

def normalize_bfl_image(image, max_list_depth: int = 3):
    depth = 0
    while isinstance(image, list):
        if not image:
            raise ValueError("empty image list")
        image = image[0]
        depth += 1
        if depth > max_list_depth:
            raise ValueError(f"image nested {depth} list-levels deep - fix payload builder")
    return image

image = normalize_bfl_image(image)  # before litellm.image_edit(...)

Type guard

def is_acceptable_image(x) -> bool:
    if isinstance(x, (bytes, bytearray)):
        return True
    if isinstance(x, str):
        return x.startswith(("http://", "https://"))
    return hasattr(x, "read") and callable(x.read)

Prevention

When it happens

Trigger: Calling the BFL image-edit route with an image argument that is a list nested >100 levels, e.g. iteratively wrapping payloads in code like `img = [img]` inside a retry/loop, or passing a deeply nested OpenAI-style content array `[[[[...[["https://..."] ...]]]]]` produced by a generic multimodal-payload builder.

Common situations: Frameworks that accumulate request history (chat-style message lists) and forward the whole structure as `image`; a bug where a wrapper prepends `[...]` on every attempt; feeding an OpenAI images-API-style array-of-partials that is itself nested by an upstream serialisation layer. Almost always a client-side payload-construction bug, not a BFL API issue.

Related errors


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

Appendix: source

Thrown at litellm/llms/black_forest_labs/image_edit/transformation.py:198

    ) -> str:
        """
        Get the complete URL for the Black Forest Labs API request.
        """
        base_url: str = api_base or get_secret_str("BFL_API_BASE") or DEFAULT_API_BASE
        base_url = base_url.rstrip("/")

        endpoint: Final = self._get_model_endpoint(model)
        return f"{base_url}{endpoint}"

    def _read_image_bytes(
        self,
        image: Any,
        depth: int = 0,
        max_depth: int = DEFAULT_MAX_RECURSE_DEPTH,
    ) -> bytes:
        """Read image bytes from various input types."""
        if depth > max_depth:
            raise ValueError(
                f"Max recursion depth {max_depth} reached while reading image bytes for Black Forest Labs image edit."
            )
        if isinstance(image, bytes):
            return image
        elif isinstance(image, list):
            # If it's a list, take the first image
            return self._read_image_bytes(image[0], depth=depth + 1, max_depth=max_depth)
        elif isinstance(image, str):
            if image.startswith(("http://", "https://")):
                response: Final = safe_get(litellm.module_level_client, image, timeout=60.0)
                response.raise_for_status()
                return response.content
            else:
                raise ValueError(
                    "Unsupported image input: plain string values that are not URLs are not accepted. "
                    "Provide image bytes or a file-like object."
                )
        elif hasattr(image, "read"):

View on GitHub (pinned to 77b7c6c40c)