mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-23 00:41:40 +00:00
fix(azure_ai): surface rejected MAI image params as 400 and drop comments
This commit is contained in:
parent
808787659b
commit
84a99dfc31
2 changed files with 48 additions and 43 deletions
|
|
@ -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,16 +22,7 @@ class AzureFoundryMAIImageGenerationConfig(BaseImageGenerationConfig):
|
|||
DEFAULT_WIDTH = 1024
|
||||
DEFAULT_HEIGHT = 1024
|
||||
|
||||
# The MAI endpoint produces exactly one image per request. Its documented
|
||||
# body is model/prompt/width/height (plus `image` for edits) — there is no
|
||||
# count field, and `n` (or the native `sampleCount`) is accepted and
|
||||
# ignored, so a request for more silently comes back with one.
|
||||
MAX_IMAGES_PER_REQUEST: Final = 1
|
||||
|
||||
# Provider-side bounds on the generated image. Both are enforced by the
|
||||
# MAI endpoint, which 400s with "'width' must be at least 768 pixels."
|
||||
# Only `size` is checked against them: `width`/`height` pass through
|
||||
# unmapped, which keeps a future model with different bounds reachable.
|
||||
MIN_DIMENSION_PX: Final = 768
|
||||
MAX_TOTAL_PX: Final = 1024 * 1024
|
||||
|
||||
|
|
@ -158,25 +150,27 @@ class AzureFoundryMAIImageGenerationConfig(BaseImageGenerationConfig):
|
|||
|
||||
if k in supported_params:
|
||||
if k == "size" and v:
|
||||
self._map_size_param(v, optional_params)
|
||||
elif k == "n" and v is not None and v > self.MAX_IMAGES_PER_REQUEST:
|
||||
self._map_size_param(v, optional_params, model)
|
||||
elif k == "n" and v is not None and int(v) > self.MAX_IMAGES_PER_REQUEST:
|
||||
if not drop_params:
|
||||
raise ValueError(
|
||||
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."
|
||||
"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:
|
||||
|
|
@ -187,7 +181,11 @@ 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 _map_size_param(self, size: str, optional_params: dict, model: str) -> None:
|
||||
size_mapping: Final = {
|
||||
"1024x1024": (1024, 1024),
|
||||
"1792x1024": (1792, 1024),
|
||||
|
|
@ -202,34 +200,32 @@ class AzureFoundryMAIImageGenerationConfig(BaseImageGenerationConfig):
|
|||
try:
|
||||
width, height = map(int, size.lower().split("x"))
|
||||
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(size=size, width=width, height=height)
|
||||
self._validate_dimensions(model=model, size=size, width=width, height=height)
|
||||
optional_params["width"] = width
|
||||
optional_params["height"] = height
|
||||
|
||||
def _validate_dimensions(self, size: str, width: int, height: int) -> None:
|
||||
"""Reject a `size` the MAI endpoint would 400 on.
|
||||
|
||||
Several OpenAI-standard sizes are outside MAI's bounds: 512x512 and
|
||||
256x256 fall under the per-side minimum, and 1792x1024 / 1024x1792
|
||||
exceed the total pixel budget. Checking here turns an opaque provider
|
||||
400 into an error that names the constraint.
|
||||
"""
|
||||
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 ValueError(
|
||||
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."
|
||||
f"height of at least {self.MIN_DIMENSION_PX} pixels.",
|
||||
)
|
||||
if width * height > self.MAX_TOTAL_PX:
|
||||
raise ValueError(
|
||||
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})."
|
||||
f"{self.MAX_TOTAL_PX} total pixels ({width}x{height} is {width * height}).",
|
||||
)
|
||||
|
||||
def transform_image_generation_response(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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 (
|
||||
|
|
@ -180,7 +181,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={},
|
||||
|
|
@ -190,7 +191,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={},
|
||||
|
|
@ -200,9 +201,8 @@ class TestAzureMAIImageGeneration:
|
|||
|
||||
@pytest.mark.parametrize("size", ["512x512", "256x256", "700x1400"])
|
||||
def test_map_openai_params_size_below_minimum_dimension_raises(self, size):
|
||||
"""MAI requires >= 768px per side; the OpenAI size table offered smaller ones."""
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(ValueError, match="at least 768 pixels"):
|
||||
with pytest.raises(UnsupportedParamsError, match="at least 768 pixels"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"size": size},
|
||||
optional_params={},
|
||||
|
|
@ -212,9 +212,8 @@ class TestAzureMAIImageGeneration:
|
|||
|
||||
@pytest.mark.parametrize("size", ["1792x1024", "1024x1792"])
|
||||
def test_map_openai_params_size_over_total_pixel_budget_raises(self, size):
|
||||
"""MAI caps total pixels at 1024*1024, so both landscape/portrait sizes 400 upstream."""
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(ValueError, match="at most 1048576 total pixels"):
|
||||
with pytest.raises(UnsupportedParamsError, match="at most 1048576 total pixels"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"size": size},
|
||||
optional_params={},
|
||||
|
|
@ -223,7 +222,6 @@ class TestAzureMAIImageGeneration:
|
|||
)
|
||||
|
||||
def test_map_openai_params_explicit_width_height_not_range_checked(self):
|
||||
"""width/height pass through unmapped, so a future model's bounds stay reachable."""
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"width": 1792, "height": 1024},
|
||||
|
|
@ -234,11 +232,10 @@ class TestAzureMAIImageGeneration:
|
|||
assert optional_params["width"] == 1792
|
||||
assert optional_params["height"] == 1024
|
||||
|
||||
@pytest.mark.parametrize("n", [2, 4])
|
||||
@pytest.mark.parametrize("n", [2, 4, "2"])
|
||||
def test_map_openai_params_multi_image_n_raises(self, n):
|
||||
"""The MAI endpoint returns one image and ignores any count, so n>1 must not pass silently."""
|
||||
config = AzureFoundryMAIImageGenerationConfig()
|
||||
with pytest.raises(ValueError, match="returns exactly 1 image per request"):
|
||||
with pytest.raises(UnsupportedParamsError, match="returns exactly 1 image per request"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"n": n},
|
||||
optional_params={},
|
||||
|
|
@ -266,9 +263,21 @@ class TestAzureMAIImageGeneration:
|
|||
)
|
||||
assert optional_params["n"] == 1
|
||||
|
||||
@pytest.mark.parametrize("params", [{"n": 2}, {"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={},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue