[Bugfix] fix: return 400 for unsupported multi-image edits such as Qwen-Image-Layered (#2298)

Signed-off-by: David Chen <530634352@qq.com>
This commit is contained in:
WeiQing Chen authored and GitHub committed 2026-03-30 20:24:57 +08:00
1 parent 911be0d426
commit 1eee8435e1
6 files changed
+91

No files matched your search

@@ -740,6 +740,29 @@ def test_image_edit_images_processing(async_omni_test_client):
assert processed_images[1].size == (24, 24)
def test_image_edit_rejects_multiple_images_when_model_does_not_support_them(async_omni_test_client):
img_bytes_1 = make_test_image_bytes((16, 16))
img_bytes_2 = make_test_image_bytes((32, 32))
engine = async_omni_test_client.app.state.engine_client
engine.get_diffusion_od_config = lambda: SimpleNamespace(supports_multimodal_inputs=False)
response = async_omni_test_client.post(
"/v1/images/edits",
files=[
("image", img_bytes_1),
("image", img_bytes_2),
],
data={"prompt": "hello world."},
)
assert response.status_code == 400
assert (
response.json()["detail"] == "Received multiple input images. Only a single image is supported by this model."
)
assert engine.captured_prompt is None
def test_image_edit_parameter_pass(async_omni_test_client):
img_bytes_1 = make_test_image_bytes((16, 16))
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import pytest
from vllm_omni.entrypoints.async_omni_diffusion import AsyncOmniDiffusion
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_get_diffusion_od_config_returns_direct_config():
diffusion = object.__new__(AsyncOmniDiffusion)
diffusion.od_config = object()
assert diffusion.get_diffusion_od_config() is diffusion.od_config
@@ -379,6 +379,19 @@ def test_openai_serving_models_can_consume_async_omni_compat_attrs():
assert serving_models.input_processor is input_processor
def test_get_diffusion_od_config_returns_diffusion_stage_config():
diffusion_od_config = object()
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
stage_clients=[
SimpleNamespace(stage_type="llm"),
SimpleNamespace(stage_type="diffusion", _engine=SimpleNamespace(od_config=diffusion_od_config)),
]
)
assert omni.get_diffusion_od_config() is diffusion_od_config
@pytest.mark.asyncio
async def test_async_omni_yields_only_final_stage_outputs(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(
+17
View File
@@ -118,6 +118,23 @@ class AsyncOmni(EngineClient, OmniBase):
"""Compatibility helper for call sites expecting async vllm config access."""
return self.vllm_config
def get_diffusion_od_config(self) -> Any | None:
"""Return the diffusion-stage config when the pipeline has one."""
for stage_client in self.engine.stage_clients:
if getattr(stage_client, "stage_type", None) != "diffusion":
continue
od_config = getattr(stage_client, "od_config", None)
if od_config is not None:
return od_config
inner_engine = getattr(stage_client, "_engine", None)
od_config = getattr(inner_engine, "od_config", None)
if od_config is not None:
return od_config
return None
@property
def model_config(self):
"""Return the model config for the comprehension stage when present."""
@@ -251,6 +251,10 @@ class AsyncOmniDiffusion:
finished=True,
)
def get_diffusion_od_config(self) -> OmniDiffusionConfig:
"""Return the diffusion config used by this engine."""
return self.od_config
# ------------------------------------------------------------------
# Public generate API
# ------------------------------------------------------------------
@@ -1406,6 +1406,11 @@ async def edit_images(
if not input_images_list:
raise HTTPException(status_code=422, detail="Field 'image' or 'url' is required")
pil_images = await _load_input_images(input_images_list)
if len(pil_images) > 1 and not _supports_multimodal_image_inputs(raw_request, engine_client):
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST.value,
detail="Received multiple input images. Only a single image is supported by this model.",
)
prompt["multi_modal_data"] = {}
prompt["multi_modal_data"]["image"] = pil_images
@@ -1584,6 +1589,20 @@ def _get_engine_and_model(raw_request: Request):
return engine_client, model_name, normalized_stage_configs
def _supports_multimodal_image_inputs(raw_request: Request, engine_client: Any) -> bool:
diffusion_engine = getattr(raw_request.app.state, "diffusion_engine", None) or engine_client
get_diffusion_od_config = getattr(diffusion_engine, "get_diffusion_od_config", None)
od_config = (
get_diffusion_od_config() if callable(get_diffusion_od_config) else getattr(diffusion_engine, "od_config", None)
)
if od_config is None:
# Preserve the existing compatibility behavior when the diffusion
# config is not exposed on the serving surface.
return True
return bool(getattr(od_config, "supports_multimodal_inputs", False))
def _get_lora_from_json_str(lora_body):
if lora_body is None:
return None