fix json bug - #65077
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates _get_vllm_engine_config in vllm_engine.py to preprocess initialization arguments using _convert_config_dicts before initializing AsyncEngineArgs. The review feedback points out a potential fragility in the _convert_config_dicts helper function when handling Union type hints, as it might resolve to a dict instead of a dataclass, and suggests a more robust implementation to prevent future regressions.
| async_engine_args = vllm.engine.arg_utils.AsyncEngineArgs( | ||
| **engine_config.get_initialization_kwargs() | ||
| ) | ||
| init_kwargs = _convert_config_dicts(engine_config.get_initialization_kwargs()) |
There was a problem hiding this comment.
While calling _convert_config_dicts here correctly resolves the initialization crash, the implementation of _convert_config_dicts (on lines 140-143) has a potential fragility when handling Union type hints.
Currently, it resolves Union types by picking the first non-None type:
hint = next((a for a in args if a is not type(None)), hint)If a type hint is defined as Union[dict, StructuredOutputsConfig] or Union[None, dict, StructuredOutputsConfig], this logic will pick dict instead of the dataclass, causing the conversion to be skipped and leading to the same AttributeError crash.
To make this more robust, we should search the Union arguments specifically for a dataclass type:
if origin is Union:
args = typing.get_args(hint)
hint = next((a for a in args if dataclasses.is_dataclass(a)), hint)Since _convert_config_dicts is defined outside the current diff hunk, this cannot be applied via an automated code suggestion, but it is highly recommended to update its definition to prevent future regressions if vLLM's type annotations change.
What happened + What you expected to happen
Deploying a Ray Serve LLM (
ray.serve.llm.build_openai_app) with a nesteddataclass field in
engine_kwargs— e.g.structured_outputs_config— crashesthe vLLM engine on startup with:
or, depending on which field is accessed first:
Expected: the engine starts successfully and
structured_outputs_configisapplied to the model, exactly as it would with vLLM's own CLI
(
--structured-outputs-config.enable_in_reasoning=True).Why this matters
This blocks the documented upstream vLLM fix for broken structured outputs on
Qwen3 Coder + reasoning models
(vllm-project/vllm#18819),
which requires setting
structured_outputs_config.enable_in_reasoning=True.Ray Serve LLM users deploying via YAML (
serveConfigV2/LLMConfig.engine_kwargs)have no working way to set this flag — the only way to express a nested vLLM
config field from YAML is as a plain dict, and that dict reaches
AsyncEngineArgsunconverted.Root cause
_get_vllm_engine_config()inpython/ray/llm/_internal/serve/engines/vllm/vllm_engine.pybuilds the engineconfig like this:
engine_config.get_initialization_kwargs()returns a plaindictfor everyengine_kwargsentry, including nested ones likestructured_outputs_config.vllm.engine.arg_utils.EngineArgs.__post_init__only coerces a fixed allowlistof fields from dict to their dataclass type (
compilation_config,attention_config,mamba_config,kernel_config,eplb_config,weight_transfer_config,ir_op_priority).structured_outputs_configandreasoning_configare not in that list, so they stay as plaindictobjects on the
AsyncEngineArgsinstance.create_engine_config()then does (vLLMengine/arg_utils.py):— which crashes immediately with
AttributeError: 'dict' object has no attribute 'reasoning_parser'whenever a top-levelengine_kwargs.reasoning_parseris also set (a very common config for Qwen3 reasoning models). Even without
that top-level field,
VllmConfig.compute_hash()later callsself.structured_outputs_config.compute_hash(), which crashes the same way.There already is a fix for a similar symptom — but it doesn't cover this path
#60380 (
[LLM] Fix nested dict to Namespace conversion in vLLM engine initialization, merged) introduced a helper,_convert_config_dicts(), thatconverts known
AsyncEngineArgsdict fields into their proper dataclassinstances using field type hints. It is called in
VLLMEngine.start():This fixes
AttributeErrorin the serving/frontend layer(
build_asgi_app()/init_app_state(), which readsargs.structured_outputs_config.backendetc. to configure the OpenAI-compatibleendpoint). But
self._vllm_argsis only built after the engine itself hasalready been started (
self._start_async_llm_engine(vllm_engine_args, ...)),using the args that came out of
_get_vllm_engine_config(). That functionbuilds
AsyncEngineArgsand callscreate_engine_config()before_convert_config_dicts()ever runs — so the crash described above stillreproduces on top of #60380.
Reproduction
Or via a RayService
serveConfigV2:The vLLM engine actor crashes on startup (
CrashLoopBackOffunder KubeRay).Related
enable_thinking=Falsevllm-project/vllm#18819 — the upstream fix this blocks (enable_in_reasoning)