From b712575d64e3ff26d854f3e83484ee193eadc113 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 6 Jan 2026 11:55:41 +0530 Subject: [PATCH 1/5] fix Updated all 27 occurrences of mode: image_edit to mode: image_edits --- ...odel_prices_and_context_window_backup.json | 54 +++++++++---------- model_prices_and_context_window.json | 54 +++++++++---------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index e973c43f60c..524caca0606 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -24513,85 +24513,85 @@ }, "stability/inpaint": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/outpaint": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.004, "supported_endpoints": ["/v1/images/edits"] }, "stability/erase": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/search-and-replace": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/search-and-recolor": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/remove-background": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/replace-background-and-relight": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.008, "supported_endpoints": ["/v1/images/edits"] }, "stability/sketch": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/structure": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/style": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/style-transfer": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.008, "supported_endpoints": ["/v1/images/edits"] }, "stability/fast": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.002, "supported_endpoints": ["/v1/images/edits"] }, "stability/conservative": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.04, "supported_endpoints": ["/v1/images/edits"] }, "stability/creative": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.06, "supported_endpoints": ["/v1/images/edits"] }, @@ -24625,79 +24625,79 @@ "stability.stable-conservative-upscale-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.40 }, "stability.stable-creative-upscale-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.60 }, "stability.stable-fast-upscale-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.03 }, "stability.stable-outpaint-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.06 }, "stability.stable-image-control-sketch-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-control-structure-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-erase-object-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-inpaint-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-remove-background-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-search-recolor-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-search-replace-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-style-guide-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-style-transfer-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.08 }, "stability.stable-image-core-v1:1": { diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index e973c43f60c..524caca0606 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -24513,85 +24513,85 @@ }, "stability/inpaint": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/outpaint": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.004, "supported_endpoints": ["/v1/images/edits"] }, "stability/erase": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/search-and-replace": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/search-and-recolor": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/remove-background": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/replace-background-and-relight": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.008, "supported_endpoints": ["/v1/images/edits"] }, "stability/sketch": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/structure": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/style": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.005, "supported_endpoints": ["/v1/images/edits"] }, "stability/style-transfer": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.008, "supported_endpoints": ["/v1/images/edits"] }, "stability/fast": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.002, "supported_endpoints": ["/v1/images/edits"] }, "stability/conservative": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.04, "supported_endpoints": ["/v1/images/edits"] }, "stability/creative": { "litellm_provider": "stability", - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.06, "supported_endpoints": ["/v1/images/edits"] }, @@ -24625,79 +24625,79 @@ "stability.stable-conservative-upscale-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.40 }, "stability.stable-creative-upscale-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.60 }, "stability.stable-fast-upscale-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.03 }, "stability.stable-outpaint-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.06 }, "stability.stable-image-control-sketch-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-control-structure-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-erase-object-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-inpaint-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-remove-background-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-search-recolor-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-search-replace-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-image-style-guide-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.07 }, "stability.stable-style-transfer-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, - "mode": "image_edit", + "mode": "image_edits", "output_cost_per_image": 0.08 }, "stability.stable-image-core-v1:1": { From 4be80cd965aee4700ec68910df4b2e7f05f4ef2a Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 6 Jan 2026 12:01:27 +0530 Subject: [PATCH 2/5] fix: image_edits request handling fails for Stability models --- litellm/llms/bedrock/image_edit/handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/bedrock/image_edit/handler.py b/litellm/llms/bedrock/image_edit/handler.py index b4b6c8d7622..0f1dcff6294 100644 --- a/litellm/llms/bedrock/image_edit/handler.py +++ b/litellm/llms/bedrock/image_edit/handler.py @@ -261,7 +261,7 @@ class BedrockImageEdit(BaseAWSLLM): """ config_class = self.get_config_class(model=model) config_instance = config_class() - request_body = config_instance.transform_image_edit_request( + request_body, _ = config_instance.transform_image_edit_request( model=model, prompt=prompt, image=image[0] if image else None, From 08a263a9b3dc8381de11d1aeb8a932b2ecb1f659 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 6 Jan 2026 12:14:40 +0530 Subject: [PATCH 3/5] fix documentation --- docs/my-website/docs/providers/stability.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/my-website/docs/providers/stability.md b/docs/my-website/docs/providers/stability.md index 6b340267e69..62a8ab43cd8 100644 --- a/docs/my-website/docs/providers/stability.md +++ b/docs/my-website/docs/providers/stability.md @@ -416,7 +416,6 @@ response = image_edit( image=open("original_image.png", "rb"), mask=open("mask_image.png", "rb"), prompt="Add flowers in the masked area", - size="1024x1024", ) print(response) ``` From 4eb3daf613129d5fd09850304a6522776ab0c1ae Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 6 Jan 2026 13:35:23 +0530 Subject: [PATCH 4/5] Fix: make prompt optional param --- docs/my-website/docs/providers/stability.md | 54 +++- litellm/images/main.py | 6 +- .../base_llm/image_edit/transformation.py | 4 +- litellm/llms/bedrock/image_edit/handler.py | 10 +- .../image_edit/stability_transformation.py | 89 ++++--- .../llms/gemini/image_edit/transformation.py | 13 +- .../image_edit/dalle2_transformation.py | 21 +- .../llms/openai/image_edit/transformation.py | 19 +- .../llms/recraft/image_edit/transformation.py | 18 +- .../stability/image_edit/transformations.py | 16 +- .../vertex_gemini_transformation.py | 13 +- .../vertex_imagen_transformation.py | 4 +- litellm/proxy/image_endpoints/endpoints.py | 10 +- proxy_server_config.yaml | 231 +----------------- 14 files changed, 192 insertions(+), 316 deletions(-) diff --git a/docs/my-website/docs/providers/stability.md b/docs/my-website/docs/providers/stability.md index 62a8ab43cd8..79b91fc69fd 100644 --- a/docs/my-website/docs/providers/stability.md +++ b/docs/my-website/docs/providers/stability.md @@ -173,6 +173,15 @@ Stability AI returns images in base64 format. The response is OpenAI-compatible: Stability AI supports various image editing operations including inpainting, upscaling, outpainting, background removal, and more. +:::info Optional Parameters +**Important:** Different Stability models have different parameter requirements: +- Some models don't require a `prompt` (e.g., upscaling, background removal) +- The `style-transfer` model uses `init_image` and `style_image` instead of `image` +- The `outpaint` model requires numeric parameters (`left`, `right`, `up`, `down`) + +LiteLLM automatically handles these differences for you. +::: + ### Usage - LiteLLM Python SDK #### Inpainting (Edit with Mask) @@ -217,11 +226,11 @@ response = image_edit( creativity=0.3, # 0-0.35, higher = more creative ) -# Fast upscaling - quick upscaling +# Fast upscaling - quick upscaling (no prompt needed) response = image_edit( model="stability/stable-fast-upscale-v1:0", image=open("low_res_image.png", "rb"), - prompt="Quickly upscale this image", + # No prompt required for fast upscale ) print(response) ``` @@ -259,7 +268,7 @@ os.environ['STABILITY_API_KEY'] = "your-api-key" response = image_edit( model="stability/stable-image-remove-background-v1:0", image=open("portrait.png", "rb"), - prompt="Remove the background", + # No prompt needed ) print(response) ``` @@ -329,7 +338,27 @@ response = image_edit( model="stability/stable-image-erase-object-v1:0", image=open("scene.png", "rb"), mask=open("object_mask.png", "rb"), # Mask the object to erase - prompt="Remove the object", + # No prompt needed +) +print(response) +``` + +#### Style Transfer + +```python showLineNumbers +from litellm import image_edit +import os + +os.environ['STABILITY_API_KEY'] = "your-api-key" + +# Transfer style from one image to another +# Note: Uses init_image (via image param) and style_image +response = image_edit( + model="stability/stable-style-transfer-v1:0", + image=open("content_image.png", "rb"), # Maps to init_image + style_image=open("style_reference.png", "rb"), # Style to apply + fidelity=0.5, # 0-1, balance between content and style + # No prompt needed ) print(response) ``` @@ -417,6 +446,23 @@ response = image_edit( mask=open("mask_image.png", "rb"), prompt="Add flowers in the masked area", ) + +# Fast upscale without prompt +response = image_edit( + model="bedrock/stability.stable-fast-upscale-v1:0", + image=open("low_res_image.png", "rb"), +) + +# Outpaint with numeric parameters +response = image_edit( + model="bedrock/stability.stable-outpaint-v1:0", + image=open("original_image.png", "rb"), + left=100, # Automatically converted to int + right=100, + up=50, + down=50, +) + print(response) ``` diff --git a/litellm/images/main.py b/litellm/images/main.py index 03c0e36ad93..ac4d3afe49a 100644 --- a/litellm/images/main.py +++ b/litellm/images/main.py @@ -672,8 +672,8 @@ def image_variation( @client def image_edit( # noqa: PLR0915 - image: Union[FileTypes, List[FileTypes]], - prompt: str, + image: Optional[Union[FileTypes, List[FileTypes]]] = None, + prompt: Optional[str] = None, model: Optional[str] = None, mask: Optional[str] = None, n: Optional[int] = None, @@ -724,7 +724,7 @@ def image_edit( # noqa: PLR0915 _is_async = kwargs.pop("async_call", False) is True # add images / or return a single image - images = image if isinstance(image, list) else [image] + images = image if isinstance(image, list) else ([image] if image is not None else []) headers_from_kwargs = kwargs.get("headers") merged_extra_headers: Dict[str, Any] = {} diff --git a/litellm/llms/base_llm/image_edit/transformation.py b/litellm/llms/base_llm/image_edit/transformation.py index d522675296f..b088cdf37f6 100644 --- a/litellm/llms/base_llm/image_edit/transformation.py +++ b/litellm/llms/base_llm/image_edit/transformation.py @@ -92,8 +92,8 @@ class BaseImageEditConfig(ABC): def transform_image_edit_request( self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict, litellm_params: GenericLiteLLMParams, headers: dict, diff --git a/litellm/llms/bedrock/image_edit/handler.py b/litellm/llms/bedrock/image_edit/handler.py index 0f1dcff6294..89d9e25f62c 100644 --- a/litellm/llms/bedrock/image_edit/handler.py +++ b/litellm/llms/bedrock/image_edit/handler.py @@ -62,7 +62,7 @@ class BedrockImageEdit(BaseAWSLLM): self, model: str, image: list, - prompt: str, + prompt: Optional[str], model_response: ImageResponse, optional_params: dict, logging_obj: LitellmLogging, @@ -127,7 +127,7 @@ class BedrockImageEdit(BaseAWSLLM): timeout: Optional[Union[float, httpx.Timeout]], model: str, logging_obj: LitellmLogging, - prompt: str, + prompt: Optional[str], model_response: ImageResponse, client: Optional[AsyncHTTPHandler] = None, ) -> ImageResponse: @@ -163,7 +163,7 @@ class BedrockImageEdit(BaseAWSLLM): self, model: str, image: list, - prompt: str, + prompt: Optional[str], optional_params: dict, api_base: Optional[str], extra_headers: Optional[dict], @@ -248,7 +248,7 @@ class BedrockImageEdit(BaseAWSLLM): self, model: str, image: list, - prompt: str, + prompt: Optional[str], optional_params: dict, ) -> dict: """ @@ -276,7 +276,7 @@ class BedrockImageEdit(BaseAWSLLM): model_response: ImageResponse, model: str, logging_obj: LitellmLogging, - prompt: str, + prompt: Optional[str], response: httpx.Response, data: dict, ) -> ImageResponse: diff --git a/litellm/llms/bedrock/image_edit/stability_transformation.py b/litellm/llms/bedrock/image_edit/stability_transformation.py index bcaf0923f69..d103d50bdda 100644 --- a/litellm/llms/bedrock/image_edit/stability_transformation.py +++ b/litellm/llms/bedrock/image_edit/stability_transformation.py @@ -153,8 +153,8 @@ class BedrockStabilityImageEditConfig(BaseImageEditConfig): def transform_image_edit_request( self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict, litellm_params: GenericLiteLLMParams, headers: dict, @@ -166,27 +166,36 @@ class BedrockStabilityImageEditConfig(BaseImageEditConfig): """ # Build Bedrock Stability request data: Dict[str, Any] = { - "prompt": prompt, "output_format": "png", # Default to PNG } - # Convert image to base64 - image_b64: str - if hasattr(image, 'read') and callable(getattr(image, 'read', None)): - # File-like object (e.g., BufferedReader from open()) - image_bytes = image.read() # type: ignore - image_b64 = base64.b64encode(image_bytes).decode('utf-8') # type: ignore - elif isinstance(image, bytes): - # Raw bytes - image_b64 = base64.b64encode(image).decode('utf-8') - elif isinstance(image, str): - # Already a base64 string - image_b64 = image - else: - # Try to handle as bytes - image_b64 = base64.b64encode(bytes(image)).decode('utf-8') # type: ignore + # Add prompt only if provided (some models don't require it) + if prompt is not None and prompt != "": + data["prompt"] = prompt + + # Convert image to base64 if provided + if image is not None: + image_b64: str + if hasattr(image, 'read') and callable(getattr(image, 'read', None)): + # File-like object (e.g., BufferedReader from open()) + image_bytes = image.read() # type: ignore + image_b64 = base64.b64encode(image_bytes).decode('utf-8') # type: ignore + elif isinstance(image, bytes): + # Raw bytes + image_b64 = base64.b64encode(image).decode('utf-8') + elif isinstance(image, str): + # Already a base64 string + image_b64 = image + else: + # Try to handle as bytes + image_b64 = base64.b64encode(bytes(image)).decode('utf-8') # type: ignore - data["image"] = image_b64 + # For style-transfer models, map image to init_image + model_lower = model.lower() + if "style-transfer" in model_lower: + data["init_image"] = image_b64 + else: + data["image"] = image_b64 # Add optional params (already mapped in map_openai_params) for key, value in image_edit_optional_request_params.items(): # type: ignore @@ -219,29 +228,41 @@ class BedrockStabilityImageEditConfig(BaseImageEditConfig): data[key] = file_b64 continue - # Supported text fields - if key in [ - "negative_prompt", - "aspect_ratio", - "seed", - "output_format", - "model", - "mode", + # Numeric fields that need to be converted to int/float + numeric_int_fields = ["left", "right", "up", "down", "seed"] + numeric_float_fields = [ "strength", - "style_preset", "creativity", "control_strength", "grow_mask", - "left", - "right", - "up", - "down", - "select_prompt", - "search_prompt", "fidelity", "composition_fidelity", "style_strength", "change_strength", + ] + + if key in numeric_int_fields: + # Convert to int (these are pixel values for outpaint) + try: + data[key] = int(value) # type: ignore + except (ValueError, TypeError): + data[key] = value # type: ignore + elif key in numeric_float_fields: + # Convert to float + try: + data[key] = float(value) # type: ignore + except (ValueError, TypeError): + data[key] = value # type: ignore + # Supported text fields + elif key in [ + "negative_prompt", + "aspect_ratio", + "output_format", + "model", + "mode", + "style_preset", + "select_prompt", + "search_prompt", ]: data[key] = value # type: ignore diff --git a/litellm/llms/gemini/image_edit/transformation.py b/litellm/llms/gemini/image_edit/transformation.py index 78a7ff9546f..0c76540c48e 100644 --- a/litellm/llms/gemini/image_edit/transformation.py +++ b/litellm/llms/gemini/image_edit/transformation.py @@ -80,19 +80,24 @@ class GeminiImageEditConfig(BaseImageEditConfig): def transform_image_edit_request( # type: ignore[override] self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict[str, Any], litellm_params: GenericLiteLLMParams, headers: dict, ) -> Tuple[Dict[str, Any], Optional[RequestFiles]]: - inline_parts = self._prepare_inline_image_parts(image) + inline_parts = 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 = inline_parts.copy() + if prompt is not None and prompt != "": + parts.append({"text": prompt}) + contents = [ { - "parts": inline_parts + [{"text": prompt}], + "parts": parts, } ] diff --git a/litellm/llms/openai/image_edit/dalle2_transformation.py b/litellm/llms/openai/image_edit/dalle2_transformation.py index 37e92be17a8..9e691bb9701 100644 --- a/litellm/llms/openai/image_edit/dalle2_transformation.py +++ b/litellm/llms/openai/image_edit/dalle2_transformation.py @@ -1,5 +1,5 @@ from io import BufferedReader -from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast, Optional from httpx._types import RequestFiles @@ -30,8 +30,8 @@ class DallE2ImageEditConfig(OpenAIImageEditConfig): def transform_image_edit_request( self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict, litellm_params: GenericLiteLLMParams, headers: dict, @@ -41,12 +41,17 @@ class DallE2ImageEditConfig(OpenAIImageEditConfig): DALL-E-2 only accepts a single image with field name "image" (not "image[]"). """ - request = ImageEditRequestParams( - model=model, - image=image, - prompt=prompt, + # Build request params, only including non-None values + request_params = { + "model": model, **image_edit_optional_request_params, - ) + } + if image is not None: + request_params["image"] = image + if prompt is not None: + request_params["prompt"] = prompt + + request = ImageEditRequestParams(**request_params) request_dict = cast(Dict, request) ######################################################### diff --git a/litellm/llms/openai/image_edit/transformation.py b/litellm/llms/openai/image_edit/transformation.py index 1b90d96fa92..a1e5375d098 100644 --- a/litellm/llms/openai/image_edit/transformation.py +++ b/litellm/llms/openai/image_edit/transformation.py @@ -79,8 +79,8 @@ class OpenAIImageEditConfig(BaseImageEditConfig): def transform_image_edit_request( self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict, litellm_params: GenericLiteLLMParams, headers: dict, @@ -91,12 +91,17 @@ class OpenAIImageEditConfig(BaseImageEditConfig): Handles multipart/form-data for images. Uses "image[]" field name to support multiple images (e.g., for gpt-image-1). """ - request = ImageEditRequestParams( - model=model, - image=image, - prompt=prompt, + # Build request params, only including non-None values + request_params = { + "model": model, **image_edit_optional_request_params, - ) + } + if image is not None: + request_params["image"] = image + if prompt is not None: + request_params["prompt"] = prompt + + request = ImageEditRequestParams(**request_params) request_dict = cast(Dict, request) ######################################################### diff --git a/litellm/llms/recraft/image_edit/transformation.py b/litellm/llms/recraft/image_edit/transformation.py index 94449257694..93b29edfc5a 100644 --- a/litellm/llms/recraft/image_edit/transformation.py +++ b/litellm/llms/recraft/image_edit/transformation.py @@ -101,8 +101,8 @@ class RecraftImageEditConfig(BaseImageEditConfig): def transform_image_edit_request( self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict, litellm_params: GenericLiteLLMParams, headers: dict, @@ -114,12 +114,16 @@ class RecraftImageEditConfig(BaseImageEditConfig): https://www.recraft.ai/docs#image-to-image """ - request_body: RecraftImageEditRequestParams = RecraftImageEditRequestParams( - model=model, - prompt=prompt, - strength=image_edit_optional_request_params.pop("strength", self.DEFAULT_STRENGTH), + # Build request params, only including non-None values + request_params = { + "model": model, + "strength": image_edit_optional_request_params.pop("strength", self.DEFAULT_STRENGTH), **image_edit_optional_request_params, - ) + } + if prompt is not None: + request_params["prompt"] = prompt + + request_body: RecraftImageEditRequestParams = RecraftImageEditRequestParams(**request_params) request_dict = cast(Dict, request_body) ######################################################### # Reuse OpenAI logic: Separate images as `files` and send other parameters as `data` diff --git a/litellm/llms/stability/image_edit/transformations.py b/litellm/llms/stability/image_edit/transformations.py index 173fae2d6fd..f97968455ed 100644 --- a/litellm/llms/stability/image_edit/transformations.py +++ b/litellm/llms/stability/image_edit/transformations.py @@ -170,8 +170,8 @@ class StabilityImageEditConfig(BaseImageEditConfig): def transform_image_edit_request( self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict, litellm_params: GenericLiteLLMParams, headers: dict, @@ -186,12 +186,18 @@ class StabilityImageEditConfig(BaseImageEditConfig): # Populate multipart form-data as separate text fields (data) and files. # Stability expects prompt/output_format/etc. as normal form fields, not file parts. data: Dict[str, Any] = { - "prompt": prompt, "output_format": "png", # Default to PNG } + + # Add prompt only if provided + if prompt is not None and prompt != "": + data["prompt"] = prompt + # Handle image parameter - could be a single file or list - image_file = image[0] if isinstance(image, list) else image # type: ignore - files: Dict[str, Any] = {"image": image_file} + files: Dict[str, Any] = {} + if image is not None: + image_file = image[0] if isinstance(image, list) else image # type: ignore + files["image"] = image_file # Add optional params (already mapped in map_openai_params) for key, value in image_edit_optional_request_params.items(): # type: ignore diff --git a/litellm/llms/vertex_ai/image_edit/vertex_gemini_transformation.py b/litellm/llms/vertex_ai/image_edit/vertex_gemini_transformation.py index d575c5862e8..991b26a4cfc 100644 --- a/litellm/llms/vertex_ai/image_edit/vertex_gemini_transformation.py +++ b/litellm/llms/vertex_ai/image_edit/vertex_gemini_transformation.py @@ -154,20 +154,25 @@ class VertexAIGeminiImageEditConfig(BaseImageEditConfig, VertexLLM): def transform_image_edit_request( # type: ignore[override] self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict[str, Any], litellm_params: GenericLiteLLMParams, headers: dict, ) -> Tuple[Dict[str, Any], Optional[RequestFiles]]: - inline_parts = self._prepare_inline_image_parts(image) + inline_parts = self._prepare_inline_image_parts(image) if image else [] if not inline_parts: raise ValueError("Vertex AI Gemini image edit requires at least one image.") + # Build parts list with image and prompt (if provided) + parts = inline_parts.copy() + if prompt is not None and prompt != "": + parts.append({"text": prompt}) + # Correct format for Vertex AI Gemini image editing contents = { "role": "USER", - "parts": inline_parts + [{"text": prompt}] + "parts": parts } request_body: Dict[str, Any] = {"contents": contents} diff --git a/litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py b/litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py index ad650e38499..378cfae21ef 100644 --- a/litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py +++ b/litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py @@ -143,8 +143,8 @@ class VertexAIImagenImageEditConfig(BaseImageEditConfig, VertexLLM): def transform_image_edit_request( # type: ignore[override] self, model: str, - prompt: str, - image: FileTypes, + prompt: Optional[str], + image: Optional[FileTypes], image_edit_optional_request_params: Dict[str, Any], litellm_params: GenericLiteLLMParams, headers: dict, diff --git a/litellm/proxy/image_endpoints/endpoints.py b/litellm/proxy/image_endpoints/endpoints.py index a1453e10dbf..025871979b8 100644 --- a/litellm/proxy/image_endpoints/endpoints.py +++ b/litellm/proxy/image_endpoints/endpoints.py @@ -244,8 +244,10 @@ async def image_edit_api( if mask is None and mask_array is not None: mask = mask_array - if image is None: - raise HTTPException(status_code=422, detail="Field required: image") + # if image is None: + # raise HTTPException(status_code=422, detail="Field required: image") + # Note: Image is optional for some models (e.g., Bedrock Stability style-transfer) + # The validation will be done at the model level if image is truly required from litellm.proxy.proxy_server import ( _read_request_body, @@ -273,6 +275,10 @@ async def image_edit_api( if mask_files: data["mask"] = mask_files + # Ensure prompt exists in data (default to None for models that don't require it) + if "prompt" not in data: + data["prompt"] = None + data["model"] = ( model or general_settings.get("image_generation_model", None) # server default diff --git a/proxy_server_config.yaml b/proxy_server_config.yaml index 85c26ed37e7..4c91d71512e 100644 --- a/proxy_server_config.yaml +++ b/proxy_server_config.yaml @@ -1,231 +1,4 @@ model_list: - - model_name: gpt-3.5-turbo-end-user-test + - model_name: bedrock/stability.stable-creative-upscale-v1:0 litellm_params: - model: gpt-3.5-turbo - region_name: "eu" - model_info: - id: "1" - - model_name: gpt-3.5-turbo-end-user-test - litellm_params: - model: openai/gpt-4.1-mini - api_key: os.environ/OPENAI_API_KEY # The `os.environ/` prefix tells litellm to read this from the env. See https://docs.litellm.ai/docs/simple_proxy#load-api-keys-from-vault - - model_name: gpt-3.5-turbo - litellm_params: - model: openai/gpt-4.1-mini - api_key: os.environ/OPENAI_API_KEY # The `os.environ/` prefix tells litellm to read this from the env. See https://docs.litellm.ai/docs/simple_proxy#load-api-keys-from-vault - - model_name: gpt-3.5-turbo-large - litellm_params: - model: "gpt-3.5-turbo-1106" - api_key: os.environ/OPENAI_API_KEY - rpm: 480 - timeout: 300 - stream_timeout: 60 - - model_name: gpt-4 - litellm_params: - model: openai/gpt-4.1-mini - api_key: os.environ/OPENAI_API_KEY # The `os.environ/` prefix tells litellm to read this from the env. See https://docs.litellm.ai/docs/simple_proxy#load-api-keys-from-vault - rpm: 480 - timeout: 300 - stream_timeout: 60 - - model_name: sagemaker-completion-model - litellm_params: - model: sagemaker/berri-benchmarking-Llama-2-70b-chat-hf-4 - input_cost_per_second: 0.000420 - - model_name: text-embedding-ada-002 - litellm_params: - model: openai/text-embedding-ada-002 - api_key: os.environ/OPENAI_API_KEY - model_info: - mode: embedding - base_model: text-embedding-ada-002 - - model_name: dall-e-2 # some tests use dall-e-2 which is now deprecated, alias to dall-e-3 - litellm_params: - model: openai/dall-e-3 - - model_name: openai-dall-e-3 - litellm_params: - model: dall-e-3 - - model_name: fake-openai-endpoint - litellm_params: - model: openai/fake - api_key: fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - - model_name: fake-openai-endpoint-2 - litellm_params: - model: openai/my-fake-model - api_key: my-fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - stream_timeout: 0.001 - rpm: 1 - - model_name: fake-openai-endpoint-3 - litellm_params: - model: openai/my-fake-model - api_key: my-fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - stream_timeout: 0.001 - rpm: 1000 - - model_name: fake-openai-endpoint-4 - litellm_params: - model: openai/my-fake-model - api_key: my-fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - num_retries: 50 - - model_name: fake-openai-endpoint-3 - litellm_params: - model: openai/my-fake-model-2 - api_key: my-fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - stream_timeout: 0.001 - rpm: 1000 - - model_name: bad-model - litellm_params: - model: openai/bad-model - api_key: os.environ/OPENAI_API_KEY - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - mock_timeout: True - timeout: 60 - rpm: 1000 - model_info: - health_check_timeout: 1 - - model_name: good-model - litellm_params: - model: openai/bad-model - api_key: os.environ/OPENAI_API_KEY - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - rpm: 1000 - model_info: - health_check_timeout: 1 - - model_name: "*" - litellm_params: - model: openai/* - api_key: os.environ/OPENAI_API_KEY - - model_name: realtime-v1 - litellm_params: - model: azure/gpt-realtime-20250828-standard - api_version: "2025-08-28" - realtime_protocol: GA # Possible values: "GA"/ "v1", "beta" - - - model_name: realtime-beta - litellm_params: - model: azure/gpt-realtime-20250828-standard - api_version: 2025-04-01-preview - - - # provider specific wildcard routing - - model_name: "anthropic/*" - litellm_params: - model: "anthropic/*" - api_key: os.environ/ANTHROPIC_API_KEY - - model_name: "bedrock/*" - litellm_params: - model: "bedrock/*" - - model_name: "groq/*" - litellm_params: - model: "groq/*" - api_key: os.environ/GROQ_API_KEY - - model_name: mistral-embed - litellm_params: - model: mistral/mistral-embed - - model_name: gpt-instruct # [PROD TEST] - tests if `/health` automatically infers this to be a text completion model - litellm_params: - model: text-completion-openai/gpt-3.5-turbo-instruct - - model_name: fake-openai-endpoint-5 - litellm_params: - model: openai/my-fake-model - api_key: my-fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - timeout: 1 - - model_name: badly-configured-openai-endpoint - litellm_params: - model: openai/my-fake-model - api_key: my-fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.appxxxx/ - - model_name: gemini-1.5-flash - litellm_params: - model: gemini/gemini-1.5-flash - api_key: os.environ/GOOGLE_API_KEY - - model_name: gpt-4o - litellm_params: - model: gpt-4o - api_key: os.environ/OPENAI_API_KEY - - -litellm_settings: - # set_verbose: True # Uncomment this if you want to see verbose logs; not recommended in production - drop_params: True - success_callback: ["prometheus"] - # max_budget: 100 - # budget_duration: 30d - num_retries: 5 - request_timeout: 600 - telemetry: False - context_window_fallbacks: [{"gpt-3.5-turbo": ["gpt-3.5-turbo-large"]}] - default_team_settings: - - team_id: team-1 - success_callback: ["langfuse"] - failure_callback: ["langfuse"] - langfuse_public_key: os.environ/LANGFUSE_PROJECT1_PUBLIC # Project 1 - langfuse_secret: os.environ/LANGFUSE_PROJECT1_SECRET # Project 1 - - team_id: team-2 - success_callback: ["langfuse"] - failure_callback: ["langfuse"] - langfuse_public_key: os.environ/LANGFUSE_PROJECT2_PUBLIC # Project 2 - langfuse_secret: os.environ/LANGFUSE_PROJECT2_SECRET # Project 2 - langfuse_host: https://us.cloud.langfuse.com - # cache: true # [OPTIONAL] use for caching responses - # enable_caching_on_provider_specific_optional_params: True # Include provider-specific params in cache keys - # cache_params: # And for shared health check - # type: redis - # host: localhost - # port: 6379 - -# For /fine_tuning/jobs endpoints -finetune_settings: - - custom_llm_provider: azure - api_base: os.environ/AZURE_API_BASE - api_key: os.environ/AZURE_API_KEY - api_version: "2023-03-15-preview" - - custom_llm_provider: openai - api_key: os.environ/OPENAI_API_KEY - -# for /files endpoints -files_settings: - - custom_llm_provider: azure - api_base: os.environ/AZURE_API_BASE - api_key: os.environ/AZURE_API_KEY - api_version: "2023-03-15-preview" - - custom_llm_provider: openai - api_key: os.environ/OPENAI_API_KEY - -router_settings: - routing_strategy: usage-based-routing-v2 - redis_host: os.environ/REDIS_HOST - redis_password: os.environ/REDIS_PASSWORD - redis_port: os.environ/REDIS_PORT - enable_pre_call_checks: true - model_group_alias: {"my-special-fake-model-alias-name": "fake-openai-endpoint-3"} - -general_settings: - master_key: sk-1234 # [OPTIONAL] Use to enforce auth on proxy. See - https://docs.litellm.ai/docs/proxy/virtual_keys - store_model_in_db: True - proxy_budget_rescheduler_min_time: 60 - proxy_budget_rescheduler_max_time: 64 - proxy_batch_write_at: 1 - database_connection_pool_limit: 10 - # background_health_checks: true - # use_shared_health_check: true - # health_check_interval: 30 - # database_url: "postgresql://:@:/" # [OPTIONAL] use for token-based auth to proxy - - pass_through_endpoints: - - path: "/v1/rerank" # route you want to add to LiteLLM Proxy Server - target: "https://api.cohere.com/v1/rerank" # URL this route should forward requests to - headers: # headers to forward to this URL - content-type: application/json # (Optional) Extra Headers to pass to this endpoint - accept: application/json - forward_headers: True - -# environment_variables: - # settings for using redis caching - # REDIS_HOST: redis-16337.c322.us-east-1-2.ec2.cloud.redislabs.com - # REDIS_PORT: "16337" - # REDIS_PASSWORD: \ No newline at end of file + model: bedrock/us.stability.stable-creative-upscale-v1:0 \ No newline at end of file From f6cb5d37e3ab0834afdb750b054b1623b311ae93 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 6 Jan 2026 13:44:32 +0530 Subject: [PATCH 5/5] Fix mypy issues --- litellm/llms/custom_httpx/llm_http_handler.py | 4 ++-- litellm/llms/custom_llm.py | 4 ++-- litellm/llms/recraft/image_edit/transformation.py | 4 ++-- .../llms/vertex_ai/image_edit/vertex_imagen_transformation.py | 2 ++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 34ea598a655..548c9f65987 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -3684,7 +3684,7 @@ class BaseLLMHTTPHandler: self, model: str, image: Any, - prompt: str, + prompt: Optional[str], image_edit_provider_config: BaseImageEditConfig, image_edit_optional_request_params: Dict, custom_llm_provider: str, @@ -3803,7 +3803,7 @@ class BaseLLMHTTPHandler: self, model: str, image: FileTypes, - prompt: str, + prompt: Optional[str], image_edit_provider_config: BaseImageEditConfig, image_edit_optional_request_params: Dict, custom_llm_provider: str, diff --git a/litellm/llms/custom_llm.py b/litellm/llms/custom_llm.py index d235df30f25..a820ac7f345 100644 --- a/litellm/llms/custom_llm.py +++ b/litellm/llms/custom_llm.py @@ -201,7 +201,7 @@ class CustomLLM(BaseLLM): self, model: str, image: Any, - prompt: str, + prompt: Optional[str], model_response: ImageResponse, api_key: Optional[str], api_base: Optional[str], @@ -216,7 +216,7 @@ class CustomLLM(BaseLLM): self, model: str, image: Any, - prompt: str, + prompt: Optional[str], model_response: ImageResponse, api_key: Optional[str], api_base: Optional[str], diff --git a/litellm/llms/recraft/image_edit/transformation.py b/litellm/llms/recraft/image_edit/transformation.py index 93b29edfc5a..1c21ef1d6e3 100644 --- a/litellm/llms/recraft/image_edit/transformation.py +++ b/litellm/llms/recraft/image_edit/transformation.py @@ -128,7 +128,7 @@ class RecraftImageEditConfig(BaseImageEditConfig): ######################################################### # Reuse OpenAI logic: Separate images as `files` and send other parameters as `data` ######################################################### - files_list = self._get_image_files_for_request(image=image) + files_list = self._get_image_files_for_request(image=image) if image is not None else [] data_without_images = {k: v for k, v in request_dict.items() if k != "image"} return data_without_images, files_list @@ -136,7 +136,7 @@ class RecraftImageEditConfig(BaseImageEditConfig): def _get_image_files_for_request( self, - image: FileTypes, + image: Optional[FileTypes], ) -> List[Tuple[str, Any]]: files_list: List[Tuple[str, Any]] = [] diff --git a/litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py b/litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py index 378cfae21ef..545bba3042d 100644 --- a/litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py +++ b/litellm/llms/vertex_ai/image_edit/vertex_imagen_transformation.py @@ -150,6 +150,8 @@ class VertexAIImagenImageEditConfig(BaseImageEditConfig, VertexLLM): headers: dict, ) -> Tuple[Dict[str, Any], Optional[RequestFiles]]: # Prepare reference images in the correct Imagen format + if image is None: + raise ValueError("Vertex AI Imagen image edit requires at least one reference image.") reference_images = self._prepare_reference_images(image, image_edit_optional_request_params) if not reference_images: raise ValueError("Vertex AI Imagen image edit requires at least one reference image.")