[Rust Frontend] Coerce completion `max_tokens: null` to default (#47166)
Signed-off-by: Blas Rodriguez Irizar <rodrigblas@gmail.com>
Blas Rodriguez Irizar committed
Jul 1, 2026 at 07:41 UTC
5b431b905c7aac89c47fde879efedc6d67615faf
2 files changed
+34
-1
rust/src/server/src/routes/openai/completions/convert.rs
+23
@@ -199,6 +199,7 @@ mod tests {
199
use super::prepare_completion_request;
200
use crate::lora::LoraModelResolution;
201
use crate::routes::openai::completions::types::CompletionRequest;
202
+ use crate::routes::openai::utils::types::Normalizable;
203
use crate::utils::{ResolvedRequestContext, resolve_request_context};
204
205
fn request_context(headers: &HeaderMap, request_id: Option<&str>) -> ResolvedRequestContext {
@@ -249,6 +250,28 @@ mod tests {
250
assert!(request.ignore_eos);
251
}
252
253
+ #[test]
254
+ fn normalize_coerces_null_max_tokens_to_default() {
255
+ // An absent `max_tokens` already gets the serde default.
256
+ let absent: CompletionRequest =
257
+ serde_json::from_value(base_request_json()).expect("parse request");
258
+ assert_eq!(absent.max_tokens, Some(16));
259
+
260
+ // An explicit `null` deserializes to `None`, bypassing the default;
261
+ // `normalize` must coerce it back to match Python vLLM.
262
+ let mut request: CompletionRequest = serde_json::from_value(json!({
263
+ "model": "Qwen/Qwen1.5-0.5B-Chat",
264
+ "prompt": "hello",
265
+ "stream": true,
266
+ "max_tokens": null
267
+ }))
268
+ .expect("parse request");
269
+ assert_eq!(request.max_tokens, None);
270
+
271
+ request.normalize();
272
+ assert_eq!(request.max_tokens, Some(16));
273
+ }
274
+
275
#[test]
276
fn prepare_completion_request_maps_sampling_fields() {
277
let request: CompletionRequest = serde_json::from_value(json!({
rust/src/server/src/routes/openai/completions/types.rs
+11
-1
@@ -179,7 +179,17 @@ pub struct CompletionRequest {
179
pub other: Map<String, Value>,
180
}
181
182
-impl Normalizable for CompletionRequest {}
182
+impl Normalizable for CompletionRequest {
183
+ /// Normalize the request by applying defaults.
184
+ fn normalize(&mut self) {
185
+ // An explicit `"max_tokens": null` deserializes to `None`, bypassing the
186
+ // serde field default. Coerce it back to the default so it behaves like
187
+ // an absent field, matching Python vLLM's `normalize_null_max_tokens`.
188
+ if self.max_tokens.is_none() {
189
+ self.max_tokens = default_completion_max_tokens();
190
+ }
191
+ }
192
+}
193
194
/// Mirrors the Python vLLM `CompletionResponse` class.
195
#[serde_with::skip_serializing_none]