From dfb5357448fcb8820c098dd888a1457e35fba64a Mon Sep 17 00:00:00 2001 From: Mick Date: Wed, 24 Dec 2025 13:26:13 +0800 Subject: [PATCH] [diffusion] http-server: relax openai image endpoint's strict content_type limit (#15717) --- .../sglang/multimodal_gen/docs/openai_api.md | 8 +++-- .../runtime/entrypoints/openai/utils.py | 35 ++++++++++++------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/python/sglang/multimodal_gen/docs/openai_api.md b/python/sglang/multimodal_gen/docs/openai_api.md index c76514f66..eae8ef6cf 100644 --- a/python/sglang/multimodal_gen/docs/openai_api.md +++ b/python/sglang/multimodal_gen/docs/openai_api.md @@ -104,14 +104,15 @@ curl -sS -X POST "http://localhost:30010/v1/images/generations" \ **Endpoint:** `POST /v1/images/edits` -This endpoint accepts a multipart form upload with an input image and a text prompt. The server can return either a base64-encoded image or a URL to download the image. +This endpoint accepts a multipart form upload with input images and a text prompt. The server can return either a base64-encoded image or a URL to download the image. **Curl Example (b64_json response):** ```bash curl -sS -X POST "http://localhost:30010/v1/images/edits" \ -H "Authorization: Bearer sk-proj-1234567890" \ - -F "image=@input.png" \ + -F "image=@local_input_image.png" \ + -F "url=image_url.jpg" \ -F "prompt=A calico cat playing a piano on stage" \ -F "size=1024x1024" \ -F "response_format=b64_json" @@ -122,7 +123,8 @@ curl -sS -X POST "http://localhost:30010/v1/images/edits" \ ```bash curl -sS -X POST "http://localhost:30010/v1/images/edits" \ -H "Authorization: Bearer sk-proj-1234567890" \ - -F "image=@input.png" \ + -F "image=@local_input_image.png" \ + -F "url=image_url.jpg" \ -F "prompt=A calico cat playing a piano on stage" \ -F "size=1024x1024" \ -F "response_format=url" diff --git a/python/sglang/multimodal_gen/runtime/entrypoints/openai/utils.py b/python/sglang/multimodal_gen/runtime/entrypoints/openai/utils.py index 0b61e44e3..839b40d0a 100644 --- a/python/sglang/multimodal_gen/runtime/entrypoints/openai/utils.py +++ b/python/sglang/multimodal_gen/runtime/entrypoints/openai/utils.py @@ -64,7 +64,7 @@ async def _save_upload_to_path(upload: UploadFile, target_path: str) -> str: return target_path -async def _maybe_url_image(img_url: str, target_path: str) -> str: +async def _maybe_url_image(img_url: str, target_path: str) -> str | None: if not isinstance(img_url, str): return None @@ -86,25 +86,36 @@ async def _save_url_image_to_path(image_url: str, target_path: str) -> str: os.makedirs(os.path.dirname(target_path), exist_ok=True) try: - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(follow_redirects=True) as client: response = await client.get(image_url, timeout=10.0) response.raise_for_status() # Determine file extension from content type or URL after downloading if not os.path.splitext(target_path)[1]: - content_type = response.headers.get("content-type", "") - if not content_type.startswith("image/"): + content_type = response.headers.get("content-type", "").lower() + + url_path = image_url.split("?")[0] + _, url_ext = os.path.splitext(url_path) + url_ext = url_ext.lower() + + if url_ext in {".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp"}: + ext = ".jpg" if url_ext == ".jpeg" else url_ext + elif content_type.startswith("image/"): + if "jpeg" in content_type or "jpg" in content_type: + ext = ".jpg" + elif "png" in content_type: + ext = ".png" + elif "webp" in content_type: + ext = ".webp" + else: + ext = ".jpg" # Default to jpg + elif content_type == "application/octet-stream": + # for octet-stream, if we couldn't get it from URL, default to jpg + ext = ".jpg" + else: raise ValueError( f"URL does not point to an image. Content-Type: {content_type}" ) - if "jpeg" in content_type or "jpg" in content_type: - ext = ".jpg" - elif "png" in content_type: - ext = ".png" - elif "webp" in content_type: - ext = ".webp" - else: - ext = ".jpg" # Default to jpg target_path = f"{target_path}{ext}" with open(target_path, "wb") as f: