BerriAI/litellm · error · ValueError

Gemini image edit requires at least one image.

Error message

Gemini image edit requires at least one image.

What it means

Request-shape validation in GeminiImageEditConfig.transform_image_edit_request. Gemini image edit inlines the input image(s) as base64 inlineData parts; if the image argument is None, an empty list, or a list whose entries are all None, the inline parts list is empty and this ValueError is raised before any HTTP call.

Solutions

  1. Always pass image as bytes, BytesIO, BufferedReader, or a non-empty list of those (OpenAI-style path strings/tuples are NOT accepted — see the unsupported-image-type error)
  2. Validate at the API boundary: reject requests without an image before calling litellm
  3. If the image is optional in your product, branch to litellm.image_generation (prompt-only) instead of image_edit
  4. Log the received image type/size at the edge to catch empty uploads early

Example fix

# before
resp = litellm.image_edit(model='gemini/gemini-2.5-flash-image', prompt='remove background')
# ValueError: Gemini image edit requires at least one image.

# after - enforce at the boundary; fall back to generation when no image
if image_bytes:
    resp = litellm.image_edit(model='gemini/gemini-2.5-flash-image', prompt=prompt, image=image_bytes, api_key=KEY)
else:
    resp = litellm.image_generation(model='gemini/2.0-flash-preview-image-generation', prompt=prompt, api_key=KEY)
Defensive patterns

Strategy: validation

Validate before calling

def has_editable_image(image) -> bool:
    if image is None:
        return False
    imgs = image if isinstance(image, list) else [image]
    return any(img is not None for img in imgs)

assert has_editable_image(request.image), 'gemini image_edit requires a non-empty image'

Prevention

When it happens

Trigger: litellm.image_edit(model='gemini/gemini-2.5-flash-image', prompt='...') with image omitted; image=[] (e.g. an upload step upstream produced an empty list); image=[None] from an optional form field that was never set.

Common situations: Web forms where the image field is optional but Gemini edit requires it; code paths shared with OpenAI images/edits where image is also required but validation happens elsewhere; upstream file-read bugs silently yielding empty payloads.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at litellm/llms/gemini/image_edit/transformation.py:96

        api_base: str | None,
        litellm_params: dict,
    ) -> str:
        base_url = api_base or get_secret_str("GEMINI_API_BASE") or self.DEFAULT_BASE_URL
        base_url = base_url.rstrip("/")
        return f"{base_url}/models/{model}:generateContent"

    def transform_image_edit_request(
        self,
        model: str,
        prompt: str | None,
        image: FileTypes | None,
        image_edit_optional_request_params: dict[str, Any],
        litellm_params: GenericLiteLLMParams,
        headers: dict,
    ) -> tuple[dict[str, Any], RequestFiles | None]:
        inline_parts: Final = self._prepare_inline_image_parts(image) if image else []
        if not inline_parts:
            raise ValueError("Gemini image edit requires at least one image.")

        # Build parts list with image and prompt (if provided)
        parts: Final = inline_parts.copy()
        if prompt is not None and prompt != "":
            parts.append({"text": prompt})

        contents: Final = [
            {
                "parts": parts,
            }
        ]

        request_body: Final[dict[str, Any]] = {"contents": contents}

        request_body["generationConfig"] = get_gemini_image_generation_config(
            model=model,
            optional_params=image_edit_optional_request_params,
        )

View on GitHub (pinned to 77b7c6c40c)