A high-throughput and memory-efficient inference and serving engine for LLMs
[Bugfix][Rust Frontend] Set a structured-output backend so requests do not 500 (#44729)
Signed-off-by: Ting Sun <suntcrick@gmail.com> Signed-off-by: Bugen Zhao <i@bugenzhao.com> Co-authored-by: Bugen Zhao <i@bugenzhao.com>
Ting SUN committed
Jun 9, 2026 at 13:30 UTC
ebf53ba373bf296d6c0fb8e88c02011397636ef0
1 file changed
+40
rust/src/engine-core-client/src/protocol/mod.rs
+40
@@ -162,6 +162,21 @@ pub enum RequestOutputKind {
162
FinalOnly = 2,
163
}
164
165
+/// Structured-output backend selected for EngineCore grammar compilation.
166
+///
167
+/// Python vLLM stores this in `StructuredOutputsParams._backend` after request
168
+/// validation. The Rust frontend currently always lowers structured-output
169
+/// requests to guidance, while ignoring any user-supplied `_backend` value.
170
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
171
+#[serde(rename_all = "kebab-case")]
172
+pub enum StructuredOutputBackend {
173
+ Xgrammar,
174
+ #[default]
175
+ Guidance,
176
+ Outlines,
177
+ LmFormatEnforcer,
178
+}
179
+
180
/// The stop reason associated with a finished output.
181
///
182
/// Python models this as the union-typed `stop_reason: int | str | None`
@@ -209,6 +224,17 @@ pub struct StructuredOutputsParams {
224
pub whitespace_pattern: Option<String>,
225
/// Structural tag configuration (JSON-encoded string).
226
pub structural_tag: Option<String>,
227
+ /// Structured-output backend, mirroring Python's internal `_backend`.
228
+ ///
229
+ /// User-supplied values are ignored during deserialization. This matches
230
+ /// Python's request boundary, where `_backend` is set by validation rather
231
+ /// than accepted as a request-level backend selector.
232
+ #[serde(
233
+ default,
234
+ rename = "_backend",
235
+ deserialize_with = "serde_with::rust::deserialize_ignore_any"
236
+ )]
237
+ pub backend: StructuredOutputBackend,
238
}
239
240
/// Engine-core-facing sampling parameters for text generation.
@@ -600,4 +626,18 @@ mod tests {
626
627
expect_test::expect![[r#"messagepack decode failed for u64: wrong msgpack marker FixMap(1); value fallback: {"status": "READY"}"#]].assert_eq(&error.to_report_string());
628
}
629
+
630
+ #[test]
631
+ fn structured_outputs_backend_ignores_deserialized_value() {
632
+ let params: StructuredOutputsParams = serde_json::from_value(serde_json::json!({
633
+ "json_object": true,
634
+ "_backend": "xgrammar",
635
+ }))
636
+ .unwrap();
637
+
638
+ assert_eq!(params.backend, StructuredOutputBackend::Guidance);
639
+
640
+ let value = serde_json::to_value(params).unwrap();
641
+ assert_eq!(value["_backend"], "guidance");
642
+ }
643
}