mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
Merge pull request #40074 from mihidumh/fix/mai-image-unsupported-params
fix(azure_ai): reject unsupported n and size params on MAI image models
This commit is contained in:
commit
1d18b61e3e
6 changed files with 238 additions and 135 deletions
|
|
@ -23,7 +23,7 @@ def get_azure_ai_image_edit_config(model: str) -> BaseImageEditConfig:
|
|||
"""
|
||||
Get the appropriate image edit config for an Azure AI model.
|
||||
|
||||
- MAI models use /mai/v1/images/edits with multipart form data and size
|
||||
- MAI models use /mai/v1/images/edits with multipart form data
|
||||
- FLUX 2 models use JSON with base64 image
|
||||
- FLUX 1 models use multipart/form-data
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import TYPE_CHECKING, Any, Final, cast
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
|
||||
import httpx
|
||||
from httpx._types import RequestFiles
|
||||
|
|
@ -13,7 +13,6 @@ from litellm.llms.azure_ai.image_generation.mai_transformation import (
|
|||
from litellm.llms.openai.common_utils import OpenAIError
|
||||
from litellm.llms.openai.image_edit.transformation import OpenAIImageEditConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.images.main import ImageEditOptionalRequestParams
|
||||
from litellm.types.llms.openai import FileTypes
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.utils import ImageResponse
|
||||
|
|
@ -26,65 +25,8 @@ if TYPE_CHECKING:
|
|||
class AzureFoundryMAIImageEditConfig(OpenAIImageEditConfig):
|
||||
"""Azure AI Foundry MAI image editing (e.g. MAI-Image-2.5)."""
|
||||
|
||||
DEFAULT_SIZE = "1024x1024"
|
||||
|
||||
def get_supported_openai_params(self, model: str) -> list:
|
||||
return ["prompt", "image", "model", "n", "size"]
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
image_edit_optional_params: ImageEditOptionalRequestParams,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict:
|
||||
optional_params: Final[dict[str, Any]] = {}
|
||||
supported_params: Final = self.get_supported_openai_params(model)
|
||||
|
||||
for key, value in dict(image_edit_optional_params).items():
|
||||
if value is None or key in optional_params:
|
||||
continue
|
||||
|
||||
if key in supported_params:
|
||||
if key == "size" and value:
|
||||
size_param = cast(str, value)
|
||||
self._validate_size_param(size_param)
|
||||
optional_params[key] = size_param
|
||||
else:
|
||||
optional_params[key] = value
|
||||
elif not drop_params:
|
||||
raise ValueError(
|
||||
f"Parameter {key} is not supported for model {model}. "
|
||||
f"Supported parameters are {supported_params}. "
|
||||
f"Set drop_params=True to drop unsupported parameters."
|
||||
)
|
||||
|
||||
if "size" not in optional_params:
|
||||
optional_params["size"] = self.DEFAULT_SIZE
|
||||
|
||||
return optional_params
|
||||
|
||||
def _validate_size_param(self, size: str) -> None:
|
||||
known_sizes: Final = {
|
||||
"1024x1024",
|
||||
"1792x1024",
|
||||
"1024x1792",
|
||||
"512x512",
|
||||
"256x256",
|
||||
}
|
||||
|
||||
if size in known_sizes:
|
||||
return
|
||||
|
||||
if "x" in size:
|
||||
try:
|
||||
tuple(map(int, size.lower().split("x", 1)))
|
||||
return
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid size format: '{size}'. Expected format 'WIDTHxHEIGHT' (e.g., '1024x1024').")
|
||||
|
||||
raise ValueError(
|
||||
f"Unsupported size value: '{size}'. Use a known size (e.g., '1024x1024') or a custom 'WIDTHxHEIGHT' string."
|
||||
)
|
||||
return ["prompt", "image", "model", "n"]
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any, Final
|
|||
|
||||
import httpx
|
||||
|
||||
from litellm.exceptions import UnsupportedParamsError
|
||||
from litellm.llms.base_llm.image_generation.transformation import (
|
||||
BaseImageGenerationConfig,
|
||||
)
|
||||
|
|
@ -21,6 +22,10 @@ class AzureFoundryMAIImageGenerationConfig(BaseImageGenerationConfig):
|
|||
DEFAULT_WIDTH = 1024
|
||||
DEFAULT_HEIGHT = 1024
|
||||
|
||||
MAX_IMAGES_PER_REQUEST: Final = 1
|
||||
MIN_DIMENSION_PX: Final = 768
|
||||
MAX_TOTAL_PX: Final = 1_056_768
|
||||
|
||||
@staticmethod
|
||||
def get_mai_image_generation_url(
|
||||
api_base: str | None,
|
||||
|
|
@ -145,16 +150,27 @@ class AzureFoundryMAIImageGenerationConfig(BaseImageGenerationConfig):
|
|||
|
||||
if k in supported_params:
|
||||
if k == "size" and v:
|
||||
self._map_size_param(v, optional_params)
|
||||
self._map_size_param(v, optional_params, model)
|
||||
elif k == "n" and v is not None and self._image_count(v, model) != self.MAX_IMAGES_PER_REQUEST:
|
||||
if not drop_params:
|
||||
raise self._unsupported(
|
||||
model,
|
||||
f"n={v} is not supported for model {model}. The Azure AI MAI image "
|
||||
f"endpoint returns exactly {self.MAX_IMAGES_PER_REQUEST} image per "
|
||||
"request and ignores any count, so a larger value would silently "
|
||||
"return fewer images than requested. Send one request per image, or "
|
||||
"set drop_params=True to drop n.",
|
||||
)
|
||||
else:
|
||||
optional_params[k] = v
|
||||
elif k in ("width", "height"):
|
||||
optional_params[k] = v
|
||||
elif not drop_params:
|
||||
raise ValueError(
|
||||
raise self._unsupported(
|
||||
model,
|
||||
f"Parameter {k} is not supported for model {model}. "
|
||||
f"Supported parameters are {supported_params} and width/height. "
|
||||
f"Set drop_params=True to drop unsupported parameters."
|
||||
f"Set drop_params=True to drop unsupported parameters.",
|
||||
)
|
||||
|
||||
if "width" not in optional_params:
|
||||
|
|
@ -165,7 +181,19 @@ class AzureFoundryMAIImageGenerationConfig(BaseImageGenerationConfig):
|
|||
optional_params.pop("size", None)
|
||||
return optional_params
|
||||
|
||||
def _map_size_param(self, size: str, optional_params: dict) -> None:
|
||||
@staticmethod
|
||||
def _unsupported(model: str, message: str) -> UnsupportedParamsError:
|
||||
return UnsupportedParamsError(message=message, llm_provider="azure_ai", model=model)
|
||||
|
||||
def _image_count(self, n: object, model: str) -> int:
|
||||
if isinstance(n, int):
|
||||
return n
|
||||
try:
|
||||
return int(str(n))
|
||||
except ValueError:
|
||||
raise self._unsupported(model, f"n={n!r} is not a whole number of images for model {model}.")
|
||||
|
||||
def _map_size_param(self, size: str, optional_params: dict, model: str) -> None:
|
||||
size_mapping: Final = {
|
||||
"1024x1024": (1024, 1024),
|
||||
"1792x1024": (1792, 1024),
|
||||
|
|
@ -176,19 +204,36 @@ class AzureFoundryMAIImageGenerationConfig(BaseImageGenerationConfig):
|
|||
|
||||
if size in size_mapping:
|
||||
width, height = size_mapping[size]
|
||||
optional_params["width"] = width
|
||||
optional_params["height"] = height
|
||||
elif "x" in size:
|
||||
try:
|
||||
width, height = map(int, size.lower().split("x"))
|
||||
optional_params["width"] = width
|
||||
optional_params["height"] = height
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid size format: '{size}'. Expected format 'WIDTHxHEIGHT' (e.g., '1024x1024').")
|
||||
raise self._unsupported(
|
||||
model, f"Invalid size format: '{size}'. Expected format 'WIDTHxHEIGHT' (e.g., '1024x1024')."
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
raise self._unsupported(
|
||||
model,
|
||||
f"Unsupported size value: '{size}'. "
|
||||
f"Use a known size (e.g., '1024x1024') or a custom 'WIDTHxHEIGHT' string."
|
||||
f"Use a known size (e.g., '1024x1024') or a custom 'WIDTHxHEIGHT' string.",
|
||||
)
|
||||
|
||||
self._validate_dimensions(model=model, size=size, width=width, height=height)
|
||||
optional_params["width"] = width
|
||||
optional_params["height"] = height
|
||||
|
||||
def _validate_dimensions(self, model: str, size: str, width: int, height: int) -> None:
|
||||
if width < self.MIN_DIMENSION_PX or height < self.MIN_DIMENSION_PX:
|
||||
raise self._unsupported(
|
||||
model,
|
||||
f"Unsupported size value: '{size}'. Azure AI MAI image models require width and "
|
||||
f"height of at least {self.MIN_DIMENSION_PX} pixels.",
|
||||
)
|
||||
if width * height > self.MAX_TOTAL_PX:
|
||||
raise self._unsupported(
|
||||
model,
|
||||
f"Unsupported size value: '{size}'. Azure AI MAI image models accept at most "
|
||||
f"{self.MAX_TOTAL_PX} total pixels ({width}x{height} is {width * height}).",
|
||||
)
|
||||
|
||||
def transform_image_generation_response(
|
||||
|
|
|
|||
|
|
@ -3412,7 +3412,7 @@ def get_optional_params_image_gen(
|
|||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=model or "",
|
||||
drop_params=drop_params if drop_params is not None else False,
|
||||
drop_params=litellm.drop_params is True or drop_params is True,
|
||||
)
|
||||
elif (
|
||||
custom_llm_provider == "openai"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import pytest
|
|||
|
||||
|
||||
import litellm
|
||||
from litellm.images.utils import ImageEditRequestUtils
|
||||
from litellm.llms.azure_ai.image_edit import (
|
||||
AzureFoundryMAIImageEditConfig,
|
||||
get_azure_ai_image_edit_config,
|
||||
|
|
@ -70,44 +71,48 @@ class TestAzureMAIImageEdit:
|
|||
assert "/mai/v1/images/edits" in url
|
||||
assert "api-version=preview" in url
|
||||
|
||||
def test_map_openai_params_keeps_size(self):
|
||||
config = AzureFoundryMAIImageEditConfig()
|
||||
optional_params = config.map_openai_params(
|
||||
image_edit_optional_params={"size": "1792x1024", "n": 1},
|
||||
def test_get_optional_params_image_edit_size_raises_400(self, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "drop_params", False)
|
||||
with pytest.raises(litellm.UnsupportedParamsError, match="size") as exc_info:
|
||||
ImageEditRequestUtils.get_optional_params_image_edit(
|
||||
model="MAI-Image-2.5",
|
||||
image_edit_provider_config=AzureFoundryMAIImageEditConfig(),
|
||||
image_edit_optional_params={"size": "1024x1024", "n": 1},
|
||||
)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
def test_get_optional_params_image_edit_size_dropped_with_drop_params(self, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "drop_params", False)
|
||||
optional_params = ImageEditRequestUtils.get_optional_params_image_edit(
|
||||
model="MAI-Image-2.5",
|
||||
image_edit_provider_config=AzureFoundryMAIImageEditConfig(),
|
||||
image_edit_optional_params={"size": "1024x1024", "n": 1},
|
||||
drop_params=True,
|
||||
)
|
||||
assert optional_params["size"] == "1792x1024"
|
||||
assert "size" not in optional_params
|
||||
assert optional_params["n"] == 1
|
||||
assert "width" not in optional_params
|
||||
assert "height" not in optional_params
|
||||
|
||||
def test_map_openai_params_defaults_size(self):
|
||||
config = AzureFoundryMAIImageEditConfig()
|
||||
optional_params = config.map_openai_params(
|
||||
image_edit_optional_params={},
|
||||
def test_get_optional_params_image_edit_without_size_forwards_nothing_extra(self, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "drop_params", False)
|
||||
optional_params = ImageEditRequestUtils.get_optional_params_image_edit(
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=True,
|
||||
image_edit_provider_config=AzureFoundryMAIImageEditConfig(),
|
||||
image_edit_optional_params={},
|
||||
)
|
||||
assert optional_params["size"] == "1024x1024"
|
||||
assert optional_params == {}
|
||||
|
||||
def test_map_openai_params_unsupported_size_raises(self):
|
||||
config = AzureFoundryMAIImageEditConfig()
|
||||
with pytest.raises(ValueError, match="Unsupported size value: 'auto'"):
|
||||
config.map_openai_params(
|
||||
image_edit_optional_params={"size": "auto"},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=True,
|
||||
)
|
||||
|
||||
def test_map_openai_params_invalid_size_format_raises(self):
|
||||
config = AzureFoundryMAIImageEditConfig()
|
||||
with pytest.raises(ValueError, match="Invalid size format: '1024xabc'"):
|
||||
config.map_openai_params(
|
||||
image_edit_optional_params={"size": "1024xabc"},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=True,
|
||||
def test_image_edit_size_surfaces_as_400(self, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "drop_params", False)
|
||||
with pytest.raises(litellm.BadRequestError) as exc_info:
|
||||
litellm.image_edit(
|
||||
model="azure_ai/MAI-Image-2.5",
|
||||
image=io.BytesIO(b"fake-image-bytes"),
|
||||
prompt="Turn this into a studio product shot",
|
||||
size="1024x1024",
|
||||
api_key="test-key",
|
||||
api_base="https://my-resource.services.ai.azure.com",
|
||||
)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
def test_transform_image_edit_request_uses_image_field(self):
|
||||
config = AzureFoundryMAIImageEditConfig()
|
||||
|
|
@ -117,14 +122,14 @@ class TestAzureMAIImageEdit:
|
|||
model="MAI-Image-2.5",
|
||||
prompt="Turn this into a studio product shot",
|
||||
image=image_bytes,
|
||||
image_edit_optional_request_params={"size": "1024x1024", "n": 1},
|
||||
image_edit_optional_request_params={"n": 1},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert data["model"] == "MAI-Image-2.5"
|
||||
assert data["prompt"] == "Turn this into a studio product shot"
|
||||
assert data["size"] == "1024x1024"
|
||||
assert "size" not in data
|
||||
assert data["n"] == 1
|
||||
assert len(files) == 1
|
||||
assert files[0][0] == "image"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from unittest.mock import MagicMock
|
|||
import httpx
|
||||
import pytest
|
||||
|
||||
|
||||
import litellm
|
||||
from litellm.exceptions import UnsupportedParamsError
|
||||
from litellm.llms.azure.azure import AzureChatCompletion
|
||||
from litellm.llms.azure.image_generation import get_azure_image_generation_config
|
||||
from litellm.llms.azure.image_generation.http_utils import (
|
||||
|
|
@ -29,9 +29,7 @@ from litellm.utils import get_optional_params_image_gen
|
|||
class TestAzureMAIImageGeneration:
|
||||
def test_is_mai_model(self):
|
||||
assert AzureFoundryMAIImageGenerationConfig.is_mai_model("MAI-Image-2.5")
|
||||
assert AzureFoundryMAIImageGenerationConfig.is_mai_model(
|
||||
"azure_ai/MAI-Image-2.5"
|
||||
)
|
||||
assert AzureFoundryMAIImageGenerationConfig.is_mai_model("azure_ai/MAI-Image-2.5")
|
||||
assert AzureFoundryMAIImageGenerationConfig.is_mai_model("MAI-Image-2.5-Flash")
|
||||
assert AzureFoundryMAIImageGenerationConfig.is_mai_model("MAI-Image-2e")
|
||||
assert not AzureFoundryMAIImageGenerationConfig.is_mai_model("flux.2-pro")
|
||||
|
|
@ -42,16 +40,10 @@ class TestAzureMAIImageGeneration:
|
|||
api_base="https://my-resource.services.ai.azure.com",
|
||||
api_version="preview",
|
||||
)
|
||||
assert (
|
||||
url
|
||||
== "https://my-resource.services.ai.azure.com/mai/v1/images/generations?api-version=preview"
|
||||
)
|
||||
assert url == "https://my-resource.services.ai.azure.com/mai/v1/images/generations?api-version=preview"
|
||||
|
||||
def test_get_mai_image_generation_url_preserves_full_path(self):
|
||||
api = (
|
||||
"https://my-resource.services.ai.azure.com/mai/v1/images/generations"
|
||||
"?api-version=preview"
|
||||
)
|
||||
api = "https://my-resource.services.ai.azure.com/mai/v1/images/generations?api-version=preview"
|
||||
url = AzureFoundryMAIImageGenerationConfig.get_mai_image_generation_url(
|
||||
api_base=api,
|
||||
api_version="preview",
|
||||
|
|
@ -63,10 +55,7 @@ class TestAzureMAIImageGeneration:
|
|||
api_base="https://my-resource.services.ai.azure.com/mai/v1",
|
||||
api_version="preview",
|
||||
)
|
||||
assert (
|
||||
url
|
||||
== "https://my-resource.services.ai.azure.com/mai/v1/images/generations?api-version=preview"
|
||||
)
|
||||
assert url == "https://my-resource.services.ai.azure.com/mai/v1/images/generations?api-version=preview"
|
||||
|
||||
def test_get_azure_ai_image_generation_config_returns_mai(self):
|
||||
config = get_azure_ai_image_generation_config("MAI-Image-2.5")
|
||||
|
|
@ -104,13 +93,13 @@ class TestAzureMAIImageGeneration:
|
|||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
optional_params = get_optional_params_image_gen(
|
||||
model="MAI-Image-2.5",
|
||||
size="1792x1024",
|
||||
size="1024x1024",
|
||||
n=1,
|
||||
custom_llm_provider="azure_ai",
|
||||
provider_config=config,
|
||||
drop_params=True,
|
||||
)
|
||||
assert optional_params["width"] == 1792
|
||||
assert optional_params["width"] == 1024
|
||||
assert optional_params["height"] == 1024
|
||||
assert "size" not in optional_params
|
||||
|
||||
|
|
@ -127,10 +116,7 @@ class TestAzureMAIImageGeneration:
|
|||
assert "api-version=preview" in url
|
||||
|
||||
def test_mai_json_body_keeps_model(self):
|
||||
api = (
|
||||
"https://my-resource.services.ai.azure.com/mai/v1/images/generations"
|
||||
"?api-version=preview"
|
||||
)
|
||||
api = "https://my-resource.services.ai.azure.com/mai/v1/images/generations?api-version=preview"
|
||||
data = {
|
||||
"model": "MAI-Image-2.5",
|
||||
"prompt": "A photograph of a red fox",
|
||||
|
|
@ -176,7 +162,7 @@ class TestAzureMAIImageGeneration:
|
|||
|
||||
def test_map_openai_params_unsupported_size_raises(self):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(ValueError, match="Unsupported size value: 'auto'"):
|
||||
with pytest.raises(UnsupportedParamsError, match="Unsupported size value: 'auto'"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"size": "auto"},
|
||||
optional_params={},
|
||||
|
|
@ -186,7 +172,7 @@ class TestAzureMAIImageGeneration:
|
|||
|
||||
def test_map_openai_params_invalid_custom_size_raises(self):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(ValueError, match="Invalid size format: '1024xabc'"):
|
||||
with pytest.raises(UnsupportedParamsError, match="Invalid size format: '1024xabc'"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"size": "1024xabc"},
|
||||
optional_params={},
|
||||
|
|
@ -194,9 +180,138 @@ class TestAzureMAIImageGeneration:
|
|||
drop_params=True,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("size", ["512x512", "256x256", "700x1400"])
|
||||
def test_map_openai_params_size_below_minimum_dimension_raises(self, size):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(UnsupportedParamsError, match="at least 768 pixels"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"size": size},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=True,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("size", ["1792x1024", "1024x1792"])
|
||||
def test_map_openai_params_size_over_total_pixel_budget_raises(self, size):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(UnsupportedParamsError, match="at most 1056768 total pixels"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"size": size},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=True,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("size", ["1032x1024", "1376x768"])
|
||||
def test_map_openai_params_size_at_live_pixel_cap_passes_through(self, size):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"size": size},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=False,
|
||||
)
|
||||
assert optional_params["width"] * optional_params["height"] == 1_056_768
|
||||
|
||||
def test_map_openai_params_size_one_pixel_over_live_cap_raises(self):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(UnsupportedParamsError, match="at most 1056768 total pixels"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"size": "1033x1024"},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
def test_map_openai_params_explicit_width_height_not_range_checked(self):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"width": 1792, "height": 1024},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=True,
|
||||
)
|
||||
assert optional_params["width"] == 1792
|
||||
assert optional_params["height"] == 1024
|
||||
|
||||
@pytest.mark.parametrize("n", [2, 4, "2", 0, -1])
|
||||
def test_map_openai_params_n_other_than_one_raises(self, n):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(UnsupportedParamsError, match="returns exactly 1 image per request"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"n": n},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
def test_map_openai_params_non_numeric_n_raises_400(self):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(UnsupportedParamsError, match="not a whole number of images") as exc_info:
|
||||
config.map_openai_params(
|
||||
non_default_params={"n": "abc"},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=False,
|
||||
)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
def test_get_optional_params_image_gen_global_drop_params_drops_multi_image_n(self, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "drop_params", True)
|
||||
optional_params = get_optional_params_image_gen(
|
||||
model="MAI-Image-2.5",
|
||||
n=4,
|
||||
custom_llm_provider="azure_ai",
|
||||
provider_config=AzureFoundryMAIImageGenerationConfig(),
|
||||
)
|
||||
assert "n" not in optional_params
|
||||
assert optional_params["width"] == 1024
|
||||
|
||||
def test_get_optional_params_image_gen_without_any_drop_params_still_raises(self, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "drop_params", False)
|
||||
with pytest.raises(UnsupportedParamsError, match="returns exactly 1 image per request"):
|
||||
get_optional_params_image_gen(
|
||||
model="MAI-Image-2.5",
|
||||
n=4,
|
||||
custom_llm_provider="azure_ai",
|
||||
provider_config=AzureFoundryMAIImageGenerationConfig(),
|
||||
)
|
||||
|
||||
def test_map_openai_params_multi_image_n_dropped_with_drop_params(self):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"n": 4},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=True,
|
||||
)
|
||||
assert "n" not in optional_params
|
||||
|
||||
def test_map_openai_params_single_image_n_still_passes_through(self):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"n": 1},
|
||||
optional_params={},
|
||||
model="MAI-Image-2.5",
|
||||
drop_params=False,
|
||||
)
|
||||
assert optional_params["n"] == 1
|
||||
|
||||
@pytest.mark.parametrize("params", [{"n": 2}, {"n": "abc"}, {"size": "512x512"}, {"size": "1792x1024"}])
|
||||
def test_image_generation_rejected_params_surface_as_400(self, params):
|
||||
with pytest.raises(litellm.BadRequestError) as exc_info:
|
||||
litellm.image_generation(
|
||||
model="azure_ai/MAI-Image-2.5",
|
||||
prompt="A photograph of a red fox",
|
||||
api_key="test-key",
|
||||
api_base="https://my-resource.services.ai.azure.com",
|
||||
**params,
|
||||
)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
def test_map_openai_params_unsupported_param_raises(self):
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(ValueError, match="Parameter quality is not supported"):
|
||||
with pytest.raises(UnsupportedParamsError, match="Parameter quality is not supported"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"quality": "hd"},
|
||||
optional_params={},
|
||||
|
|
@ -343,16 +458,12 @@ class TestAzureMAIImageGeneration:
|
|||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
model = "azure_ai/MAI-Image-2.5"
|
||||
model_info = litellm.get_model_info(model=model, custom_llm_provider="azure_ai")
|
||||
image_response = ImageResponse(
|
||||
data=[ImageObject(b64_json="img1"), ImageObject(b64_json="img2")]
|
||||
)
|
||||
image_response = ImageResponse(data=[ImageObject(b64_json="img1"), ImageObject(b64_json="img2")])
|
||||
|
||||
cost = azure_ai_image_cost_calculator(
|
||||
model=model,
|
||||
image_response=image_response,
|
||||
)
|
||||
|
||||
assert (
|
||||
cost == len(image_response.data or []) * model_info["output_cost_per_image"]
|
||||
)
|
||||
assert cost == len(image_response.data or []) * model_info["output_cost_per_image"]
|
||||
assert cost > 0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue