fix(azure-ai): keep FLUX.2 tolerant of OpenAI-only image params

This commit is contained in:
mateo-berri 2026-09-18 12:41:40 -07:00
parent cf08440557
commit c0b0ba20b2
4 changed files with 67 additions and 29 deletions

View file

@ -31,22 +31,7 @@ class AzureFoundryFlux2ImageEditConfig(OpenAIImageEditConfig):
"""
def get_supported_openai_params(self, model: str) -> list:
"""
FLUX 2 supports a subset of OpenAI image edit params
"""
return [
"n",
"size",
"width",
"height",
"num_images",
"seed",
"safety_tolerance",
"output_format",
"aspect_ratio",
"guidance",
"steps",
]
return AzureFoundryFluxImageGenerationConfig().get_supported_openai_params(model)
def map_openai_params(
self,

View file

@ -2,9 +2,18 @@ from collections.abc import Mapping
from types import MappingProxyType
from typing import Final
from litellm.exceptions import BadRequestError, UnsupportedParamsError
from litellm.llms.openai.image_generation import GPTImageGenerationConfig
from litellm.types.llms.openai import OpenAIImageGenerationOptionalParams
FLUX2_DROPPED_OPENAI_PARAMS: Final[tuple[OpenAIImageGenerationOptionalParams, ...]] = (
"background",
"moderation",
"output_compression",
"quality",
"user",
)
class AzureFoundryFluxImageGenerationConfig(GPTImageGenerationConfig):
"""Azure Foundry BFL API configuration for FLUX image generation."""
@ -81,10 +90,13 @@ class AzureFoundryFluxImageGenerationConfig(GPTImageGenerationConfig):
"num_images",
"guidance",
"steps",
*FLUX2_DROPPED_OPENAI_PARAMS,
]
@staticmethod
def _map_parameter(name: str, value: object) -> tuple[tuple[str, object], ...]:
def _map_parameter(name: str, value: object, model: str) -> tuple[tuple[str, object], ...]:
if name in FLUX2_DROPPED_OPENAI_PARAMS:
return ()
if isinstance(value, str):
if name in ("n", "num_images", "width", "height", "steps", "seed", "safety_tolerance"):
return (("num_images" if name == "n" else name, int(value)),)
@ -94,11 +106,17 @@ class AzureFoundryFluxImageGenerationConfig(GPTImageGenerationConfig):
return (("num_images", value),)
if name != "size":
return ((name, value),)
if str(value).lower() == "auto":
return ()
try:
width, height = (int(dimension) for dimension in str(value).lower().split("x"))
except (TypeError, ValueError):
raise ValueError(f"Invalid size format '{value}'. Expected 'WxH', for example '1024x1024'.")
raise BadRequestError(
message=f"Invalid size format '{value}'. Expected 'WxH', for example '1024x1024'.",
model=model,
llm_provider="azure_ai",
)
return (("width", width), ("height", height))
def map_openai_params(
@ -118,9 +136,13 @@ class AzureFoundryFluxImageGenerationConfig(GPTImageGenerationConfig):
supported_params: Final = self.get_supported_openai_params(model)
unsupported_params: Final = tuple(name for name in non_default_params if name not in supported_params)
if unsupported_params and not drop_params:
raise ValueError(
f"Parameters {unsupported_params} are not supported for model {model}. "
f"Supported parameters are {supported_params}. Set drop_params=True to drop unsupported parameters."
raise UnsupportedParamsError(
message=(
f"Parameters {unsupported_params} are not supported for model {model}. "
f"Supported parameters are {supported_params}. Set drop_params=True to drop unsupported parameters."
),
model=model,
llm_provider="azure_ai",
)
mapped_params: Final[Mapping[str, object]] = MappingProxyType(
@ -128,7 +150,7 @@ class AzureFoundryFluxImageGenerationConfig(GPTImageGenerationConfig):
mapped_name: mapped_value
for name, value in non_default_params.items()
if name in supported_params
for mapped_name, mapped_value in self._map_parameter(name, value)
for mapped_name, mapped_value in self._map_parameter(name, value, model)
}
)
return {**optional_params, **mapped_params} # mutable-ok: inherited config contract returns a dict

View file

@ -176,3 +176,14 @@ def test_flux2_image_edit_preserves_controls_and_pixel_cost(dimensions: Mapping[
)
assert response._hidden_params["response_cost"] == pytest.approx(5e-08 * 2048 * 1024 * 2)
def test_flux2_image_edit_accepts_and_drops_openai_only_parameters():
optional_params: Final = ImageEditRequestUtils.get_optional_params_image_edit(
model="FLUX.2-pro",
image_edit_provider_config=AzureFoundryFlux2ImageEditConfig(),
image_edit_optional_params={"n": 1, "size": "auto", "quality": "high", "user": "end-user-1"},
drop_params=False,
)
assert optional_params == {"num_images": 1}

View file

@ -15,7 +15,7 @@ from litellm.llms.azure_ai.image_generation.flux_transformation import (
AzureFoundryFluxImageGenerationConfig,
)
from litellm.types.utils import ImageObject, ImageResponse
from litellm.utils import _invalidate_model_cost_lowercase_map
from litellm.utils import _invalidate_model_cost_lowercase_map, get_optional_params_image_gen
@pytest.fixture(autouse=True)
@ -86,15 +86,35 @@ def test_flux2_flex_maps_openai_and_provider_parameters():
}
def test_flux2_flex_rejects_invalid_size():
with pytest.raises(ValueError, match="Expected 'WxH'"):
AzureFoundryFluxImageGenerationConfig().map_openai_params(
non_default_params={"size": "large"},
optional_params={},
def test_flux2_flex_rejects_invalid_size_as_bad_request():
with pytest.raises(litellm.BadRequestError, match="Expected 'WxH'") as raised:
get_optional_params_image_gen(
model="FLUX.2-flex",
drop_params=False,
custom_llm_provider="azure_ai",
provider_config=AzureFoundryFluxImageGenerationConfig(),
size="large",
)
assert raised.value.status_code == 400
@pytest.mark.parametrize("model", ("FLUX.2-pro", "FLUX.2-flex"))
def test_flux2_accepts_and_drops_openai_only_image_parameters(model: str):
optional_params: Final = get_optional_params_image_gen(
model=model,
custom_llm_provider="azure_ai",
provider_config=AzureFoundryFluxImageGenerationConfig(),
n=1,
size="auto",
quality="high",
user="end-user-1",
background="transparent",
moderation="low",
output_compression=50,
)
assert optional_params == {"num_images": 1}
def test_flux2_flex_model_info():
model_info = litellm.get_model_info(