[Feature] HunyuanImage-3.0 IT2I: multi-image input + prompt API cleanup (#3444)

Signed-off-by: TaffyOfficial <2324465096@qq.com>
Signed-off-by: TaffyOfficial <wu15922848573@outlook.com>
Signed-off-by: skf1999 <13234016272@163.com>
Signed-off-by: zuiho <2324465096@qq.com>
Signed-off-by: Claude Code <noreply@anthropic.com>
Signed-off-by: zuiho <wu15922848573@outlook.com>
Signed-off-by: TaffyOfficial <2587297563@qq.com>
Co-authored-by: TaffyOfficial <2324465096@qq.com>
Co-authored-by: TaffyOfficial <wu15922848573@outlook.com>
Co-authored-by: skf1999 <13234016272@163.com>
This commit is contained in:
authored and GitHub committed 2026-05-14 16:58:40 +08:00
1 parent c4f859bf56
commit 3f63aaf982
17 files changed
+1559 -534

No files matched your search

+37 -9
View File
@@ -1700,7 +1700,10 @@ async def edit_images(
# vllm-omni extension for layered models (e.g., Qwen-Image-Layered)
layers: int | None = Form(None),
resolution: int | None = Form(None), # See SUPPORTED_LAYERED_RESOLUTIONS
# /v1/images/edits is always IT2I; only the prompting knobs are exposed.
bot_task: str | None = Form(None),
sys_type: str | None = Form(None),
system_prompt: str | None = Form(None),
) -> ImageGenerationResponse:
"""
OpenAI-compatible image edit endpoint.
@@ -1751,16 +1754,20 @@ async def edit_images(
status_code=HTTPStatus.BAD_REQUEST.value,
detail=detail,
)
pil_images = await _load_input_images(input_images_list)
# Match the offline path: RGB normalize when the caller opts into
# Hunyuan-aware behavior. RGBA/P uploads otherwise diverge from offline.
normalize_edit_images_rgb = bot_task is not None or sys_type is not None
pil_images = await _load_input_images(input_images_list, normalize_rgb=normalize_edit_images_rgb)
prompt["multi_modal_data"] = {}
prompt["multi_modal_data"]["image"] = pil_images
if mask_image is not None:
loaded = await _load_input_images([mask_image])
# Mask role is different (alpha channel matters); never normalize.
loaded = await _load_input_images([mask_image], normalize_rgb=False)
prompt["multi_modal_data"]["mask_image"] = loaded[0]
if reference_image is not None:
loaded = await _load_input_images([reference_image])
loaded = await _load_input_images([reference_image], normalize_rgb=normalize_edit_images_rgb)
prompt["multi_modal_data"]["reference_image"] = loaded[0]
# 3 Build sample params
@@ -1811,7 +1818,8 @@ async def edit_images(
# 3.3 Parse and add size if provided
width, height = None, None
if size.lower() == "auto":
size_was_auto = size.lower() == "auto"
if size_was_auto:
if resolution is None:
# No resolution specified, use input image size
width, height = pil_images[0].size
@@ -1882,10 +1890,13 @@ async def edit_images(
"seed": effective_seed,
"num_outputs_per_prompt": n,
}
if width is not None:
extra_body["width"] = width
if height is not None:
extra_body["height"] = height
# size="auto" resolves width/height from input image; forwarding
# those would override AR-driven `<img_ratio_*>` token selection.
if not size_was_auto:
if width is not None:
extra_body["width"] = width
if height is not None:
extra_body["height"] = height
if negative_prompt is not None:
extra_body["negative_prompt"] = negative_prompt
if num_inference_steps is not None:
@@ -1907,6 +1918,10 @@ async def edit_images(
extra_body["lora"] = lora_dict
if bot_task is not None:
extra_body["bot_task"] = bot_task
if sys_type is not None:
extra_body["sys_type"] = sys_type
if system_prompt is not None:
extra_body["system_prompt"] = system_prompt
prompt_text = prompt.get("prompt", "")
generation_result = await chat_handler.generate_diffusion_images(
@@ -2187,6 +2202,8 @@ def _extract_images_from_result(result: Any) -> list[Any]:
async def _load_input_images(
inputs: list[str],
*,
normalize_rgb: bool = True,
) -> list[Image.Image]:
"""
convert to PIL.Image.Image list
@@ -2233,7 +2250,18 @@ async def _load_input_images(
if not images:
raise ValueError("No valid input images found")
return images
if not normalize_rgb:
return images
# Match the offline HunyuanImage3 image-edit example path, which eagerly
# normalizes input files with ``Image.open(...).convert("RGB")`` before
# they reach the AR stage. Keeping uploads as RGBA/P PIL objects makes
# online IT2I observe a different visual input than offline (for example
# transparent-logo uploads alpha-composited over white instead of black),
# which is enough for HunyuanImage3 AR recaption to diverge before DiT
# sees the request -- root cause of the "online 3 magnets vs offline 1
# magnet" systematic semantic mismatch.
return [img.convert("RGB") for img in images]
def _choose_output_format(output_format: str | None, background: str | None) -> str: