Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions litellm/images/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,8 @@ def image_edit(
model=model,
image_edit_provider_config=image_edit_provider_config,
image_edit_optional_params=image_edit_optional_params,
drop_params=kwargs.get("drop_params"),
additional_drop_params=kwargs.get("additional_drop_params"),
)
)

Expand Down
40 changes: 26 additions & 14 deletions litellm/images/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from io import BufferedReader, BytesIO
from typing import Any, Dict, cast, get_type_hints
from typing import Any, Dict, List, Optional, cast, get_type_hints

import litellm
from litellm.litellm_core_utils.token_counter import get_image_type
Expand All @@ -14,41 +14,53 @@ def get_optional_params_image_edit(
model: str,
image_edit_provider_config: BaseImageEditConfig,
image_edit_optional_params: ImageEditOptionalRequestParams,
drop_params: Optional[bool] = None,
additional_drop_params: Optional[List[str]] = None,
) -> Dict:
"""
Get optional parameters for the image edit API.

Args:
params: Dictionary of all parameters
model: The model name
image_edit_provider_config: The provider configuration for image edit API
image_edit_optional_params: The optional parameters for the image edit API
drop_params: If True, silently drop unsupported parameters instead of raising
additional_drop_params: List of additional parameter names to drop

Returns:
A dictionary of supported parameters for the image edit API
"""
# Remove None values and internal parameters

# Get supported parameters for the model
supported_params = image_edit_provider_config.get_supported_openai_params(model)

# Check for unsupported parameters
should_drop = litellm.drop_params is True or drop_params is True

filtered_optional_params = dict(image_edit_optional_params)
if additional_drop_params:
for param in additional_drop_params:
filtered_optional_params.pop(param, None)

unsupported_params = [
param
for param in image_edit_optional_params
for param in filtered_optional_params
if param not in supported_params
]

if unsupported_params:
raise litellm.UnsupportedParamsError(
model=model,
message=f"The following parameters are not supported for model {model}: {', '.join(unsupported_params)}",
)
if should_drop:
for param in unsupported_params:
filtered_optional_params.pop(param, None)
else:
raise litellm.UnsupportedParamsError(
model=model,
message=f"The following parameters are not supported for model {model}: {', '.join(unsupported_params)}",
)

# Map parameters to provider-specific format
mapped_params = image_edit_provider_config.map_openai_params(
image_edit_optional_params=image_edit_optional_params,
image_edit_optional_params=cast(
ImageEditOptionalRequestParams, filtered_optional_params
),
model=model,
drop_params=litellm.drop_params,
drop_params=should_drop,
)

return mapped_params
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,24 @@ def get_complete_url(
"""
Get the complete URL for Vertex AI Gemini generateContent API
"""
vertex_project = self._resolve_vertex_project()
vertex_location = self._resolve_vertex_location()
vertex_project = (
litellm_params.get("vertex_project") or self._resolve_vertex_project()
)
vertex_location = (
litellm_params.get("vertex_location") or self._resolve_vertex_location()
)

if not vertex_project or not vertex_location:
raise ValueError("vertex_project and vertex_location are required for Vertex AI")

# Use the model name as provided, handling vertex_ai prefix
model_name = model
if model.startswith("vertex_ai/"):
model_name = model.replace("vertex_ai/", "")

if api_base:
base_url = api_base.rstrip("/")
elif vertex_location == "global":
base_url = "https://aiplatform.googleapis.com"
else:
base_url = f"https://{vertex_location}-aiplatform.googleapis.com"

Expand Down
170 changes: 170 additions & 0 deletions tests/test_litellm/images/test_image_edit_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
from typing import Any, Dict, List
from unittest.mock import MagicMock, patch

import pytest

import litellm
from litellm.images.utils import ImageEditRequestUtils
from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig
from litellm.types.images.main import ImageEditOptionalRequestParams


class MockImageEditConfig(BaseImageEditConfig):
def get_supported_openai_params(self, model: str) -> List[str]:
return ["size", "quality"]

def map_openai_params(
self,
image_edit_optional_params: ImageEditOptionalRequestParams,
model: str,
drop_params: bool,
) -> Dict[str, Any]:
return dict(image_edit_optional_params)

def get_complete_url(
self, model: str, api_base: str, litellm_params: dict
) -> str:
return "https://example.com/api"

def validate_environment(
self, headers: dict, model: str, api_key: str = None
) -> dict:
return headers

def transform_image_edit_request(self, *args, **kwargs):
return {}, []

def transform_image_edit_response(self, *args, **kwargs):
return MagicMock()


class TestImageEditRequestUtilsDropParams:
def setup_method(self):
self.config = MockImageEditConfig()
self.model = "test-model"
self._original_drop_params = getattr(litellm, "drop_params", None)

def teardown_method(self):
if self._original_drop_params is None:
if hasattr(litellm, "drop_params"):
delattr(litellm, "drop_params")
else:
litellm.drop_params = self._original_drop_params

def test_unsupported_params_raises_without_drop(self):
litellm.drop_params = False
optional_params: ImageEditOptionalRequestParams = {
"size": "1024x1024",
"unsupported_param": "value",
}

with pytest.raises(litellm.UnsupportedParamsError) as exc_info:
ImageEditRequestUtils.get_optional_params_image_edit(
model=self.model,
image_edit_provider_config=self.config,
image_edit_optional_params=optional_params,
)

assert "unsupported_param" in str(exc_info.value)

def test_drop_params_global_setting(self):
litellm.drop_params = True
optional_params: ImageEditOptionalRequestParams = {
"size": "1024x1024",
"unsupported_param": "value",
}

result = ImageEditRequestUtils.get_optional_params_image_edit(
model=self.model,
image_edit_provider_config=self.config,
image_edit_optional_params=optional_params,
)

assert "size" in result
assert "unsupported_param" not in result

def test_drop_params_explicit_parameter(self):
litellm.drop_params = False
optional_params: ImageEditOptionalRequestParams = {
"size": "1024x1024",
"unsupported_param": "value",
}

result = ImageEditRequestUtils.get_optional_params_image_edit(
model=self.model,
image_edit_provider_config=self.config,
image_edit_optional_params=optional_params,
drop_params=True,
)

assert "size" in result
assert "unsupported_param" not in result

def test_additional_drop_params(self):
litellm.drop_params = False
optional_params: ImageEditOptionalRequestParams = {
"size": "1024x1024",
"quality": "high",
}

result = ImageEditRequestUtils.get_optional_params_image_edit(
model=self.model,
image_edit_provider_config=self.config,
image_edit_optional_params=optional_params,
additional_drop_params=["quality"],
)

assert "size" in result
assert "quality" not in result

def test_drop_params_false_with_global_true(self):
litellm.drop_params = True
optional_params: ImageEditOptionalRequestParams = {
"size": "1024x1024",
"unsupported_param": "value",
}

result = ImageEditRequestUtils.get_optional_params_image_edit(
model=self.model,
image_edit_provider_config=self.config,
image_edit_optional_params=optional_params,
drop_params=False,
)

assert "size" in result
assert "unsupported_param" not in result

def test_supported_params_pass_through(self):
litellm.drop_params = False
optional_params: ImageEditOptionalRequestParams = {
"size": "1024x1024",
"quality": "high",
}

result = ImageEditRequestUtils.get_optional_params_image_edit(
model=self.model,
image_edit_provider_config=self.config,
image_edit_optional_params=optional_params,
)

assert result["size"] == "1024x1024"
assert result["quality"] == "high"

def test_additional_drop_params_with_unsupported_and_drop_true(self):
litellm.drop_params = True
optional_params: ImageEditOptionalRequestParams = {
"size": "1024x1024",
"quality": "high",
"unsupported_param": "value",
}

result = ImageEditRequestUtils.get_optional_params_image_edit(
model=self.model,
image_edit_provider_config=self.config,
image_edit_optional_params=optional_params,
additional_drop_params=["quality"],
)

assert "size" in result
assert "quality" not in result
assert "unsupported_param" not in result
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,55 @@ def test_transform_image_edit_request_without_image_raises(self) -> None:
headers={},
)

def test_get_complete_url_from_litellm_params(self) -> None:
"""Test vertex_project/vertex_location read from litellm_params first"""
url = self.config.get_complete_url(
model="gemini-2.5-flash",
api_base=None,
litellm_params={
"vertex_project": "params-project",
"vertex_location": "us-east1",
},
)
assert "params-project" in url
assert "us-east1" in url

def test_get_complete_url_global_location(self) -> None:
"""Test global location uses correct base URL without region prefix"""
url = self.config.get_complete_url(
model="gemini-2.5-flash",
api_base=None,
litellm_params={
"vertex_project": "test-project",
"vertex_location": "global",
},
)
assert "aiplatform.googleapis.com" in url
assert "global-aiplatform.googleapis.com" not in url
assert "/locations/global/" in url

def test_get_complete_url_litellm_params_overrides_env(self) -> None:
"""Test litellm_params takes precedence over environment variables"""
with patch.dict(
os.environ,
{
"VERTEXAI_PROJECT": "env-project",
"VERTEXAI_LOCATION": "us-central1",
},
):
url = self.config.get_complete_url(
model="gemini-2.5-flash",
api_base=None,
litellm_params={
"vertex_project": "params-project",
"vertex_location": "eu-west1",
},
)
assert "params-project" in url
assert "eu-west1" in url
assert "env-project" not in url
assert "us-central1" not in url


class TestVertexAIImagenImageEditTransformation:
def setup_method(self) -> None:
Expand Down
Loading