From daaeec3556334c7d754ead0f7567cf778d21f0f0 Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Wed, 24 Sep 2025 22:07:53 +0000 Subject: [PATCH 1/5] Add backward compatibility for `guided_...` API Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- docs/features/structured_outputs.md | 11 +++ vllm/entrypoints/openai/protocol.py | 122 ++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/docs/features/structured_outputs.md b/docs/features/structured_outputs.md index 1f955c6e30d6..901d87e7ed3d 100644 --- a/docs/features/structured_outputs.md +++ b/docs/features/structured_outputs.md @@ -6,6 +6,17 @@ vLLM supports the generation of structured outputs using This document shows you some examples of the different options that are available to generate structured outputs. +!!! warning + If you are still using the following deprecated API fields, please update your code to use `structured_outputs` as demonstrated in the rest of this document: + + - `guided_json` -> `{"structured_outputs": {"json": ...}}` or `StructuredOutputsParams(json=...)` + - `guided_regex` -> `{"structured_outputs": {"regex": ...}}` or `StructuredOutputsParams(regex=...)` + - `guided_choice` -> `{"structured_outputs": {"choice": ...}}` or `StructuredOutputsParams(choice=...)` + - `guided_grammar` -> `{"structured_outputs": {"grammar": ...}}` or `StructuredOutputsParams(grammar=...)` + - `guided_whitespace_pattern` -> `{"structured_outputs": {"whitespace_pattern": ...}}` or `StructuredOutputsParams(whitespace_pattern=...)` + - `structural_tag` -> `{"structured_outputs": {"structural_tag": ...}}` or `StructuredOutputsParams(structural_tag=...)` + - `guided_decoding_backend` -> Remove this field from your request + ## Online Serving (OpenAI API) You can generate structured outputs using the OpenAI's [Completions](https://platform.openai.com/docs/api-reference/completions) and [Chat](https://platform.openai.com/docs/api-reference/chat) API. diff --git a/vllm/entrypoints/openai/protocol.py b/vllm/entrypoints/openai/protocol.py index c30681318f69..87ec0b3d23cc 100644 --- a/vllm/entrypoints/openai/protocol.py +++ b/vllm/entrypoints/openai/protocol.py @@ -541,6 +541,56 @@ class ChatCompletionRequest(OpenAIBaseModel): default=None, description="Additional kwargs for structured outputs", ) + guided_json: Optional[Union[str, dict, BaseModel]] = Field( + default=None, + description=( + "`guided_json` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `json` to `structured_outputs` instead."), + ) + guided_regex: Optional[str] = Field( + default=None, + description=( + "`guided_regex` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `regex` to `structured_outputs` instead."), + ) + guided_choice: Optional[list[str]] = Field( + default=None, + description=( + "`guided_choice` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `choice` to `structured_outputs` instead."), + ) + guided_grammar: Optional[str] = Field( + default=None, + description=( + "`guided_grammar` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `grammar` to `structured_outputs` instead."), + ) + structural_tag: Optional[str] = Field( + default=None, + description=( + "`structural_tag` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `structural_tag` to `structured_outputs` instead."), + ) + guided_decoding_backend: Optional[str] = Field( + default=None, + description=( + "`guided_decoding_backend` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please remove it from your request."), + ) + guided_whitespace_pattern: Optional[str] = Field( + default=None, + description=( + "`guided_whitespace_pattern` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `whitespace_pattern` to `structured_outputs` instead." + ), + ) priority: int = Field( default=0, description=( @@ -658,6 +708,21 @@ def to_sampling_params( if prompt_logprobs is None and self.echo: prompt_logprobs = self.top_logprobs + # Forward deprecated guided_* parameters to structured_outputs + if self.structured_outputs is None: + kwargs = dict( + json=self.guided_json, + regex=self.guided_regex, + choice=self.guided_choice, + grammar=self.guided_grammar, + backend=self.guided_decoding_backend, + whitespace_pattern=self.guided_whitespace_pattern, + structural_tag=self.structural_tag, + ) + kwargs = {k: v for k, v in kwargs.items() if v is not None} + if len(kwargs) > 0: + self.structured_outputs = StructuredOutputsParams(**kwargs) + response_format = self.response_format json_schema_from_tool = self._get_json_schema_from_tool() if response_format is not None or json_schema_from_tool is not None: @@ -1016,6 +1081,49 @@ class CompletionRequest(OpenAIBaseModel): default=None, description="Additional kwargs for structured outputs", ) + guided_json: Optional[Union[str, dict, BaseModel]] = Field( + default=None, + description=( + "`guided_json` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `json` to `structured_outputs` instead."), + ) + guided_regex: Optional[str] = Field( + default=None, + description=( + "`guided_regex` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `regex` to `structured_outputs` instead."), + ) + guided_choice: Optional[list[str]] = Field( + default=None, + description=( + "`guided_choice` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `choice` to `structured_outputs` instead."), + ) + guided_grammar: Optional[str] = Field( + default=None, + description=( + "`guided_grammar` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `grammar` to `structured_outputs` instead."), + ) + guided_decoding_backend: Optional[str] = Field( + default=None, + description=( + "`guided_decoding_backend` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please remove it from your request."), + ) + guided_whitespace_pattern: Optional[str] = Field( + default=None, + description=( + "`guided_whitespace_pattern` is deprecated. " + "This will be removed in v0.12.0 or v1.0.0, whichever is soonest. " + "Please pass `whitespace_pattern` to `structured_outputs` instead." + ), + ) priority: int = Field( default=0, description=( @@ -1145,6 +1253,20 @@ def to_sampling_params( echo_without_generation = self.echo and self.max_tokens == 0 + # Forward deprecated guided_* parameters to structured_outputs + if self.structured_outputs is None: + kwargs = dict( + json=self.guided_json, + regex=self.guided_regex, + choice=self.guided_choice, + grammar=self.guided_grammar, + backend=self.guided_decoding_backend, + whitespace_pattern=self.guided_whitespace_pattern, + ) + kwargs = {k: v for k, v in kwargs.items() if v is not None} + if len(kwargs) > 0: + self.structured_outputs = StructuredOutputsParams(**kwargs) + if (self.structured_outputs is not None and self.response_format is not None and self.response_format.type == "json_object"): From a77bc51d19d9bf5edb9ac394e2a719d1fe14de23 Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Wed, 24 Sep 2025 22:26:40 +0000 Subject: [PATCH 2/5] Review comments Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- vllm/entrypoints/openai/protocol.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vllm/entrypoints/openai/protocol.py b/vllm/entrypoints/openai/protocol.py index 87ec0b3d23cc..b5f738e55a90 100644 --- a/vllm/entrypoints/openai/protocol.py +++ b/vllm/entrypoints/openai/protocol.py @@ -715,7 +715,6 @@ def to_sampling_params( regex=self.guided_regex, choice=self.guided_choice, grammar=self.guided_grammar, - backend=self.guided_decoding_backend, whitespace_pattern=self.guided_whitespace_pattern, structural_tag=self.structural_tag, ) @@ -1260,7 +1259,6 @@ def to_sampling_params( regex=self.guided_regex, choice=self.guided_choice, grammar=self.guided_grammar, - backend=self.guided_decoding_backend, whitespace_pattern=self.guided_whitespace_pattern, ) kwargs = {k: v for k, v in kwargs.items() if v is not None} From df996df164085adf41bc5b2fd73bcfd7b8f653df Mon Sep 17 00:00:00 2001 From: Cyrus Leung Date: Thu, 25 Sep 2025 11:20:37 +0800 Subject: [PATCH 3/5] Apply suggestions from code review Signed-off-by: Cyrus Leung --- vllm/entrypoints/openai/protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vllm/entrypoints/openai/protocol.py b/vllm/entrypoints/openai/protocol.py index b5f738e55a90..e15519c29c33 100644 --- a/vllm/entrypoints/openai/protocol.py +++ b/vllm/entrypoints/openai/protocol.py @@ -710,7 +710,7 @@ def to_sampling_params( # Forward deprecated guided_* parameters to structured_outputs if self.structured_outputs is None: - kwargs = dict( + kwargs = dict[str, Any]( json=self.guided_json, regex=self.guided_regex, choice=self.guided_choice, @@ -1254,7 +1254,7 @@ def to_sampling_params( # Forward deprecated guided_* parameters to structured_outputs if self.structured_outputs is None: - kwargs = dict( + kwargs = dictstr, Any]( json=self.guided_json, regex=self.guided_regex, choice=self.guided_choice, From 205a20b9d3bafba39e7eb5e84522b6ce1d816458 Mon Sep 17 00:00:00 2001 From: Cyrus Leung Date: Thu, 25 Sep 2025 11:21:10 +0800 Subject: [PATCH 4/5] Update vllm/entrypoints/openai/protocol.py Signed-off-by: Cyrus Leung --- vllm/entrypoints/openai/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vllm/entrypoints/openai/protocol.py b/vllm/entrypoints/openai/protocol.py index e15519c29c33..484825905766 100644 --- a/vllm/entrypoints/openai/protocol.py +++ b/vllm/entrypoints/openai/protocol.py @@ -1254,7 +1254,7 @@ def to_sampling_params( # Forward deprecated guided_* parameters to structured_outputs if self.structured_outputs is None: - kwargs = dictstr, Any]( + kwargs = dict[str, Any]( json=self.guided_json, regex=self.guided_regex, choice=self.guided_choice, From ba8ca3a24019cc566ae665bf2c8bdc7aef156efd Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+hmellor@users.noreply.github.com> Date: Thu, 25 Sep 2025 09:00:35 +0000 Subject: [PATCH 5/5] Fix `{"structured_outputs": null}` passed to API Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- vllm/entrypoints/openai/protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vllm/entrypoints/openai/protocol.py b/vllm/entrypoints/openai/protocol.py index 484825905766..8829fa4886f6 100644 --- a/vllm/entrypoints/openai/protocol.py +++ b/vllm/entrypoints/openai/protocol.py @@ -903,7 +903,7 @@ def check_structured_outputs_count(cls, data): if isinstance(data, ValueError): raise data - if "structured_outputs" not in data: + if data.get("structured_outputs", None) is None: return data structured_outputs_kwargs = data['structured_outputs'] @@ -1309,7 +1309,7 @@ def to_sampling_params( @model_validator(mode="before") @classmethod def check_structured_outputs_count(cls, data): - if "structured_outputs" not in data: + if data.get("structured_outputs", None) is None: return data structured_outputs_kwargs = data['structured_outputs']