{"record":{"id":"487f85b0dfbaa2f2","repo":"BerriAI/litellm","slug":"max-recursion-depth-max-depth-reached-while-read","errorCode":null,"errorMessage":"Max recursion depth {max_depth} reached while reading image bytes for Black Forest Labs image edit.","messagePattern":"Max recursion depth (.+?) reached while reading image bytes for Black Forest Labs image edit\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/black_forest_labs/image_edit/transformation.py","lineNumber":198,"sourceCode":"    ) -> str:\n        \"\"\"\n        Get the complete URL for the Black Forest Labs API request.\n        \"\"\"\n        base_url: str = api_base or get_secret_str(\"BFL_API_BASE\") or DEFAULT_API_BASE\n        base_url = base_url.rstrip(\"/\")\n\n        endpoint: Final = self._get_model_endpoint(model)\n        return f\"{base_url}{endpoint}\"\n\n    def _read_image_bytes(\n        self,\n        image: Any,\n        depth: int = 0,\n        max_depth: int = DEFAULT_MAX_RECURSE_DEPTH,\n    ) -> bytes:\n        \"\"\"Read image bytes from various input types.\"\"\"\n        if depth > max_depth:\n            raise ValueError(\n                f\"Max recursion depth {max_depth} reached while reading image bytes for Black Forest Labs image edit.\"\n            )\n        if isinstance(image, bytes):\n            return image\n        elif isinstance(image, list):\n            # If it's a list, take the first image\n            return self._read_image_bytes(image[0], depth=depth + 1, max_depth=max_depth)\n        elif isinstance(image, str):\n            if image.startswith((\"http://\", \"https://\")):\n                response: Final = safe_get(litellm.module_level_client, image, timeout=60.0)\n                response.raise_for_status()\n                return response.content\n            else:\n                raise ValueError(\n                    \"Unsupported image input: plain string values that are not URLs are not accepted. \"\n                    \"Provide image bytes or a file-like object.\"\n                )\n        elif hasattr(image, \"read\"):","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/black_forest_labs/image_edit/transformation.py#L180-L216","documentation":"_read_image_bytes normalizes diverse image inputs (bytes, list, URL string, file-like object) into raw bytes, recursing one level per list wrapper. A depth counter guards against pathologically nested lists; exceeding DEFAULT_MAX_RECURSE_DEPTH raises ValueError. In practice this almost always means an empty or unexpectedly nested structure (e.g. a list of lists with no bytes at the leaf) rather than genuine 10+ level nesting.","triggerScenarios":"Passing image as deeply nested lists, e.g. [[[...[b\"...\"]...]]], or a structure whose leaf elements are never bytes/str/file-like so each recursion only descends (an empty list would IndexError first, but nested single-element lists chain to the depth cap).","commonSituations":"Wrapping the image argument multiple times by mistake (e.g. image=[image_list] where image_list was already a list); passing a parsed JSON/AI-message content array instead of the media payload; programmatic construction of the image arg that accumulates wrappers across layers.","solutions":["Pass the image directly: raw bytes, a single URL string, or one file-like object.","If you must use a list, use exactly one level: image=[b\"...\"].","Inspect the value right before the call: print(type(image), len(image)) down the nesting to find where extra wrappers come from.","Fix the upstream code that keeps collecting the image into another list on every layer."],"exampleFix":"# before\nimg = load_bytes()\nlitellm.image_edit(model=..., image=[[[img]]], prompt=\"...\")\n\n# after\nimg = load_bytes()\nlitellm.image_edit(model=..., image=img, prompt=\"...\")","handlingStrategy":"validation","validationCode":"def flat_image(x, depth=0):\n    while isinstance(x, list):\n        if not x: raise ValueError(\"empty image list\")\n        x = x[0]; depth += 1\n        if depth > 5: raise ValueError(\"image wrapped in too many lists\")\n    return x\n\nimage = flat_image(image)","typeGuard":"from typing import Any\n\ndef is_recursion_safe_image(x: Any, depth: int = 0) -> bool:\n    if isinstance(x, (bytes,)) or (isinstance(x, str) and x.startswith((\"http://\",\"https://\"))) or hasattr(x, \"read\"):\n        return True\n    if isinstance(x, list) and x:\n        return depth < 5 and is_recursion_safe_image(x[0], depth + 1)\n    return False","tryCatchPattern":"null","preventionTips":["Never wrap the image argument in a list at more than one level.","In wrapper functions, accept one image parameter and pass it through unchanged.","Assert the leaf type (bytes/URL/file-like) before calling image_edit."],"tags":["bfl","image-input","validation","recursion"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}