2025-12-17 17:40:45 +08:00
import json
2025-11-17 14:44:10 +08:00
import multiprocessing
import multiprocessing.forkserver as forkserver
import os
2025-12-22 20:05:37 -05:00
# Image generation API imports
import time
2025-11-17 14:44:10 +08:00
from argparse import Namespace
2025-11-18 20:54:26 +08:00
from collections.abc import AsyncIterator
2025-11-17 14:44:10 +08:00
from contextlib import asynccontextmanager
2025-12-17 17:40:45 +08:00
from dataclasses import fields
2025-11-17 14:44:10 +08:00
from http import HTTPStatus
2025-12-16 15:19:40 +08:00
from typing import Any
2025-11-17 14:44:10 +08:00
2025-11-18 20:54:26 +08:00
import vllm.envs as envs
2025-11-17 14:44:10 +08:00
from fastapi import Depends , HTTPException , Request
from fastapi.responses import JSONResponse , StreamingResponse
from starlette.datastructures import State
from vllm.config import VllmConfig
from vllm.engine.protocol import EngineClient
2025-11-18 20:54:26 +08:00
from vllm.entrypoints.chat_utils import load_chat_template , resolve_hf_chat_template , resolve_mistral_chat_template
2025-11-17 14:44:10 +08:00
from vllm.entrypoints.launcher import serve_http
from vllm.entrypoints.logger import RequestLogger
from vllm.entrypoints.openai.api_server import (
base ,
build_app ,
load_log_config ,
maybe_register_tokenizer_info_endpoint ,
router ,
setup_server ,
validate_json_request ,
)
2025-11-18 20:54:26 +08:00
from vllm.entrypoints.openai.protocol import ChatCompletionRequest , ChatCompletionResponse , ErrorResponse
from vllm.entrypoints.openai.serving_models import BaseModelPath , LoRAModulePath , OpenAIServingModels
2025-11-17 14:44:10 +08:00
from vllm.entrypoints.openai.tool_parsers import ToolParserManager
# yapf conflicts with isort for this block
# yapf: disable
# yapf: enable
from vllm.entrypoints.tool_server import DemoToolServer , MCPToolServer , ToolServer
from vllm.entrypoints.utils import load_aware_call , with_cancellation
from vllm.logger import init_logger
2025-12-19 16:42:02 +08:00
from vllm.tokenizers import MistralTokenizer
from vllm.utils.system_utils import decorate_logs
2025-11-18 20:54:26 +08:00
2025-12-19 11:59:07 +08:00
from vllm_omni.diffusion.data import DiffusionParallelConfig , OmniDiffusionConfig
2025-12-17 17:40:45 +08:00
from vllm_omni.diffusion.utils.hf_utils import is_diffusion_model
from vllm_omni.entrypoints.async_diffusion import AsyncOmniDiffusion
2025-11-30 22:26:33 +08:00
from vllm_omni.entrypoints.async_omni import AsyncOmni
2025-12-22 20:05:37 -05:00
from vllm_omni.entrypoints.openai.image_api_utils import (
encode_image_base64 ,
parse_size ,
)
from vllm_omni.entrypoints.openai.protocol.images import (
ImageData ,
ImageGenerationRequest ,
ImageGenerationResponse ,
)
2025-11-17 14:44:10 +08:00
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
logger = init_logger ( __name__ )
2025-12-22 20:05:37 -05:00
# Server entry points
2025-11-17 14:44:10 +08:00
async def omni_run_server ( args , ** uvicorn_kwargs ) -> None :
2025-12-17 17:40:45 +08:00
"""Run a single-worker API server.
Automatically detects if the model is a diffusion model and routes
to the appropriate server implementation.
"""
2025-11-17 14:44:10 +08:00
# Add process-specific prefix to stdout and stderr.
decorate_logs ( "APIServer" )
listen_address , sock = setup_server ( args )
2025-12-17 17:40:45 +08:00
# Check if model is a diffusion model
if is_diffusion_model ( args . model ):
logger . info ( "Detected diffusion model, starting diffusion API server" )
await omni_run_diffusion_server_worker ( listen_address , sock , args , ** uvicorn_kwargs )
else :
await omni_run_server_worker ( listen_address , sock , args , ** uvicorn_kwargs )
async def omni_run_diffusion_server ( args , ** uvicorn_kwargs ) -> None :
"""Run a diffusion model API server."""
# Add process-specific prefix to stdout and stderr.
decorate_logs ( "DiffusionAPIServer" )
listen_address , sock = setup_server ( args )
await omni_run_diffusion_server_worker ( listen_address , sock , args , ** uvicorn_kwargs )
async def omni_run_diffusion_server_worker ( listen_address , sock , args , ** uvicorn_kwargs ) -> None :
"""Run a diffusion model API server worker."""
# Load logging config for uvicorn if specified
log_config = load_log_config ( args . log_config_file )
if log_config is not None :
uvicorn_kwargs [ "log_config" ] = log_config
async with build_async_diffusion ( args ) as diffusion_engine :
app = build_app ( args )
await omni_diffusion_init_app_state ( diffusion_engine , app . state , args )
logger . info ( "Starting vLLM Diffusion API server on %s " , listen_address )
shutdown_task = await serve_http (
app ,
sock = sock ,
enable_ssl_refresh = getattr ( args , "enable_ssl_refresh" , False ),
host = args . host ,
port = args . port ,
log_level = args . uvicorn_log_level ,
access_log = not getattr ( args , "disable_uvicorn_access_log" , False ),
timeout_keep_alive = envs . VLLM_HTTP_TIMEOUT_KEEP_ALIVE ,
ssl_keyfile = getattr ( args , "ssl_keyfile" , None ),
ssl_certfile = getattr ( args , "ssl_certfile" , None ),
ssl_ca_certs = getattr ( args , "ssl_ca_certs" , None ),
ssl_cert_reqs = getattr ( args , "ssl_cert_reqs" , 0 ),
h11_max_incomplete_event_size = getattr ( args , "h11_max_incomplete_event_size" , None ),
h11_max_header_count = getattr ( args , "h11_max_header_count" , None ),
** uvicorn_kwargs ,
)
# NB: Await server shutdown only after the backend context is exited
try :
await shutdown_task
finally :
sock . close ()
2025-11-17 14:44:10 +08:00
2025-11-18 20:54:26 +08:00
async def omni_run_server_worker ( listen_address , sock , args , client_config = None , ** uvicorn_kwargs ) -> None :
2025-11-17 14:44:10 +08:00
"""Run a single API server worker."""
if args . tool_parser_plugin and len ( args . tool_parser_plugin ) > 3 :
ToolParserManager . import_tool_parser ( args . tool_parser_plugin )
# Load logging config for uvicorn if specified
log_config = load_log_config ( args . log_config_file )
if log_config is not None :
uvicorn_kwargs [ "log_config" ] = log_config
2025-11-30 22:26:33 +08:00
async with build_async_omni (
2025-11-17 14:44:10 +08:00
args ,
client_config = client_config ,
) as engine_client :
maybe_register_tokenizer_info_endpoint ( args )
app = build_app ( args )
vllm_config = await engine_client . get_vllm_config ()
await omni_init_app_state ( engine_client , vllm_config , app . state , args )
logger . info (
"Starting vLLM API server %d on %s " ,
vllm_config . parallel_config . _api_process_rank ,
listen_address ,
)
shutdown_task = await serve_http (
app ,
sock = sock ,
enable_ssl_refresh = args . enable_ssl_refresh ,
host = args . host ,
port = args . port ,
log_level = args . uvicorn_log_level ,
# NOTE: When the 'disable_uvicorn_access_log' value is True,
# no access log will be output.
access_log = not args . disable_uvicorn_access_log ,
timeout_keep_alive = envs . VLLM_HTTP_TIMEOUT_KEEP_ALIVE ,
ssl_keyfile = args . ssl_keyfile ,
ssl_certfile = args . ssl_certfile ,
ssl_ca_certs = args . ssl_ca_certs ,
ssl_cert_reqs = args . ssl_cert_reqs ,
h11_max_incomplete_event_size = args . h11_max_incomplete_event_size ,
h11_max_header_count = args . h11_max_header_count ,
** uvicorn_kwargs ,
)
# NB: Await server shutdown only after the backend context is exited
try :
await shutdown_task
finally :
sock . close ()
@asynccontextmanager
2025-11-30 22:26:33 +08:00
async def build_async_omni (
2025-11-17 14:44:10 +08:00
args : Namespace ,
* ,
2025-12-16 15:19:40 +08:00
disable_frontend_multiprocessing : bool | None = None ,
client_config : dict [ str , Any ] | None = None ,
2025-11-17 14:44:10 +08:00
) -> AsyncIterator [ EngineClient ]:
2025-11-30 22:26:33 +08:00
"""Build an AsyncOmni instance from command-line arguments.
2025-11-28 11:03:52 +08:00
2025-11-30 22:26:33 +08:00
Creates an async context manager that yields an AsyncOmni instance
2025-11-28 11:03:52 +08:00
configured from the provided arguments. Handles forkserver setup if
needed and ensures proper cleanup on exit.
Args:
args: Parsed command-line arguments containing model and configuration
disable_frontend_multiprocessing: Optional flag to disable frontend
multiprocessing (deprecated in V1)
client_config: Optional client configuration dictionary
Yields:
2025-11-30 22:26:33 +08:00
EngineClient instance (AsyncOmni) ready for use
2025-11-28 11:03:52 +08:00
"""
2025-11-17 14:44:10 +08:00
if os . getenv ( "VLLM_WORKER_MULTIPROC_METHOD" ) == "forkserver" :
# The executor is expected to be mp.
# Pre-import heavy modules in the forkserver process
logger . debug ( "Setup forkserver with pre-imports" )
multiprocessing . set_start_method ( "forkserver" )
multiprocessing . set_forkserver_preload ([ "vllm.v1.engine.async_llm" ])
forkserver . ensure_running ()
logger . debug ( "Forkserver setup complete!" )
2025-11-30 22:26:33 +08:00
# Context manager to handle async_omni lifecycle
2025-11-17 14:44:10 +08:00
# Ensures everything is shutdown and cleaned up on error/exit
2025-11-30 22:26:33 +08:00
async with build_async_omni_from_stage_config (
2025-11-17 14:44:10 +08:00
args ,
disable_frontend_multiprocessing = disable_frontend_multiprocessing ,
2025-11-30 22:26:33 +08:00
) as async_omni :
yield async_omni
2025-11-17 14:44:10 +08:00
2025-12-17 17:40:45 +08:00
@asynccontextmanager
async def build_async_diffusion (
args : Namespace ,
** kwargs : Any ,
) -> AsyncIterator [ AsyncOmniDiffusion ]:
"""Build an AsyncOmniDiffusion instance from command-line arguments.
Creates an async context manager that yields an AsyncOmniDiffusion
instance configured from the provided arguments.
Args:
args: Parsed command-line arguments containing model and configuration
**kwargs: Additional keyword arguments passed to AsyncOmniDiffusion
Yields:
AsyncOmniDiffusion instance ready for use
"""
diffusion_engine : AsyncOmniDiffusion | None = None
try :
# Build diffusion kwargs by extracting matching OmniDiffusionConfig fields from args
config_field_names = { f . name for f in fields ( OmniDiffusionConfig )}
diffusion_kwargs : dict [ str , Any ] = { "model" : args . model }
2025-12-19 11:59:07 +08:00
# Diffusion parallelism configuration (e.g. `--usp 2`).
parallel_config_kwargs : dict [ str , Any ] = {}
for field in fields ( DiffusionParallelConfig ):
if not hasattr ( args , field . name ):
continue
value = getattr ( args , field . name )
if value is None :
continue
parallel_config_kwargs [ field . name ] = value
if parallel_config_kwargs :
diffusion_kwargs [ "parallel_config" ] = DiffusionParallelConfig ( ** parallel_config_kwargs )
2025-12-17 17:40:45 +08:00
for field_name in config_field_names :
if not hasattr ( args , field_name ):
continue
value = getattr ( args , field_name )
if value is None :
continue
# Special handling for cache_config JSON string
if field_name == "cache_config" and isinstance ( value , str ):
try :
value = json . loads ( value )
except json . JSONDecodeError as e :
logger . warning ( f "Failed to parse cache_config JSON: { e } " )
continue
diffusion_kwargs [ field_name ] = value
diffusion_kwargs . update ( kwargs )
logger . info ( f "diffusion_kwargs: { diffusion_kwargs } " )
logger . info (
"Building AsyncOmniDiffusion with model= %s , num_gpus= %s " ,
args . model ,
diffusion_kwargs . get ( "num_gpus" , 1 ),
)
diffusion_engine = AsyncOmniDiffusion ( ** diffusion_kwargs )
yield diffusion_engine
finally :
if diffusion_engine :
diffusion_engine . shutdown ()
2025-11-17 14:44:10 +08:00
@asynccontextmanager
2025-11-30 22:26:33 +08:00
async def build_async_omni_from_stage_config (
2025-11-17 14:44:10 +08:00
args : Namespace ,
* ,
disable_frontend_multiprocessing : bool = False ,
2025-12-16 15:19:40 +08:00
client_config : dict [ str , Any ] | None = None ,
2025-11-17 14:44:10 +08:00
) -> AsyncIterator [ EngineClient ]:
2025-11-30 22:26:33 +08:00
"""Create AsyncOmni from stage configuration.
2025-11-17 14:44:10 +08:00
2025-11-30 22:26:33 +08:00
Creates an AsyncOmni instance either in-process or using multiprocess
2025-11-28 11:03:52 +08:00
RPC. Loads stage configurations from the model or from a specified path.
Args:
args: Parsed command-line arguments containing model and stage configs
disable_frontend_multiprocessing: Flag to disable frontend multiprocessing
(deprecated in V1)
client_config: Optional client configuration dictionary
Yields:
2025-11-30 22:26:33 +08:00
EngineClient instance (AsyncOmni) ready for use
2025-11-28 11:03:52 +08:00
Note:
Stage configurations are loaded from args.stage_configs_path if provided,
otherwise from the model's default configuration.
2025-11-17 14:44:10 +08:00
"""
# V1 AsyncLLM.
if disable_frontend_multiprocessing :
2025-12-19 16:42:02 +08:00
logger . warning ( "V1 is enabled, but got --disable-frontend-multiprocessing." )
2025-11-17 14:44:10 +08:00
2025-12-16 15:19:40 +08:00
async_omni : EngineClient | None = None
2025-11-17 14:44:10 +08:00
try :
2025-12-08 14:59:53 +08:00
async_omni = AsyncOmni ( model = args . model , cli_args = args )
2025-11-17 14:44:10 +08:00
# # Don't keep the dummy data in memory
# await async_llm.reset_mm_cache()
2025-11-30 22:26:33 +08:00
yield async_omni
2025-11-17 14:44:10 +08:00
finally :
2025-11-30 22:26:33 +08:00
if async_omni :
async_omni . shutdown ()
2025-11-17 14:44:10 +08:00
async def omni_init_app_state (
engine_client : EngineClient ,
vllm_config : VllmConfig ,
state : State ,
args : Namespace ,
) -> None :
2025-11-28 11:03:52 +08:00
"""Initialize the FastAPI application state for omni API server.
Sets up the application state with model information, request logger,
and other server configuration needed for handling API requests.
Args:
2025-11-30 22:26:33 +08:00
engine_client: Engine client instance (AsyncOmni)
2025-11-28 11:03:52 +08:00
vllm_config: vLLM configuration object
state: FastAPI application state object to initialize
args: Parsed command-line arguments
"""
2025-11-17 14:44:10 +08:00
if args . served_model_name is not None :
served_model_names = args . served_model_name
else :
served_model_names = [ args . model ]
if args . enable_log_requests :
request_logger = RequestLogger ( max_log_len = args . max_log_len )
else :
request_logger = None
2025-11-18 20:54:26 +08:00
base_model_paths = [ BaseModelPath ( name = name , model_path = args . model ) for name in served_model_names ]
2025-11-17 14:44:10 +08:00
state . engine_client = engine_client
state . log_stats = not args . disable_log_stats
state . vllm_config = vllm_config
2025-12-19 16:42:02 +08:00
_model_config = vllm_config . model_config
2025-11-17 14:44:10 +08:00
state . log_stats = not args . disable_log_stats
# For omni models
state . stage_configs = engine_client . stage_configs
resolved_chat_template = load_chat_template ( args . chat_template )
if resolved_chat_template is not None :
# Get the tokenizer to check official template
tokenizer = await engine_client . get_tokenizer ()
if isinstance ( tokenizer , MistralTokenizer ):
# The warning is logged in resolve_mistral_chat_template.
2025-11-18 20:54:26 +08:00
resolved_chat_template = resolve_mistral_chat_template ( chat_template = resolved_chat_template )
2025-11-17 14:44:10 +08:00
else :
hf_chat_template = resolve_hf_chat_template (
tokenizer = tokenizer ,
chat_template = None ,
tools = None ,
model_config = vllm_config . model_config ,
)
if hf_chat_template != resolved_chat_template :
logger . warning (
2025-11-18 20:54:26 +08:00
"Using supplied chat template: %s \n It is different from official chat template ' %s '. This discrepancy may lead to performance degradation." , # noqa: E501
2025-11-17 14:44:10 +08:00
resolved_chat_template ,
args . model ,
)
if args . tool_server == "demo" :
2025-12-16 15:19:40 +08:00
tool_server : ToolServer | None = DemoToolServer ()
2025-11-17 14:44:10 +08:00
assert isinstance ( tool_server , DemoToolServer )
await tool_server . init_and_validate ()
elif args . tool_server :
tool_server = MCPToolServer ()
await tool_server . add_tool_server ( args . tool_server )
else :
tool_server = None
# Merge default_mm_loras into the static lora_modules
2025-11-18 20:54:26 +08:00
default_mm_loras = vllm_config . lora_config . default_mm_loras if vllm_config . lora_config is not None else {}
2025-11-17 14:44:10 +08:00
lora_modules = args . lora_modules
if default_mm_loras :
default_mm_lora_paths = [
LoRAModulePath (
name = modality ,
path = lora_path ,
)
for modality , lora_path in default_mm_loras . items ()
]
if args . lora_modules is None :
lora_modules = default_mm_lora_paths
else :
lora_modules += default_mm_lora_paths
state . openai_serving_models = OpenAIServingModels (
engine_client = engine_client ,
base_model_paths = base_model_paths ,
lora_modules = lora_modules ,
)
await state . openai_serving_models . init_static_loras ()
state . openai_serving_chat = OmniOpenAIServingChat (
engine_client ,
state . openai_serving_models ,
args . response_role ,
request_logger = request_logger ,
chat_template = resolved_chat_template ,
chat_template_content_format = args . chat_template_content_format ,
trust_request_chat_template = args . trust_request_chat_template ,
return_tokens_as_token_ids = args . return_tokens_as_token_ids ,
enable_auto_tools = args . enable_auto_tool_choice ,
exclude_tools_when_tool_choice_none = args . exclude_tools_when_tool_choice_none ,
tool_parser = args . tool_call_parser ,
reasoning_parser = args . structured_outputs_config . reasoning_parser ,
enable_prompt_tokens_details = args . enable_prompt_tokens_details ,
enable_force_include_usage = args . enable_force_include_usage ,
enable_log_outputs = args . enable_log_outputs ,
log_error_stack = args . log_error_stack ,
)
state . enable_server_load_tracking = args . enable_server_load_tracking
state . server_load_metrics = 0
2025-12-16 15:19:40 +08:00
def Omnichat ( request : Request ) -> OmniOpenAIServingChat | None :
2025-11-17 14:44:10 +08:00
return request . app . state . openai_serving_chat
2025-12-17 17:40:45 +08:00
async def omni_diffusion_init_app_state (
diffusion_engine : AsyncOmniDiffusion ,
state : State ,
args : Namespace ,
) -> None :
"""Initialize the FastAPI application state for diffusion model API server.
Sets up the application state with diffusion model information and
chat completion handler for image generation via /v1/chat/completions.
Args:
diffusion_engine: AsyncOmniDiffusion engine instance
state: FastAPI application state object to initialize
args: Parsed command-line arguments
"""
if args . served_model_name is not None :
served_model_names = args . served_model_name
else :
served_model_names = [ args . model ]
model_name = served_model_names [ 0 ] if served_model_names else args . model
state . diffusion_engine = diffusion_engine
2025-12-22 20:05:37 -05:00
state . diffusion_model_name = model_name # Store for image endpoints
2025-12-17 17:40:45 +08:00
state . log_stats = not getattr ( args , "disable_log_stats" , False )
# Initialize chat handler with diffusion engine (uses /v1/chat/completions endpoint)
# Note: Request-level parameters (num_inference_steps, guidance_scale, seed, height, width, etc.)
# are passed per-request via the API, not as server defaults
state . openai_serving_chat = OmniOpenAIServingChat . for_diffusion (
diffusion_engine = diffusion_engine ,
model_name = model_name ,
)
# Set other handlers to None for diffusion-only mode
state . engine_client = None
state . vllm_config = None
state . enable_server_load_tracking = getattr ( args , "enable_server_load_tracking" , False )
state . server_load_metrics = 0
logger . info ( "Diffusion API server initialized for model: %s " , model_name )
2025-11-17 14:44:10 +08:00
@router.post (
"/v1/chat/completions" ,
dependencies = [ Depends ( validate_json_request )],
responses = {
HTTPStatus . OK . value : { "content" : { "text/event-stream" : {}}},
HTTPStatus . BAD_REQUEST . value : { "model" : ErrorResponse },
HTTPStatus . NOT_FOUND . value : { "model" : ErrorResponse },
HTTPStatus . INTERNAL_SERVER_ERROR . value : { "model" : ErrorResponse },
},
)
@with_cancellation
@load_aware_call
async def create_chat_completion ( request : ChatCompletionRequest , raw_request : Request ):
handler = Omnichat ( raw_request )
if handler is None :
2025-11-18 20:54:26 +08:00
return base ( raw_request ) . create_error_response ( message = "The model does not support Chat Completions API" )
2025-11-17 14:44:10 +08:00
try :
generator = await handler . create_chat_completion ( request , raw_request )
except Exception as e :
2025-12-17 17:40:45 +08:00
logger . exception ( "Chat completion failed: %s " , e )
2025-11-18 20:54:26 +08:00
raise HTTPException ( status_code = HTTPStatus . INTERNAL_SERVER_ERROR . value , detail = str ( e )) from e
2025-12-17 17:40:45 +08:00
2025-11-17 14:44:10 +08:00
if isinstance ( generator , ErrorResponse ):
2025-12-17 17:40:45 +08:00
return JSONResponse (
content = generator . model_dump (), status_code = generator . code if hasattr ( generator , "code" ) else 400
)
2025-11-17 14:44:10 +08:00
elif isinstance ( generator , ChatCompletionResponse ):
return JSONResponse ( content = generator . model_dump ())
return StreamingResponse ( content = generator , media_type = "text/event-stream" )
2025-12-22 20:05:37 -05:00
# Image generation API endpoints
@router.post (
"/v1/images/generations" ,
dependencies = [ Depends ( validate_json_request )],
responses = {
HTTPStatus . OK . value : { "model" : ImageGenerationResponse },
HTTPStatus . BAD_REQUEST . value : { "model" : ErrorResponse },
HTTPStatus . SERVICE_UNAVAILABLE . value : { "model" : ErrorResponse },
HTTPStatus . INTERNAL_SERVER_ERROR . value : { "model" : ErrorResponse },
},
)
async def generate_images ( request : ImageGenerationRequest , raw_request : Request ) -> ImageGenerationResponse :
"""Generate images from text prompts using diffusion models.
OpenAI DALL-E compatible endpoint for text-to-image generation.
Args:
request: Image generation request with prompt and parameters
raw_request: Raw FastAPI request for accessing app state
Returns:
ImageGenerationResponse with generated images as base64 PNG
Raises:
HTTPException: For validation errors, missing engine, or generation failures
"""
# Get diffusion engine from app state
diffusion_engine : AsyncOmniDiffusion | None = getattr ( raw_request . app . state , "diffusion_engine" , None )
if diffusion_engine is None :
raise HTTPException (
status_code = HTTPStatus . SERVICE_UNAVAILABLE . value ,
detail = "Diffusion engine not initialized. Start server with a diffusion model." ,
)
# Get server's loaded model
model_name = getattr ( raw_request . app . state , "diffusion_model_name" , "unknown" )
# Validate model field (warn if mismatch, don't error)
if request . model is not None and request . model != model_name :
logger . warning (
f "Model mismatch: request specifies ' { request . model } ' but "
f "server is running ' { model_name } '. Using server model."
)
try :
# Build params - pass through user values directly
gen_params = {
"prompt" : request . prompt ,
"num_outputs_per_prompt" : request . n ,
}
# Parse and add size if provided
if request . size :
width , height = parse_size ( request . size )
gen_params [ "height" ] = height
gen_params [ "width" ] = width
size_str = f " { width } x { height } "
else :
size_str = "model default"
# Add optional parameters ONLY if provided
if request . num_inference_steps is not None :
gen_params [ "num_inference_steps" ] = request . num_inference_steps
if request . negative_prompt is not None :
gen_params [ "negative_prompt" ] = request . negative_prompt
if request . guidance_scale is not None :
gen_params [ "guidance_scale" ] = request . guidance_scale
if request . true_cfg_scale is not None :
gen_params [ "true_cfg_scale" ] = request . true_cfg_scale
if request . seed is not None :
gen_params [ "seed" ] = request . seed
logger . info ( f "Generating { request . n } image(s) { size_str } " )
# Generate images using AsyncOmniDiffusion
result = await diffusion_engine . generate ( ** gen_params )
# Extract images from result
images = result . images if hasattr ( result , "images" ) else []
logger . info ( f "Successfully generated { len ( images ) } image(s)" )
# Encode images to base64
image_data = [ ImageData ( b64_json = encode_image_base64 ( img ), revised_prompt = None ) for img in images ]
return ImageGenerationResponse (
created = int ( time . time ()),
data = image_data ,
)
except HTTPException :
# Re-raise HTTPExceptions as-is
raise
except ValueError as e :
logger . error ( f "Validation error: { e } " )
raise HTTPException ( status_code = HTTPStatus . BAD_REQUEST . value , detail = str ( e ))
except Exception as e :
logger . exception ( f "Image generation failed: { e } " )
raise HTTPException (
status_code = HTTPStatus . INTERNAL_SERVER_ERROR . value , detail = f "Image generation failed: { str ( e ) } "
)