[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:
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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user