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
- Log `type(image)` and its nesting depth before calling LiteLLM; anything beyond 1-2 list levels is a bug in your payload builder.
- Unwrap the image yourself and pass the innermost element directly: bytes, a file-like object, or an http(s) URL string.
- If a generic payload builder keeps re-wrapping in `[...]`, fix that loop/wrapper so it stores the image once instead of accumulating wrappers.
- 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
- Never wrap the image in fresh lists inside loops or retries; store the original reference.
- Assert the image argument is bytes/file-like/http(s)-URL right before the API call.
- Keep multimodal payload builders from forwarding whole chat-style nested arrays as `image`.
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
- Unsupported image input: plain string values that are not…
- Unsupported image type
- Error parsing BFL response
- No image URL in BFL result
- BFL error
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)