[Rust Frontend] Split engine core DTOs into separate modules (#47265)
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Bugen Zhao committed
Jul 1, 2026 at 22:28 UTC
c638f9216a08bfb5644d8a266ddd35421e04118d
54 files changed
+806
-789
rust/src/chat/src/lib.rs
+2
-1
@@ -50,7 +50,8 @@ mod request;
50
mod stream;
51
52
use vllm_engine_core_client::EngineCoreClient;
53
-use vllm_engine_core_client::protocol::{ModelDtype, ReasoningParserKwargs};
53
+use vllm_engine_core_client::protocol::dtype::ModelDtype;
54
+use vllm_engine_core_client::protocol::request::ReasoningParserKwargs;
55
use vllm_llm::Llm;
56
use vllm_text::{Prompt, TextLlm, TextRequest};
57
rust/src/chat/src/multimodal.rs
+1
-1
@@ -22,7 +22,7 @@ use llm_multimodal::{
22
TrackedMedia,
23
};
24
use tracing::warn;
25
-use vllm_engine_core_client::protocol::ModelDtype;
25
+use vllm_engine_core_client::protocol::dtype::ModelDtype;
26
use vllm_engine_core_client::protocol::multimodal::{
27
MmBatchedField, MmFeatureSpec, MmFeatures, MmField, MmFieldElem, MmFlatField, MmKwargsItem,
28
MmSharedField, MmSlice, PlaceholderRange, SliceSpec,
rust/src/chat/src/multimodal/tensor.rs
+1
-1
@@ -2,7 +2,7 @@ use std::collections::HashMap;
2
3
use half::{bf16, f16};
4
use llm_multimodal::{ModelSpecificValue, PreprocessedImages};
5
-use vllm_engine_core_client::protocol::ModelDtype;
5
+use vllm_engine_core_client::protocol::dtype::ModelDtype;
6
use vllm_engine_core_client::protocol::multimodal::MmKwargValue as ProtocolKwargValue;
7
use vllm_engine_core_client::protocol::tensor::{ShapeExt as _, WireTensor};
8
rust/src/chat/src/output/default/structural_tag.rs
+6
-2
@@ -1,7 +1,9 @@
1
//! Applies xgrammar structural-tag constraints for strict tool calling.
2
3
use thiserror_ext::AsReport;
4
-use vllm_engine_core_client::protocol::{StructuredOutputBackend, StructuredOutputsParams};
4
+use vllm_engine_core_client::protocol::structured_outputs::{
5
+ StructuredOutputBackend, StructuredOutputsParams,
6
+};
7
use vllm_parser::tool::StructuralTagModel;
8
use xgrammar_structural_tag::{
9
FunctionDefinition, FunctionToolParam, ToolChoice as StructuralTagToolChoice, ToolParam,
@@ -76,7 +78,9 @@ fn structural_tag_tool_choice(request: &ChatRequest) -> Option<StructuralTagTool
78
#[cfg(test)]
79
mod tests {
80
use serde_json::{Value, json};
79
- use vllm_engine_core_client::protocol::{StructuredOutputBackend, StructuredOutputsParams};
81
+ use vllm_engine_core_client::protocol::structured_outputs::{
82
+ StructuredOutputBackend, StructuredOutputsParams,
83
+ };
84
use vllm_parser::tool::{Qwen3CoderToolParser, Tool, ToolParser};
85
86
use super::*;
rust/src/chat/tests/chat.rs
+3
-2
@@ -15,9 +15,10 @@ use vllm_chat::{
15
use vllm_engine_core_client::protocol::logprobs::{
16
Logprobs, MaybeWireLogprobs, PositionLogprobs, TokenLogprob,
17
};
18
-use vllm_engine_core_client::protocol::{
19
- EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, EngineCoreRequest, StopReason,
18
+use vllm_engine_core_client::protocol::output::{
19
+ EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, StopReason,
20
};
21
+use vllm_engine_core_client::protocol::request::EngineCoreRequest;
22
use vllm_engine_core_client::test_utils::{IpcNamespace, spawn_mock_engine_task};
23
use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig};
24
use vllm_llm::Llm;
rust/src/engine-core-client/examples/external_engine_logprobs.rs
+3
-3
@@ -5,9 +5,9 @@ use clap::Parser;
5
use futures::StreamExt as _;
6
use tokio::time::timeout;
7
use tracing_subscriber::EnvFilter;
8
-use vllm_engine_core_client::protocol::{
9
- EngineCoreFinishReason, EngineCoreRequest, EngineCoreSamplingParams,
10
-};
8
+use vllm_engine_core_client::protocol::output::EngineCoreFinishReason;
9
+use vllm_engine_core_client::protocol::request::EngineCoreRequest;
10
+use vllm_engine_core_client::protocol::sampling::EngineCoreSamplingParams;
11
use vllm_engine_core_client::{
12
EngineCoreClient, EngineCoreClientConfig, EngineCoreStreamOutput, TransportMode,
13
};
rust/src/engine-core-client/src/client.rs
+2
-1
@@ -11,10 +11,11 @@ use tracing::{debug, info, trace};
11
use crate::client::imp::{ClientInner, run_abort_loop, run_output_dispatcher_loop};
12
use crate::coordinator::CoordinatorHandle;
13
use crate::error::{Error, Result};
14
+use crate::protocol::dtype::ModelDtype;
15
use crate::protocol::handshake::EngineCoreReadyResponse;
16
use crate::protocol::lora::LoraRequest;
17
+use crate::protocol::request::{EngineCoreRequest, EngineCoreRequestType};
18
use crate::protocol::utility::{EngineCoreUtilityRequest, PauseMode};
17
-use crate::protocol::{EngineCoreRequest, EngineCoreRequestType, ModelDtype};
19
use crate::runtime::{BackgroundShutdownRuntime, build_zmq_runtime};
20
use crate::transport::{self, ConnectedEngine};
21
rust/src/engine-core-client/src/client/imp.rs
+3
-4
@@ -16,12 +16,11 @@ use crate::client::stream::EngineCoreStreamOutput;
16
use crate::client::{AbortCause, AbortRequest};
17
use crate::error::{client_closed, dispatcher_closed, unexpected_dispatcher_output};
18
use crate::metrics::{LoraInfoExporter, record_scheduler_stats};
19
+use crate::protocol::encode_msgpack;
20
+use crate::protocol::output::{ClassifiedEngineCoreOutputs, EngineCoreOutput, EngineCoreOutputs};
21
+use crate::protocol::request::EngineCoreRequestType;
22
use crate::protocol::stats::SchedulerStats;
23
use crate::protocol::utility::UtilityOutput;
21
-use crate::protocol::{
22
- ClassifiedEngineCoreOutputs, EngineCoreOutput, EngineCoreOutputs, EngineCoreRequestType,
23
- encode_msgpack,
24
-};
24
use crate::transport::{ConnectedEngine, EngineId};
25
use crate::{Error, Result, transport};
26
rust/src/engine-core-client/src/client/state.rs
+2
-2
@@ -7,9 +7,9 @@ use tracing::trace;
7
use crate::EngineId;
8
use crate::client::stream::EngineCoreStreamOutput;
9
use crate::error::{Error, Result};
10
+use crate::protocol::output::{EngineCoreEventType, EngineCoreFinishReason, EngineCoreOutput};
11
use crate::protocol::stats::SchedulerStats;
12
use crate::protocol::utility::UtilityOutput;
12
-use crate::protocol::{EngineCoreEventType, EngineCoreFinishReason, EngineCoreOutput};
13
use crate::transport::ConnectedEngine;
14
15
pub type OutputSender = mpsc::UnboundedSender<Result<EngineCoreStreamOutput>>;
@@ -452,7 +452,7 @@ mod tests {
452
EngineLoadSnapshot, EngineRoutingState, RequestRegistry, UtilityRegistry,
453
};
454
use crate::mock_engine::default_ready_response;
455
- use crate::protocol::{
455
+ use crate::protocol::output::{
456
EngineCoreEvent, EngineCoreEventType, EngineCoreFinishReason, EngineCoreOutput,
457
};
458
use crate::transport::ConnectedEngine;
rust/src/engine-core-client/src/client/stream.rs
+1
-1
@@ -10,7 +10,7 @@ use tracing::{debug, error, warn};
10
11
use crate::client::AbortRequest;
12
use crate::client::state::OutputReceiver;
13
-use crate::protocol::{EngineCoreFinishReason, EngineCoreOutput};
13
+use crate::protocol::output::{EngineCoreFinishReason, EngineCoreOutput};
14
use crate::{AbortCause, Error, Result};
15
16
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
rust/src/engine-core-client/src/coordinator/inproc.rs
+3
-4
@@ -10,10 +10,9 @@ use zeromq::{XPubSocket, ZmqMessage};
10
use crate::client::imp::ClientInner;
11
use crate::coordinator::handle::{CoordinatorCommand, CoordinatorState};
12
use crate::error::{Error, Result, bail_unexpected_coordinator_output};
13
-use crate::protocol::{
14
- ClassifiedEngineCoreOutputs, DpControlMessage, EngineCoreOutputs, EngineCoreRequestType,
15
- encode_msgpack,
16
-};
13
+use crate::protocol::encode_msgpack;
14
+use crate::protocol::output::{ClassifiedEngineCoreOutputs, DpControlMessage, EngineCoreOutputs};
15
+use crate::protocol::request::EngineCoreRequestType;
16
17
/// Coordinator-to-engine `START_DP_WAVE` control payload encoded on the
18
/// engine-facing coordinator socket.
rust/src/engine-core-client/src/mock_engine.rs
+2
-1
@@ -8,8 +8,9 @@ use zeromq::{DealerSocket, PushSocket, SocketOptions, SubSocket, ZmqMessage};
8
9
use crate::EngineId;
10
use crate::error::{Error, Result, bail_unexpected_handshake_message};
11
+use crate::protocol::dtype::ModelDtype;
12
use crate::protocol::handshake::{EngineCoreReadyResponse, HandshakeInitMessage, ReadyMessage};
12
-use crate::protocol::{ModelDtype, decode_msgpack, encode_msgpack};
13
+use crate::protocol::{decode_msgpack, encode_msgpack};
14
15
/// Default model length advertised by reusable mock engine helpers.
16
pub const DEFAULT_MOCK_MAX_MODEL_LEN: u64 = 1024 * 1024;
rust/src/engine-core-client/src/protocol/handshake.rs
+2
-1
@@ -2,7 +2,8 @@ use std::collections::BTreeMap;
2
3
use serde::{Deserialize, Serialize};
4
5
-use crate::protocol::{ModelDtype, OpaqueValue};
5
+use crate::protocol::OpaqueValue;
6
+use crate::protocol::dtype::ModelDtype;
7
8
/// Decoded engine startup-handshake payload sent on the handshake socket.
9
///
rust/src/engine-core-client/src/protocol/logprobs.rs
+2
-47
@@ -9,8 +9,7 @@ use enum_as_inner::EnumAsInner;
9
use serde::{Deserialize, Deserializer, Serialize};
10
11
use self::wire::*;
12
-use super::{EngineCoreOutput, EngineCoreOutputs, decode_msgpack};
13
-use crate::error::{Error, Result, bail_ext_value_decode, ext_value_decode};
12
+use crate::error::{Error, Result, bail_ext_value_decode};
13
use crate::protocol::tensor::{WireArrayData, WireNdArray};
14
15
/// One token candidate and its logprob metadata for a single sequence position.
@@ -160,7 +159,7 @@ impl Serialize for MaybeWireLogprobs {
159
impl MaybeWireLogprobs {
160
/// Resolve the wire representation into decoded logprobs by looking up aux
161
/// frames and decoding raw views as needed.
163
- fn resolve<Frame>(self, frames: &[Frame], field_prefix: &str) -> Result<Self>
162
+ pub(super) fn resolve<Frame>(self, frames: &[Frame], field_prefix: &str) -> Result<Self>
163
where
164
Frame: AsRef<[u8]>,
165
{
@@ -171,37 +170,6 @@ impl MaybeWireLogprobs {
170
}
171
}
172
174
-impl EngineCoreOutputs {
175
- /// Resolve all wire-format fields in-place by looking up aux frames and
176
- /// decoding raw-view payloads as needed.
177
- fn resolve_in_place<Frame>(&mut self, frames: &[Frame]) -> Result<()>
178
- where
179
- Frame: AsRef<[u8]>,
180
- {
181
- for output in &mut self.outputs {
182
- output.resolve_in_place(frames)?;
183
- }
184
- Ok(())
185
- }
186
-}
187
-
188
-impl EngineCoreOutput {
189
- /// Resolve all wire-format fields in-place by looking up aux frames and
190
- /// decoding raw-view payloads as needed.
191
- fn resolve_in_place<Frame>(&mut self, frames: &[Frame]) -> Result<()>
192
- where
193
- Frame: AsRef<[u8]>,
194
- {
195
- self.new_logprobs = (self.new_logprobs.take())
196
- .map(|value| value.resolve(frames, "new_logprobs"))
197
- .transpose()?;
198
- self.new_prompt_logprobs_tensors = (self.new_prompt_logprobs_tensors.take())
199
- .map(|value| value.resolve(frames, "new_prompt_logprobs_tensors"))
200
- .transpose()?;
201
- Ok(())
202
- }
203
-}
204
-
173
impl WireLogprobs {
174
/// Convert semantic per-position logprobs into the Python wire tuple shape.
175
///
@@ -315,16 +283,3 @@ impl WireLogprobs {
283
Ok(Logprobs { positions })
284
}
285
}
318
-
319
-/// Decode one ordinary or multipart engine-core output message into the strong
320
-/// typed public protocol shape.
321
-pub fn decode_engine_core_outputs<Frame>(frames: &[Frame]) -> Result<EngineCoreOutputs>
322
-where
323
- Frame: AsRef<[u8]>,
324
-{
325
- let first_frame = frames.first().ok_or_else(|| ext_value_decode!("missing output frame"))?;
326
-
327
- let mut outputs: EngineCoreOutputs = decode_msgpack(first_frame.as_ref())?;
328
- outputs.resolve_in_place(frames)?;
329
- Ok(outputs)
330
-}
rust/src/engine-core-client/src/protocol/logprobs/tests.rs
+2
-2
@@ -3,8 +3,8 @@ use std::collections::BTreeSet;
3
use bytes::Bytes;
4
use rmpv::Value;
5
6
-use super::{Logprobs, PositionLogprobs, TokenLogprob, decode_engine_core_outputs};
7
-use crate::protocol::EngineCoreFinishReason;
6
+use super::{Logprobs, PositionLogprobs, TokenLogprob};
7
+use crate::protocol::output::{EngineCoreFinishReason, decode_engine_core_outputs};
8
9
fn encode_value(value: &Value) -> Vec<u8> {
10
let mut out = Vec::new();
rust/src/engine-core-client/src/protocol/mod.rs
+6
-636
@@ -1,28 +1,11 @@
1
use std::any::type_name;
2
-use std::collections::{BTreeMap, BTreeSet, HashMap};
2
use std::io::Cursor;
3
5
-use bytes::Bytes;
4
use rmpv::Value;
5
use serde::{Deserialize, Serialize};
8
-use serde_default::DefaultFromSerde;
9
-use serde_repr::{Deserialize_repr, Serialize_repr};
10
-use serde_tuple::{Deserialize_tuple, Serialize_tuple};
6
use thiserror_ext::AsReport;
7
8
use crate::error::{Error, Result};
14
-use crate::protocol::logprobs::MaybeWireLogprobs;
15
-use crate::protocol::multimodal::MmFeatures;
16
-use crate::protocol::stats::{PrefillStats, SchedulerStats};
17
-use crate::protocol::utility::UtilityOutput;
18
-
19
-// TODO: This module currently mixes reusable frontend-facing semantic types
20
-// (for example `FinishReason`, `StopReason`, `RequestOutputKind`, and future
21
-// cleaned-up frontend sampling types) with engine-core-specific wire DTOs and
22
-// handshake/control messages. While the Rust frontend is still evolving
23
-// quickly, keep them co-located here for iteration speed. Once the higher-level
24
-// API boundary stabilizes, move the truly reusable semantic types into a
25
-// lower-level common crate and keep the engine transport/wire messages here.
9
10
/// Dynamic msgpack value used for schema positions that are preserved but not
11
/// yet strongly typed in the early-stage Rust client.
@@ -36,499 +19,18 @@ fn is_false(v: &bool) -> bool {
19
!v
20
}
21
39
-fn default_top_p() -> f32 {
40
- 1.0
41
-}
42
-
43
-fn default_repetition_penalty() -> f32 {
44
- 1.0
45
-}
46
-
47
-fn default_temperature() -> f32 {
48
- 1.0
49
-}
50
-
51
-fn default_max_tokens() -> u32 {
52
- 16
53
-}
54
-
55
-mod classified_outputs;
22
pub mod dtype;
23
pub mod handshake;
24
pub mod logprobs;
25
pub mod lora;
26
pub mod multimodal;
27
+pub mod output;
28
+pub mod request;
29
+pub mod sampling;
30
pub mod stats;
31
+pub mod structured_outputs;
32
pub mod tensor;
33
pub mod utility;
64
-pub use classified_outputs::{
65
- ClassifiedEngineCoreOutputs, DpControlMessage, RequestBatchOutputs, UtilityCallOutput,
66
-};
67
-pub use dtype::ModelDtype;
68
-pub use logprobs::decode_engine_core_outputs;
69
-
70
-/// Request types are encoded as single-byte protocol constants so they can be
71
-/// sent over the ZMQ socket without an extra encoding step.
72
-///
73
-/// Original Python definition:
74
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L217-L228>
75
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76
-#[repr(u8)]
77
-pub enum EngineCoreRequestType {
78
- Add = 0,
79
- Abort = 1,
80
- StartDpWave = 2,
81
- Utility = 3,
82
-}
83
-
84
-impl EngineCoreRequestType {
85
- /// Decode the single-byte request type frame used on the engine input
86
- /// socket. Returns `None` for unrecognized values.
87
- pub fn from_frame(frame: &[u8]) -> Option<Self> {
88
- let [value] = frame else {
89
- return None;
90
- };
91
-
92
- match value {
93
- 0 => Some(Self::Add),
94
- 1 => Some(Self::Abort),
95
- 2 => Some(Self::StartDpWave),
96
- 3 => Some(Self::Utility),
97
- _ => None,
98
- }
99
- }
100
-
101
- /// Encode the request type as the single-byte frame used on the engine
102
- /// input socket.
103
- pub fn to_frame(self) -> Bytes {
104
- Bytes::from_static(match self {
105
- Self::Add => b"\x00",
106
- Self::Abort => b"\x01",
107
- Self::StartDpWave => b"\x02",
108
- Self::Utility => b"\x03",
109
- })
110
- }
111
-}
112
-
113
-/// Reason a request finished: stop, length, abort, error, or repetition.
114
-///
115
-/// This mirrors the Python enum and uses integer encoding for compact wire
116
-/// representation.
117
-///
118
-/// Original Python definition:
119
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L41-L63>
120
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
121
-#[repr(u8)]
122
-pub enum EngineCoreFinishReason {
123
- /// A stop string was emitted.
124
- Stop = 0,
125
- /// `max_tokens` or `max_model_len` was reached.
126
- Length = 1,
127
- /// The request was aborted by the client.
128
- Abort = 2,
129
- /// A retryable request-level internal error occurred.
130
- Error = 3,
131
- /// A repetitive token pattern was detected.
132
- Repetition = 4,
133
-}
134
-
135
-/// Event types emitted by engine-core for one request.
136
-///
137
-/// Original Python definition:
138
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L113-L118>
139
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
140
-#[repr(u8)]
141
-pub enum EngineCoreEventType {
142
- Queued = 1,
143
- Scheduled = 2,
144
- Preempted = 3,
145
-}
146
-
147
-/// A timestamped engine-core event associated with one request.
148
-///
149
-/// Original Python definition:
150
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L121-L130>
151
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
152
-pub struct EngineCoreEvent {
153
- pub r#type: EngineCoreEventType,
154
- pub timestamp: f64,
155
-}
156
-
157
-/// Controls how intermediate outputs are returned to the frontend.
158
-///
159
-/// `Cumulative = 0` is intentionally not supported in Rust frontend.
160
-///
161
-/// Original Python definition:
162
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/sampling_params.py#L146-L152>
163
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize_repr, Deserialize_repr)]
164
-#[repr(u8)]
165
-pub enum RequestOutputKind {
166
- /// Return only token deltas in each update.
167
- #[default]
168
- Delta = 1,
169
- /// Suppress intermediate updates and return only the final output.
170
- FinalOnly = 2,
171
-}
172
-
173
-/// Structured-output backend selected for EngineCore grammar compilation.
174
-///
175
-/// Python vLLM stores this in `StructuredOutputsParams._backend` after request
176
-/// validation. The Rust frontend currently always lowers structured-output
177
-/// requests to guidance, while ignoring any user-supplied `_backend` value.
178
-#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
179
-#[serde(rename_all = "kebab-case")]
180
-pub enum StructuredOutputBackend {
181
- Xgrammar,
182
- #[default]
183
- Guidance,
184
- Outlines,
185
- LmFormatEnforcer,
186
-}
187
-
188
-/// The stop reason associated with a finished output.
189
-///
190
-/// Python models this as the union-typed `stop_reason: int | str | None`
191
-/// field on `EngineCoreOutput`; the Rust client narrows it into a tagged enum.
192
-///
193
-/// Original Python field:
194
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L155>
195
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
196
-#[serde(untagged)]
197
-pub enum StopReason {
198
- TokenId(u32),
199
- Text(String),
200
-}
201
-
202
-/// Parameters for configuring structured outputs (guided decoding).
203
-///
204
-/// Exactly one constraint field (`json`, `regex`, `choice`, `grammar`,
205
-/// `json_object`, or `structural_tag`) should be set. The engine-core
206
-/// backend selects the appropriate grammar compiler based on which field
207
-/// is present.
208
-///
209
-/// Original Python definition:
210
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/sampling_params.py#L36-L107>
211
-#[serde_with::skip_serializing_none]
212
-#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
213
-#[serde(default)]
214
-pub struct StructuredOutputsParams {
215
- /// JSON schema (as a dict/object or JSON string) constraining the output.
216
- pub json: Option<serde_json::Value>,
217
- /// Regular expression the output must match.
218
- pub regex: Option<String>,
219
- /// List of allowed output strings (the model must produce one of these).
220
- pub choice: Option<Vec<String>>,
221
- /// Context-free grammar (in EBNF-like notation) the output must conform to.
222
- pub grammar: Option<String>,
223
- /// When `true`, output must be valid JSON (free-form, no schema).
224
- pub json_object: Option<bool>,
225
- /// Disable any additional whitespace in guided JSON output.
226
- #[serde(skip_serializing_if = "crate::protocol::is_false")]
227
- pub disable_any_whitespace: bool,
228
- /// Disable `additionalProperties` in JSON schema output.
229
- #[serde(skip_serializing_if = "crate::protocol::is_false")]
230
- pub disable_additional_properties: bool,
231
- /// Custom whitespace pattern for guided JSON output.
232
- pub whitespace_pattern: Option<String>,
233
- /// Structural tag configuration (JSON-encoded string).
234
- pub structural_tag: Option<String>,
235
- /// Structured-output backend, mirroring Python's internal `_backend`.
236
- ///
237
- /// User-supplied values are ignored during deserialization. This matches
238
- /// Python's request boundary, where `_backend` is set by validation rather
239
- /// than accepted as a request-level backend selector.
240
- #[serde(
241
- default,
242
- rename = "_backend",
243
- deserialize_with = "serde_with::rust::deserialize_ignore_any"
244
- )]
245
- pub backend: StructuredOutputBackend,
246
-}
247
-
248
-/// Engine-core-facing sampling parameters for text generation.
249
-///
250
-/// This is the normalized southbound subset used by the Rust frontend when it
251
-/// talks to Python engine-core over the wire. User-facing request semantics
252
-/// such as `stop` strings, `n`, `ignore_eos`, and output aggregation mode are
253
-/// intentionally handled by higher layers before values reach this DTO.
254
-///
255
-/// Original Python definition:
256
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/sampling_params.py#L155-L291>
257
-// Python's SamplingParams is `omit_defaults=True`, so msgpack drops
258
-// default-valued keys; default the whole struct. Per-field fns cover the
259
-// non-zero defaults.
260
-#[serde_with::skip_serializing_none]
261
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, DefaultFromSerde)]
262
-#[serde(default)]
263
-pub struct EngineCoreSamplingParams {
264
- /// Controls randomness. Lower values are more deterministic; zero means
265
- /// greedy sampling.
266
- #[serde(default = "default_temperature")]
267
- pub temperature: f32,
268
- /// Cumulative probability threshold for nucleus sampling.
269
- #[serde(default = "default_top_p")]
270
- pub top_p: f32,
271
- /// Maximum number of top tokens to consider. `0` means all tokens.
272
- pub top_k: u32,
273
- /// Random seed used by the sampler when present.
274
- pub seed: Option<i64>,
275
- /// Maximum number of tokens to generate per output sequence.
276
- #[serde(default = "default_max_tokens")]
277
- pub max_tokens: u32,
278
- /// Minimum number of tokens to generate before EOS or stop-token handling.
279
- pub min_tokens: u32,
280
- /// Maximum number of reasoning ("thinking") tokens to emit before the
281
- /// reasoning section is force-closed. `None` means unlimited; the
282
- /// user-facing `-1` sentinel is normalized to `None` by the frontend before
283
- /// reaching this DTO, so only non-negative values are sent. Enforced
284
- /// engine-side (and only when a reasoning parser is configured).
285
- pub thinking_token_budget: Option<u64>,
286
- /// Number of log probabilities to return per generated token.
287
- ///
288
- /// `None` disables sample logprobs. `-1` requests the full vocabulary.
289
- pub logprobs: Option<i32>,
290
- /// Number of log probabilities to return per prompt token.
291
- ///
292
- /// `None` disables prompt logprobs. `-1` requests the full vocabulary.
293
- pub prompt_logprobs: Option<i32>,
294
- /// Minimum probability threshold for token sampling.
295
- pub min_p: f32,
296
- /// Frequency penalty applied by the sampler.
297
- pub frequency_penalty: f32,
298
- /// Presence penalty applied by the sampler.
299
- pub presence_penalty: f32,
300
- /// Repetition penalty applied by the sampler.
301
- #[serde(default = "default_repetition_penalty")]
302
- pub repetition_penalty: f32,
303
- /// Token IDs that stop generation.
304
- pub stop_token_ids: Vec<u32>,
305
- /// Primary EOS token ID used by engine-core's dedicated EOS stop path.
306
- ///
307
- /// This mirrors Python's internal `_eos_token_id` field and is derived by
308
- /// the frontend from tokenizer/model metadata rather than supplied directly
309
- /// by end users.
310
- #[serde(rename = "_eos_token_id")]
311
- pub eos_token_id: Option<u32>,
312
- /// Complete stop-token set used by engine-core for `min_tokens` masking.
313
- ///
314
- /// This mirrors Python's internal `_all_stop_token_ids` field and should
315
- /// contain explicit `stop_token_ids` plus any frontend-derived EOS token
316
- /// IDs.
317
- #[serde(rename = "_all_stop_token_ids")]
318
- pub all_stop_token_ids: BTreeSet<u32>,
319
- /// Logit biases to apply during sampling.
320
- /// Keys are token IDs
321
- pub logit_bias: Option<HashMap<u32, f32>>,
322
- /// Restrict output to these token IDs only.
323
- pub allowed_token_ids: Option<Vec<u32>>,
324
- /// Tokenized bad words to avoid during generation.
325
- #[serde(rename = "_bad_words_token_ids")]
326
- pub bad_words_token_ids: Option<Vec<Vec<u32>>>,
327
- /// Parameters for configuring structured outputs (guided decoding).
328
- pub structured_outputs: Option<StructuredOutputsParams>,
329
- /// Specific token IDs for which log probabilities should be returned at
330
- /// each position.
331
- ///
332
- /// When set, the engine returns logprobs for exactly these tokens in
333
- /// addition to the sampled/scored token. Mutually exclusive with the
334
- /// `logprobs` count field in practice.
335
- pub logprob_token_ids: Option<Vec<u32>>,
336
- /// If `Some(true)`, the request will not attempt to read from the prefix
337
- /// cache; newly computed blocks may still populate the cache. `None`
338
- /// defers to engine-core defaults.
339
- pub skip_reading_prefix_cache: Option<bool>,
340
- /// Additional request parameters for custom extensions (from `vllm_xargs`).
341
- pub extra_args: Option<HashMap<String, serde_json::Value>>,
342
-}
343
-
344
-impl EngineCoreSamplingParams {
345
- /// Constructs a default sampling params for testing purposes only.
346
- pub fn for_test() -> Self {
347
- Self {
348
- temperature: 1.0,
349
- top_p: 1.0,
350
- top_k: 0,
351
- seed: None,
352
- max_tokens: 65536,
353
- min_tokens: 0,
354
- thinking_token_budget: None,
355
- logprobs: None,
356
- prompt_logprobs: None,
357
- min_p: 0.0,
358
- frequency_penalty: 0.0,
359
- presence_penalty: 0.0,
360
- repetition_penalty: 1.0,
361
- stop_token_ids: Vec::new(),
362
- eos_token_id: None,
363
- all_stop_token_ids: BTreeSet::new(),
364
- logit_bias: None,
365
- allowed_token_ids: None,
366
- bad_words_token_ids: None,
367
- structured_outputs: None,
368
- logprob_token_ids: None,
369
- skip_reading_prefix_cache: None,
370
- extra_args: None,
371
- }
372
- }
373
-}
374
-
375
-/// Extra kwargs consumed by engine-side reasoning parsers.
376
-///
377
-/// Original Python construction point:
378
-/// <https://github.com/vllm-project/vllm/blob/cec2ec11760f9f3beabd4c90451936078bf91533/vllm/entrypoints/openai/chat_completion/serving.py#L367-L369>
379
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
380
-pub struct ReasoningParserKwargs {
381
- /// Effective kwargs visible to the chat template for this request.
382
- pub chat_template_kwargs: HashMap<String, serde_json::Value>,
383
-}
384
-
385
-/// Engine-core add-request payload sent from frontend to engine.
386
-///
387
-/// Original Python definition:
388
-/// <https://github.com/vllm-project/vllm/blob/3f5bd482f5c1a5dbdffbbf68d624e20bb7032013/vllm/v1/engine/__init__.py#L80-L129>
389
-#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple, DefaultFromSerde)]
390
-pub struct EngineCoreRequest {
391
- pub request_id: String,
392
- pub prompt_token_ids: Option<Vec<u32>>,
393
- /// Multimodal features attached to the request.
394
- pub mm_features: Option<MmFeatures>,
395
- pub sampling_params: Option<EngineCoreSamplingParams>,
396
- /// Pooling parameters are preserved in the schema but not yet strongly
397
- /// typed.
398
- pub pooling_params: Option<OpaqueValue>,
399
- pub arrival_time: f64,
400
- #[serde(default)]
401
- pub lora_request: Option<lora::LoraRequest>,
402
- #[serde(default)]
403
- pub cache_salt: Option<String>,
404
- #[serde(default)]
405
- pub data_parallel_rank: Option<u32>,
406
- /// Unsupported in the first-stage Rust client because Python uses a custom
407
- /// tensor/aux-frame encoding path for this field.
408
- #[serde(default)]
409
- pub prompt_embeds: Option<OpaqueValue>,
410
- /// Per-position mask for mixed-mode inputs (e.g. chat completion with
411
- /// `prompt_embeds` content parts). `Some(true)` means real token id;
412
- /// `Some(false)` means the position uses a pre-computed entry from
413
- /// `prompt_embeds`. `None` for pure-tokens and pure-embeds requests.
414
- #[serde(default)]
415
- pub prompt_is_token_ids: Option<Vec<bool>>,
416
- /// Index of the client, used to ensure outputs are sent back to the same
417
- /// client when scaling out the frontend.
418
- #[serde(default)]
419
- pub client_index: u32,
420
- /// In DP mode, indicates which wave this request is expected to belong to.
421
- #[serde(default)]
422
- pub current_wave: u32,
423
- #[serde(default)]
424
- pub priority: i32,
425
- #[serde(default)]
426
- pub trace_headers: Option<BTreeMap<String, String>>,
427
- #[serde(default)]
428
- pub resumable: bool,
429
- /// Original user-provided request ID, used for output reporting and aborts.
430
- #[serde(default)]
431
- pub external_req_id: Option<String>,
432
- #[serde(default)]
433
- pub reasoning_ended: Option<bool>,
434
- /// Reasoning-parser kwargs forwarded from the frontend to the
435
- /// structured-output backend.
436
- #[serde(default)]
437
- pub reasoning_parser_kwargs: Option<ReasoningParserKwargs>,
438
- /// If `true`, the request should be added to the scheduler's waiting queue
439
- /// and immediately aborted, so connector-side cleanup runs via the
440
- /// standard `request_finished` hook.
441
- #[serde(default)]
442
- pub abort_immediately: bool,
443
-}
444
-
445
-impl EngineCoreRequest {
446
- /// Validate fields intentionally not supported in the first-stage client.
447
- pub fn validate(&self) -> Result<()> {
448
- if self.prompt_embeds.is_some() {
449
- return Err(Error::UnsupportedField {
450
- context: "EngineCoreRequest",
451
- field: "prompt_embeds",
452
- });
453
- }
454
- Ok(())
455
- }
456
-}
457
-
458
-/// Engine-core output for a single request.
459
-///
460
-/// Original Python definition:
461
-/// <https://github.com/vllm-project/vllm/blob/d3af8c18317c0dc008d42e4367fbb9045cfb7bf6/vllm/v1/engine/__init__.py#L154-L184>
462
-#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple, DefaultFromSerde)]
463
-pub struct EngineCoreOutput {
464
- pub request_id: String,
465
- pub new_token_ids: Vec<u32>,
466
- /// Decoded sample logprobs for the newly generated positions in this
467
- /// output.
468
- #[serde(default)]
469
- pub new_logprobs: Option<MaybeWireLogprobs>,
470
- /// Decoded prompt logprobs for the scored prompt positions emitted in this
471
- /// output.
472
- #[serde(default)]
473
- pub new_prompt_logprobs_tensors: Option<MaybeWireLogprobs>,
474
- #[serde(default)]
475
- pub pooling_output: Option<OpaqueValue>,
476
- #[serde(default)]
477
- pub finish_reason: Option<EngineCoreFinishReason>,
478
- #[serde(default)]
479
- pub stop_reason: Option<StopReason>,
480
- #[serde(default)]
481
- pub events: Option<Vec<EngineCoreEvent>>,
482
- #[serde(default)]
483
- pub kv_transfer_params: Option<serde_json::Value>,
484
- #[serde(default)]
485
- pub trace_headers: Option<OpaqueValue>,
486
- /// Breakdown of the scheduled prefill computation, set on the first output
487
- /// of a newly scheduled prefill and elided for subsequent decode outputs.
488
- #[serde(default)]
489
- pub prefill_stats: Option<PrefillStats>,
490
- #[serde(default)]
491
- pub routed_experts: Option<OpaqueValue>,
492
- /// Number of NaNs seen in logits. Values above zero indicate corruption.
493
- #[serde(default)]
494
- pub num_nans_in_logits: u32,
495
-}
496
-
497
-impl EngineCoreOutput {
498
- /// Returns whether this output is terminal for the request.
499
- pub fn finished(&self) -> bool {
500
- self.finish_reason.is_some()
501
- }
502
-}
503
-
504
-/// Batch of engine-core outputs returned to a frontend client.
505
-///
506
-/// Original Python definition:
507
-/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L186-L214>
508
-#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple, DefaultFromSerde)]
509
-pub struct EngineCoreOutputs {
510
- #[serde(default)]
511
- pub engine_index: u32,
512
- /// Outputs grouped for this client in the current engine tick.
513
- #[serde(default)]
514
- pub outputs: Vec<EngineCoreOutput>,
515
- #[serde(default)]
516
- pub scheduler_stats: Option<Box<SchedulerStats>>,
517
- #[serde(default)]
518
- pub timestamp: f64,
519
- #[serde(default)]
520
- pub utility_output: Option<UtilityOutput>,
521
- #[serde(default)]
522
- pub finished_requests: Option<BTreeSet<String>>,
523
- /// In DP mode, signals that the current wave finished and engines are
524
- /// paused.
525
- #[serde(default)]
526
- pub wave_complete: Option<u32>,
527
- /// In DP mode, signals that a request arrived for an old wave and the next
528
- /// wave needs to start in other engines.
529
- #[serde(default)]
530
- pub start_wave: Option<u32>,
531
-}
34
35
/// Encode a Rust value into msgpack using the protocol crate's serde model.
36
pub fn encode_msgpack<T>(value: &T) -> Result<Vec<u8>>
@@ -564,81 +66,17 @@ where
66
})
67
}
68
69
+/// Decode a msgpack payload into a dynamic value for diagnostics and tests.
70
pub fn decode_value(bytes: &[u8]) -> Result<Value> {
71
Ok(rmpv::decode::read_value(&mut Cursor::new(bytes))?)
72
}
73
74
#[cfg(test)]
75
mod tests {
573
- use std::collections::BTreeSet;
76
+ use std::collections::BTreeMap;
77
78
use super::*;
79
577
- #[test]
578
- fn engine_core_request_serializes_as_full_array() {
579
- let request = EngineCoreRequest {
580
- request_id: "req-1".to_string(),
581
- prompt_token_ids: Some(vec![1, 2, 3]),
582
- sampling_params: Some(EngineCoreSamplingParams {
583
- max_tokens: 8,
584
- ..EngineCoreSamplingParams::for_test()
585
- }),
586
- arrival_time: 1234.5,
587
- client_index: 7,
588
- ..EngineCoreRequest::default()
589
- };
590
-
591
- let encoded = encode_msgpack(&request).unwrap();
592
- let value = decode_value(&encoded).unwrap();
593
- let array = match value {
594
- Value::Array(array) => array,
595
- other => panic!("expected array, got {other:?}"),
596
- };
597
-
598
- assert_eq!(array.len(), 20);
599
- assert_eq!(array[0], Value::from("req-1"));
600
- assert_eq!(array[2], Value::Nil);
601
- assert_eq!(array[4], Value::Nil);
602
- assert_eq!(array[10], Value::Nil);
603
- assert_eq!(array[11], Value::from(7));
604
- }
605
-
606
- #[test]
607
- fn engine_core_outputs_roundtrip_finished_fields() {
608
- let outputs = EngineCoreOutputs {
609
- outputs: vec![EngineCoreOutput {
610
- request_id: "req-1".to_string(),
611
- new_token_ids: vec![42],
612
- new_logprobs: None,
613
- new_prompt_logprobs_tensors: None,
614
- pooling_output: None,
615
- finish_reason: Some(EngineCoreFinishReason::Length),
616
- stop_reason: Some(StopReason::Text("stop".to_string())),
617
- events: None,
618
- kv_transfer_params: None,
619
- trace_headers: None,
620
- prefill_stats: None,
621
- routed_experts: None,
622
- num_nans_in_logits: 0,
623
- }],
624
- finished_requests: Some(BTreeSet::from(["req-1".to_string()])),
625
- ..Default::default()
626
- };
627
-
628
- let encoded = encode_msgpack(&outputs).unwrap();
629
- let decoded: EngineCoreOutputs = decode_msgpack(&encoded).unwrap();
630
-
631
- assert_eq!(decoded.outputs.len(), 1);
632
- assert_eq!(
633
- decoded.outputs[0].finish_reason,
634
- Some(EngineCoreFinishReason::Length)
635
- );
636
- assert_eq!(
637
- decoded.finished_requests,
638
- Some(BTreeSet::from(["req-1".to_string()]))
639
- );
640
- }
641
-
80
#[test]
81
fn decode_msgpack_includes_type_name_and_value_fallback() {
82
let error = decode_msgpack::<u64>(
@@ -648,72 +86,4 @@ mod tests {
86
87
expect_test::expect![[r#"messagepack decode failed for u64: wrong msgpack marker FixMap(1); value fallback: {"status": "READY"}"#]].assert_eq(&error.to_report_string());
88
}
651
-
652
- #[test]
653
- fn structured_outputs_backend_ignores_deserialized_value() {
654
- let params: StructuredOutputsParams = serde_json::from_value(serde_json::json!({
655
- "json_object": true,
656
- "_backend": "xgrammar",
657
- }))
658
- .unwrap();
659
-
660
- assert_eq!(params.backend, StructuredOutputBackend::Guidance);
661
-
662
- let value = serde_json::to_value(params).unwrap();
663
- assert_eq!(value["_backend"], "guidance");
664
- }
665
-
666
- /// A real `sampling_params` is a sparse `omit_defaults` map; absent fields
667
- /// must fall back to defaults. `python_compat` can't catch this since Rust
668
- /// encodes full maps (see `engine_core_request_serializes_as_full_array`).
669
- #[test]
670
- fn decodes_sampling_params_with_omitted_defaults() {
671
- let sampling_params = Value::Map(vec![
672
- (
673
- Value::from("stop_token_ids"),
674
- Value::Array(vec![Value::from(151643u32)]),
675
- ),
676
- (Value::from("skip_reading_prefix_cache"), Value::from(false)),
677
- ]);
678
- let request = Value::Array(vec![
679
- Value::from("req-omit-defaults"),
680
- Value::Array(vec![
681
- Value::from(1u32),
682
- Value::from(2u32),
683
- Value::from(3u32),
684
- ]),
685
- Value::Nil,
686
- sampling_params,
687
- Value::Nil,
688
- Value::from(1.0f64),
689
- ]);
690
-
691
- let mut bytes = Vec::new();
692
- rmpv::encode::write_value(&mut bytes, &request).unwrap();
693
-
694
- let decoded: EngineCoreRequest = decode_msgpack(&bytes)
695
- .expect("a real omit_defaults request must decode (regression: missing field)");
696
-
697
- assert_eq!(decoded.request_id, "req-omit-defaults");
698
- let sampling = decoded.sampling_params.expect("sampling params present");
699
-
700
- assert_eq!(sampling.stop_token_ids, vec![151643]);
701
- assert_eq!(sampling.skip_reading_prefix_cache, Some(false));
702
-
703
- // Omitted fields -> Python defaults.
704
- assert_eq!(sampling.temperature, 1.0);
705
- assert_eq!(sampling.top_p, 1.0);
706
- assert_eq!(sampling.top_k, 0);
707
- assert_eq!(sampling.seed, None);
708
- assert_eq!(sampling.max_tokens, 16);
709
- assert_eq!(sampling.min_tokens, 0);
710
- assert_eq!(sampling.min_p, 0.0);
711
- assert_eq!(sampling.frequency_penalty, 0.0);
712
- assert_eq!(sampling.presence_penalty, 0.0);
713
- assert_eq!(sampling.repetition_penalty, 1.0);
714
- assert_eq!(sampling.logprobs, None);
715
- assert_eq!(sampling.prompt_logprobs, None);
716
- assert_eq!(sampling.eos_token_id, None);
717
- assert!(sampling.all_stop_token_ids.is_empty());
718
- }
89
}
rust/src/engine-core-client/src/protocol/output.rs
renamed
+219
-3
@@ -1,10 +1,164 @@
1
use std::collections::BTreeSet;
2
3
use enum_as_inner::EnumAsInner;
4
+use serde::{Deserialize, Serialize};
5
+use serde_default::DefaultFromSerde;
6
+use serde_repr::{Deserialize_repr, Serialize_repr};
7
+use serde_tuple::{Deserialize_tuple, Serialize_tuple};
8
9
use super::utility::UtilityOutput;
6
-use super::{EngineCoreOutput, EngineCoreOutputs};
7
-use crate::protocol::stats::SchedulerStats;
10
+use crate::error::{Error, Result, ext_value_decode};
11
+use crate::protocol::logprobs::MaybeWireLogprobs;
12
+use crate::protocol::stats::{PrefillStats, SchedulerStats};
13
+use crate::protocol::{OpaqueValue, decode_msgpack};
14
+
15
+/// The stop reason associated with a finished output.
16
+///
17
+/// Python models this as the union-typed `stop_reason: int | str | None`
18
+/// field on `EngineCoreOutput`; the Rust client narrows it into a tagged enum.
19
+///
20
+/// Original Python field:
21
+/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L155>
22
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23
+#[serde(untagged)]
24
+pub enum StopReason {
25
+ TokenId(u32),
26
+ Text(String),
27
+}
28
+
29
+/// Reason a request finished: stop, length, abort, error, or repetition.
30
+///
31
+/// This mirrors the Python enum and uses integer encoding for compact wire
32
+/// representation.
33
+///
34
+/// Original Python definition:
35
+/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L41-L63>
36
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
37
+#[repr(u8)]
38
+pub enum EngineCoreFinishReason {
39
+ /// A stop string was emitted.
40
+ Stop = 0,
41
+ /// `max_tokens` or `max_model_len` was reached.
42
+ Length = 1,
43
+ /// The request was aborted by the client.
44
+ Abort = 2,
45
+ /// A retryable request-level internal error occurred.
46
+ Error = 3,
47
+ /// A repetitive token pattern was detected.
48
+ Repetition = 4,
49
+}
50
+
51
+/// Event types emitted by engine-core for one request.
52
+///
53
+/// Original Python definition:
54
+/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L113-L118>
55
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
56
+#[repr(u8)]
57
+pub enum EngineCoreEventType {
58
+ Queued = 1,
59
+ Scheduled = 2,
60
+ Preempted = 3,
61
+}
62
+
63
+/// A timestamped engine-core event associated with one request.
64
+///
65
+/// Original Python definition:
66
+/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L121-L130>
67
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68
+pub struct EngineCoreEvent {
69
+ pub r#type: EngineCoreEventType,
70
+ pub timestamp: f64,
71
+}
72
+
73
+/// Engine-core output for a single request.
74
+///
75
+/// Original Python definition:
76
+/// <https://github.com/vllm-project/vllm/blob/d3af8c18317c0dc008d42e4367fbb9045cfb7bf6/vllm/v1/engine/__init__.py#L154-L184>
77
+#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple, DefaultFromSerde)]
78
+pub struct EngineCoreOutput {
79
+ pub request_id: String,
80
+ pub new_token_ids: Vec<u32>,
81
+ /// Decoded sample logprobs for the newly generated positions in this
82
+ /// output.
83
+ #[serde(default)]
84
+ pub new_logprobs: Option<MaybeWireLogprobs>,
85
+ /// Decoded prompt logprobs for the scored prompt positions emitted in this
86
+ /// output.
87
+ #[serde(default)]
88
+ pub new_prompt_logprobs_tensors: Option<MaybeWireLogprobs>,
89
+ #[serde(default)]
90
+ pub pooling_output: Option<OpaqueValue>,
91
+ #[serde(default)]
92
+ pub finish_reason: Option<EngineCoreFinishReason>,
93
+ #[serde(default)]
94
+ pub stop_reason: Option<StopReason>,
95
+ #[serde(default)]
96
+ pub events: Option<Vec<EngineCoreEvent>>,
97
+ #[serde(default)]
98
+ pub kv_transfer_params: Option<serde_json::Value>,
99
+ #[serde(default)]
100
+ pub trace_headers: Option<OpaqueValue>,
101
+ /// Breakdown of the scheduled prefill computation, set on the first output
102
+ /// of a newly scheduled prefill and elided for subsequent decode outputs.
103
+ #[serde(default)]
104
+ pub prefill_stats: Option<PrefillStats>,
105
+ #[serde(default)]
106
+ pub routed_experts: Option<OpaqueValue>,
107
+ /// Number of NaNs seen in logits. Values above zero indicate corruption.
108
+ #[serde(default)]
109
+ pub num_nans_in_logits: u32,
110
+}
111
+
112
+impl EngineCoreOutput {
113
+ /// Returns whether this output is terminal for the request.
114
+ pub fn finished(&self) -> bool {
115
+ self.finish_reason.is_some()
116
+ }
117
+
118
+ /// Resolve all wire-format fields in-place by looking up aux frames and
119
+ /// decoding raw-view payloads as needed.
120
+ fn resolve_in_place<Frame>(&mut self, frames: &[Frame]) -> Result<()>
121
+ where
122
+ Frame: AsRef<[u8]>,
123
+ {
124
+ self.new_logprobs = (self.new_logprobs.take())
125
+ .map(|value| value.resolve(frames, "new_logprobs"))
126
+ .transpose()?;
127
+ self.new_prompt_logprobs_tensors = (self.new_prompt_logprobs_tensors.take())
128
+ .map(|value| value.resolve(frames, "new_prompt_logprobs_tensors"))
129
+ .transpose()?;
130
+ Ok(())
131
+ }
132
+}
133
+
134
+/// Batch of engine-core outputs returned to a frontend client.
135
+///
136
+/// Original Python definition:
137
+/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L186-L214>
138
+#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple, DefaultFromSerde)]
139
+pub struct EngineCoreOutputs {
140
+ #[serde(default)]
141
+ pub engine_index: u32,
142
+ /// Outputs grouped for this client in the current engine tick.
143
+ #[serde(default)]
144
+ pub outputs: Vec<EngineCoreOutput>,
145
+ #[serde(default)]
146
+ pub scheduler_stats: Option<Box<SchedulerStats>>,
147
+ #[serde(default)]
148
+ pub timestamp: f64,
149
+ #[serde(default)]
150
+ pub utility_output: Option<UtilityOutput>,
151
+ #[serde(default)]
152
+ pub finished_requests: Option<BTreeSet<String>>,
153
+ /// In DP mode, signals that the current wave finished and engines are
154
+ /// paused.
155
+ #[serde(default)]
156
+ pub wave_complete: Option<u32>,
157
+ /// In DP mode, signals that a request arrived for an old wave and the next
158
+ /// wave needs to start in other engines.
159
+ #[serde(default)]
160
+ pub start_wave: Option<u32>,
161
+}
162
163
/// Data-parallel control notifications multiplexed through `EngineCoreOutputs`.
164
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -49,6 +203,18 @@ pub enum ClassifiedEngineCoreOutputs {
203
}
204
205
impl EngineCoreOutputs {
206
+ /// Resolve all wire-format fields in-place by looking up aux frames and
207
+ /// decoding raw-view payloads as needed.
208
+ fn resolve_in_place<Frame>(&mut self, frames: &[Frame]) -> Result<()>
209
+ where
210
+ Frame: AsRef<[u8]>,
211
+ {
212
+ for output in &mut self.outputs {
213
+ output.resolve_in_place(frames)?;
214
+ }
215
+ Ok(())
216
+ }
217
+
218
/// Classify the raw wire message into a more semantic Rust enum.
219
pub fn classify(self) -> ClassifiedEngineCoreOutputs {
220
let has_request_payload = !self.outputs.is_empty()
@@ -92,12 +258,62 @@ impl EngineCoreOutputs {
258
}
259
}
260
261
+/// Decode one ordinary or multipart engine-core output message into the strong
262
+/// typed public protocol shape.
263
+pub fn decode_engine_core_outputs<Frame>(frames: &[Frame]) -> Result<EngineCoreOutputs>
264
+where
265
+ Frame: AsRef<[u8]>,
266
+{
267
+ let first_frame = frames.first().ok_or_else(|| ext_value_decode!("missing output frame"))?;
268
+
269
+ let mut outputs: EngineCoreOutputs = decode_msgpack(first_frame.as_ref())?;
270
+ outputs.resolve_in_place(frames)?;
271
+ Ok(outputs)
272
+}
273
+
274
#[cfg(test)]
275
mod tests {
276
use std::collections::BTreeSet;
277
278
use super::*;
100
- use crate::protocol::EngineCoreOutput;
279
+ use crate::protocol::output::EngineCoreOutput;
280
+ use crate::protocol::{decode_msgpack, encode_msgpack};
281
+
282
+ #[test]
283
+ fn engine_core_outputs_roundtrip_finished_fields() {
284
+ let outputs = EngineCoreOutputs {
285
+ outputs: vec![EngineCoreOutput {
286
+ request_id: "req-1".to_string(),
287
+ new_token_ids: vec![42],
288
+ new_logprobs: None,
289
+ new_prompt_logprobs_tensors: None,
290
+ pooling_output: None,
291
+ finish_reason: Some(EngineCoreFinishReason::Length),
292
+ stop_reason: Some(StopReason::Text("stop".to_string())),
293
+ events: None,
294
+ kv_transfer_params: None,
295
+ trace_headers: None,
296
+ prefill_stats: None,
297
+ routed_experts: None,
298
+ num_nans_in_logits: 0,
299
+ }],
300
+ finished_requests: Some(BTreeSet::from(["req-1".to_string()])),
301
+ ..Default::default()
302
+ };
303
+
304
+ let encoded = encode_msgpack(&outputs).unwrap();
305
+ let decoded: EngineCoreOutputs = decode_msgpack(&encoded).unwrap();
306
+
307
+ assert_eq!(decoded.outputs.len(), 1);
308
+ assert_eq!(
309
+ decoded.outputs[0].finish_reason,
310
+ Some(EngineCoreFinishReason::Length)
311
+ );
312
+ assert_eq!(
313
+ decoded.finished_requests,
314
+ Some(BTreeSet::from(["req-1".to_string()]))
315
+ );
316
+ }
317
318
#[test]
319
fn engine_core_outputs_classify_request_batch() {
rust/src/engine-core-client/src/protocol/request.rs
new
+175
@@ -0,0 +1,175 @@
1
+use std::collections::{BTreeMap, HashMap};
2
+
3
+use bytes::Bytes;
4
+use serde::{Deserialize, Serialize};
5
+use serde_default::DefaultFromSerde;
6
+use serde_tuple::{Deserialize_tuple, Serialize_tuple};
7
+
8
+use crate::protocol::multimodal::MmFeatures;
9
+use crate::protocol::sampling::EngineCoreSamplingParams;
10
+use crate::protocol::{OpaqueValue, lora};
11
+use crate::{Error, Result};
12
+
13
+/// Request types are encoded as single-byte protocol constants so they can be
14
+/// sent over the ZMQ socket without an extra encoding step.
15
+///
16
+/// Original Python definition:
17
+/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/v1/engine/__init__.py#L217-L228>
18
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19
+#[repr(u8)]
20
+pub enum EngineCoreRequestType {
21
+ Add = 0,
22
+ Abort = 1,
23
+ StartDpWave = 2,
24
+ Utility = 3,
25
+}
26
+
27
+impl EngineCoreRequestType {
28
+ /// Decode the single-byte request type frame used on the engine input
29
+ /// socket. Returns `None` for unrecognized values.
30
+ pub fn from_frame(frame: &[u8]) -> Option<Self> {
31
+ let [value] = frame else {
32
+ return None;
33
+ };
34
+
35
+ match value {
36
+ 0 => Some(Self::Add),
37
+ 1 => Some(Self::Abort),
38
+ 2 => Some(Self::StartDpWave),
39
+ 3 => Some(Self::Utility),
40
+ _ => None,
41
+ }
42
+ }
43
+
44
+ /// Encode the request type as the single-byte frame used on the engine
45
+ /// input socket.
46
+ pub fn to_frame(self) -> Bytes {
47
+ Bytes::from_static(match self {
48
+ Self::Add => b"\x00",
49
+ Self::Abort => b"\x01",
50
+ Self::StartDpWave => b"\x02",
51
+ Self::Utility => b"\x03",
52
+ })
53
+ }
54
+}
55
+
56
+/// Extra kwargs consumed by engine-side reasoning parsers.
57
+///
58
+/// Original Python construction point:
59
+/// <https://github.com/vllm-project/vllm/blob/cec2ec11760f9f3beabd4c90451936078bf91533/vllm/entrypoints/openai/chat_completion/serving.py#L367-L369>
60
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61
+pub struct ReasoningParserKwargs {
62
+ /// Effective kwargs visible to the chat template for this request.
63
+ pub chat_template_kwargs: HashMap<String, serde_json::Value>,
64
+}
65
+
66
+/// Engine-core add-request payload sent from frontend to engine.
67
+///
68
+/// Original Python definition:
69
+/// <https://github.com/vllm-project/vllm/blob/3f5bd482f5c1a5dbdffbbf68d624e20bb7032013/vllm/v1/engine/__init__.py#L80-L129>
70
+#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple, DefaultFromSerde)]
71
+pub struct EngineCoreRequest {
72
+ pub request_id: String,
73
+ pub prompt_token_ids: Option<Vec<u32>>,
74
+ /// Multimodal features attached to the request.
75
+ pub mm_features: Option<MmFeatures>,
76
+ pub sampling_params: Option<EngineCoreSamplingParams>,
77
+ /// Pooling parameters are preserved in the schema but not yet strongly
78
+ /// typed.
79
+ pub pooling_params: Option<OpaqueValue>,
80
+ pub arrival_time: f64,
81
+ #[serde(default)]
82
+ pub lora_request: Option<lora::LoraRequest>,
83
+ #[serde(default)]
84
+ pub cache_salt: Option<String>,
85
+ #[serde(default)]
86
+ pub data_parallel_rank: Option<u32>,
87
+ /// Unsupported in the first-stage Rust client because Python uses a custom
88
+ /// tensor/aux-frame encoding path for this field.
89
+ #[serde(default)]
90
+ pub prompt_embeds: Option<OpaqueValue>,
91
+ /// Per-position mask for mixed-mode inputs (e.g. chat completion with
92
+ /// `prompt_embeds` content parts). `Some(true)` means real token id;
93
+ /// `Some(false)` means the position uses a pre-computed entry from
94
+ /// `prompt_embeds`. `None` for pure-tokens and pure-embeds requests.
95
+ #[serde(default)]
96
+ pub prompt_is_token_ids: Option<Vec<bool>>,
97
+ /// Index of the client, used to ensure outputs are sent back to the same
98
+ /// client when scaling out the frontend.
99
+ #[serde(default)]
100
+ pub client_index: u32,
101
+ /// In DP mode, indicates which wave this request is expected to belong to.
102
+ #[serde(default)]
103
+ pub current_wave: u32,
104
+ #[serde(default)]
105
+ pub priority: i32,
106
+ #[serde(default)]
107
+ pub trace_headers: Option<BTreeMap<String, String>>,
108
+ #[serde(default)]
109
+ pub resumable: bool,
110
+ /// Original user-provided request ID, used for output reporting and aborts.
111
+ #[serde(default)]
112
+ pub external_req_id: Option<String>,
113
+ #[serde(default)]
114
+ pub reasoning_ended: Option<bool>,
115
+ /// Reasoning-parser kwargs forwarded from the frontend to the
116
+ /// structured-output backend.
117
+ #[serde(default)]
118
+ pub reasoning_parser_kwargs: Option<ReasoningParserKwargs>,
119
+ /// If `true`, the request should be added to the scheduler's waiting queue
120
+ /// and immediately aborted, so connector-side cleanup runs via the
121
+ /// standard `request_finished` hook.
122
+ #[serde(default)]
123
+ pub abort_immediately: bool,
124
+}
125
+
126
+impl EngineCoreRequest {
127
+ /// Validate fields intentionally not supported in the first-stage client.
128
+ pub fn validate(&self) -> Result<()> {
129
+ if self.prompt_embeds.is_some() {
130
+ return Err(Error::UnsupportedField {
131
+ context: "EngineCoreRequest",
132
+ field: "prompt_embeds",
133
+ });
134
+ }
135
+ Ok(())
136
+ }
137
+}
138
+
139
+#[cfg(test)]
140
+mod tests {
141
+ use rmpv::Value;
142
+
143
+ use super::*;
144
+ use crate::protocol::sampling::EngineCoreSamplingParams;
145
+ use crate::protocol::{decode_value, encode_msgpack};
146
+
147
+ #[test]
148
+ fn engine_core_request_serializes_as_full_array() {
149
+ let request = EngineCoreRequest {
150
+ request_id: "req-1".to_string(),
151
+ prompt_token_ids: Some(vec![1, 2, 3]),
152
+ sampling_params: Some(EngineCoreSamplingParams {
153
+ max_tokens: 8,
154
+ ..EngineCoreSamplingParams::for_test()
155
+ }),
156
+ arrival_time: 1234.5,
157
+ client_index: 7,
158
+ ..EngineCoreRequest::default()
159
+ };
160
+
161
+ let encoded = encode_msgpack(&request).unwrap();
162
+ let value = decode_value(&encoded).unwrap();
163
+ let array = match value {
164
+ Value::Array(array) => array,
165
+ other => panic!("expected array, got {other:?}"),
166
+ };
167
+
168
+ assert_eq!(array.len(), 20);
169
+ assert_eq!(array[0], Value::from("req-1"));
170
+ assert_eq!(array[2], Value::Nil);
171
+ assert_eq!(array[4], Value::Nil);
172
+ assert_eq!(array[10], Value::Nil);
173
+ assert_eq!(array[11], Value::from(7));
174
+ }
175
+}
rust/src/engine-core-client/src/protocol/sampling.rs
new
+211
@@ -0,0 +1,211 @@
1
+use std::collections::{BTreeSet, HashMap};
2
+
3
+use serde::{Deserialize, Serialize};
4
+use serde_default::DefaultFromSerde;
5
+
6
+use crate::protocol::structured_outputs::StructuredOutputsParams;
7
+
8
+fn default_top_p() -> f32 {
9
+ 1.0
10
+}
11
+
12
+fn default_repetition_penalty() -> f32 {
13
+ 1.0
14
+}
15
+
16
+fn default_temperature() -> f32 {
17
+ 1.0
18
+}
19
+
20
+fn default_max_tokens() -> u32 {
21
+ 16
22
+}
23
+
24
+/// Engine-core-facing sampling parameters for text generation.
25
+///
26
+/// This is the normalized southbound subset used by the Rust frontend when it
27
+/// talks to Python engine-core over the wire. User-facing request semantics
28
+/// such as `stop` strings, `n`, `ignore_eos`, and output aggregation mode are
29
+/// intentionally handled by higher layers before values reach this DTO.
30
+///
31
+/// Original Python definition:
32
+/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/sampling_params.py#L155-L291>
33
+// Python's SamplingParams is `omit_defaults=True`, so msgpack drops
34
+// default-valued keys; default the whole struct. Per-field fns cover the
35
+// non-zero defaults.
36
+#[serde_with::skip_serializing_none]
37
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, DefaultFromSerde)]
38
+#[serde(default)]
39
+pub struct EngineCoreSamplingParams {
40
+ /// Controls randomness. Lower values are more deterministic; zero means
41
+ /// greedy sampling.
42
+ #[serde(default = "default_temperature")]
43
+ pub temperature: f32,
44
+ /// Cumulative probability threshold for nucleus sampling.
45
+ #[serde(default = "default_top_p")]
46
+ pub top_p: f32,
47
+ /// Maximum number of top tokens to consider. `0` means all tokens.
48
+ pub top_k: u32,
49
+ /// Random seed used by the sampler when present.
50
+ pub seed: Option<i64>,
51
+ /// Maximum number of tokens to generate per output sequence.
52
+ #[serde(default = "default_max_tokens")]
53
+ pub max_tokens: u32,
54
+ /// Minimum number of tokens to generate before EOS or stop-token handling.
55
+ pub min_tokens: u32,
56
+ /// Maximum number of reasoning ("thinking") tokens to emit before the
57
+ /// reasoning section is force-closed. `None` means unlimited; the
58
+ /// user-facing `-1` sentinel is normalized to `None` by the frontend before
59
+ /// reaching this DTO, so only non-negative values are sent. Enforced
60
+ /// engine-side (and only when a reasoning parser is configured).
61
+ pub thinking_token_budget: Option<u64>,
62
+ /// Number of log probabilities to return per generated token.
63
+ ///
64
+ /// `None` disables sample logprobs. `-1` requests the full vocabulary.
65
+ pub logprobs: Option<i32>,
66
+ /// Number of log probabilities to return per prompt token.
67
+ ///
68
+ /// `None` disables prompt logprobs. `-1` requests the full vocabulary.
69
+ pub prompt_logprobs: Option<i32>,
70
+ /// Minimum probability threshold for token sampling.
71
+ pub min_p: f32,
72
+ /// Frequency penalty applied by the sampler.
73
+ pub frequency_penalty: f32,
74
+ /// Presence penalty applied by the sampler.
75
+ pub presence_penalty: f32,
76
+ /// Repetition penalty applied by the sampler.
77
+ #[serde(default = "default_repetition_penalty")]
78
+ pub repetition_penalty: f32,
79
+ /// Token IDs that stop generation.
80
+ pub stop_token_ids: Vec<u32>,
81
+ /// Primary EOS token ID used by engine-core's dedicated EOS stop path.
82
+ ///
83
+ /// This mirrors Python's internal `_eos_token_id` field and is derived by
84
+ /// the frontend from tokenizer/model metadata rather than supplied directly
85
+ /// by end users.
86
+ #[serde(rename = "_eos_token_id")]
87
+ pub eos_token_id: Option<u32>,
88
+ /// Complete stop-token set used by engine-core for `min_tokens` masking.
89
+ ///
90
+ /// This mirrors Python's internal `_all_stop_token_ids` field and should
91
+ /// contain explicit `stop_token_ids` plus any frontend-derived EOS token
92
+ /// IDs.
93
+ #[serde(rename = "_all_stop_token_ids")]
94
+ pub all_stop_token_ids: BTreeSet<u32>,
95
+ /// Logit biases to apply during sampling.
96
+ /// Keys are token IDs
97
+ pub logit_bias: Option<HashMap<u32, f32>>,
98
+ /// Restrict output to these token IDs only.
99
+ pub allowed_token_ids: Option<Vec<u32>>,
100
+ /// Tokenized bad words to avoid during generation.
101
+ #[serde(rename = "_bad_words_token_ids")]
102
+ pub bad_words_token_ids: Option<Vec<Vec<u32>>>,
103
+ /// Parameters for configuring structured outputs (guided decoding).
104
+ pub structured_outputs: Option<StructuredOutputsParams>,
105
+ /// Specific token IDs for which log probabilities should be returned at
106
+ /// each position.
107
+ ///
108
+ /// When set, the engine returns logprobs for exactly these tokens in
109
+ /// addition to the sampled/scored token. Mutually exclusive with the
110
+ /// `logprobs` count field in practice.
111
+ pub logprob_token_ids: Option<Vec<u32>>,
112
+ /// If `Some(true)`, the request will not attempt to read from the prefix
113
+ /// cache; newly computed blocks may still populate the cache. `None`
114
+ /// defers to engine-core defaults.
115
+ pub skip_reading_prefix_cache: Option<bool>,
116
+ /// Additional request parameters for custom extensions (from `vllm_xargs`).
117
+ pub extra_args: Option<HashMap<String, serde_json::Value>>,
118
+}
119
+
120
+impl EngineCoreSamplingParams {
121
+ /// Constructs a default sampling params for testing purposes only.
122
+ pub fn for_test() -> Self {
123
+ Self {
124
+ temperature: 1.0,
125
+ top_p: 1.0,
126
+ top_k: 0,
127
+ seed: None,
128
+ max_tokens: 65536,
129
+ min_tokens: 0,
130
+ thinking_token_budget: None,
131
+ logprobs: None,
132
+ prompt_logprobs: None,
133
+ min_p: 0.0,
134
+ frequency_penalty: 0.0,
135
+ presence_penalty: 0.0,
136
+ repetition_penalty: 1.0,
137
+ stop_token_ids: Vec::new(),
138
+ eos_token_id: None,
139
+ all_stop_token_ids: BTreeSet::new(),
140
+ logit_bias: None,
141
+ allowed_token_ids: None,
142
+ bad_words_token_ids: None,
143
+ structured_outputs: None,
144
+ logprob_token_ids: None,
145
+ skip_reading_prefix_cache: None,
146
+ extra_args: None,
147
+ }
148
+ }
149
+}
150
+
151
+#[cfg(test)]
152
+mod tests {
153
+ use rmpv::Value;
154
+
155
+ use crate::protocol::decode_msgpack;
156
+ use crate::protocol::request::EngineCoreRequest;
157
+
158
+ /// A real `sampling_params` is a sparse `omit_defaults` map; absent fields
159
+ /// must fall back to defaults. `python_compat` can't catch this since Rust
160
+ /// encodes full maps (see `engine_core_request_serializes_as_full_array`).
161
+ #[test]
162
+ fn decodes_sampling_params_with_omitted_defaults() {
163
+ let sampling_params = Value::Map(vec![
164
+ (
165
+ Value::from("stop_token_ids"),
166
+ Value::Array(vec![Value::from(151643u32)]),
167
+ ),
168
+ (Value::from("skip_reading_prefix_cache"), Value::from(false)),
169
+ ]);
170
+ let request = Value::Array(vec![
171
+ Value::from("req-omit-defaults"),
172
+ Value::Array(vec![
173
+ Value::from(1u32),
174
+ Value::from(2u32),
175
+ Value::from(3u32),
176
+ ]),
177
+ Value::Nil,
178
+ sampling_params,
179
+ Value::Nil,
180
+ Value::from(1.0f64),
181
+ ]);
182
+
183
+ let mut bytes = Vec::new();
184
+ rmpv::encode::write_value(&mut bytes, &request).unwrap();
185
+
186
+ let decoded: EngineCoreRequest = decode_msgpack(&bytes)
187
+ .expect("a real omit_defaults request must decode (regression: missing field)");
188
+
189
+ assert_eq!(decoded.request_id, "req-omit-defaults");
190
+ let sampling = decoded.sampling_params.expect("sampling params present");
191
+
192
+ assert_eq!(sampling.stop_token_ids, vec![151643]);
193
+ assert_eq!(sampling.skip_reading_prefix_cache, Some(false));
194
+
195
+ // Omitted fields -> Python defaults.
196
+ assert_eq!(sampling.temperature, 1.0);
197
+ assert_eq!(sampling.top_p, 1.0);
198
+ assert_eq!(sampling.top_k, 0);
199
+ assert_eq!(sampling.seed, None);
200
+ assert_eq!(sampling.max_tokens, 16);
201
+ assert_eq!(sampling.min_tokens, 0);
202
+ assert_eq!(sampling.min_p, 0.0);
203
+ assert_eq!(sampling.frequency_penalty, 0.0);
204
+ assert_eq!(sampling.presence_penalty, 0.0);
205
+ assert_eq!(sampling.repetition_penalty, 1.0);
206
+ assert_eq!(sampling.logprobs, None);
207
+ assert_eq!(sampling.prompt_logprobs, None);
208
+ assert_eq!(sampling.eos_token_id, None);
209
+ assert!(sampling.all_stop_token_ids.is_empty());
210
+ }
211
+}
rust/src/engine-core-client/src/protocol/structured_outputs.rs
new
+81
@@ -0,0 +1,81 @@
1
+use serde::{Deserialize, Serialize};
2
+
3
+/// Structured-output backend selected for EngineCore grammar compilation.
4
+///
5
+/// Python vLLM stores this in `StructuredOutputsParams._backend` after request
6
+/// validation. The Rust frontend currently always lowers structured-output
7
+/// requests to guidance, while ignoring any user-supplied `_backend` value.
8
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
9
+#[serde(rename_all = "kebab-case")]
10
+pub enum StructuredOutputBackend {
11
+ Xgrammar,
12
+ #[default]
13
+ Guidance,
14
+ Outlines,
15
+ LmFormatEnforcer,
16
+}
17
+
18
+/// Parameters for configuring structured outputs (guided decoding).
19
+///
20
+/// Exactly one constraint field (`json`, `regex`, `choice`, `grammar`,
21
+/// `json_object`, or `structural_tag`) should be set. The engine-core
22
+/// backend selects the appropriate grammar compiler based on which field
23
+/// is present.
24
+///
25
+/// Original Python definition:
26
+/// <https://github.com/vllm-project/vllm/blob/f22d6e026798a74e6542a52ef776c054f2de572a/vllm/sampling_params.py#L36-L107>
27
+#[serde_with::skip_serializing_none]
28
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
29
+#[serde(default)]
30
+pub struct StructuredOutputsParams {
31
+ /// JSON schema (as a dict/object or JSON string) constraining the output.
32
+ pub json: Option<serde_json::Value>,
33
+ /// Regular expression the output must match.
34
+ pub regex: Option<String>,
35
+ /// List of allowed output strings (the model must produce one of these).
36
+ pub choice: Option<Vec<String>>,
37
+ /// Context-free grammar (in EBNF-like notation) the output must conform to.
38
+ pub grammar: Option<String>,
39
+ /// When `true`, output must be valid JSON (free-form, no schema).
40
+ pub json_object: Option<bool>,
41
+ /// Disable any additional whitespace in guided JSON output.
42
+ #[serde(skip_serializing_if = "crate::protocol::is_false")]
43
+ pub disable_any_whitespace: bool,
44
+ /// Disable `additionalProperties` in JSON schema output.
45
+ #[serde(skip_serializing_if = "crate::protocol::is_false")]
46
+ pub disable_additional_properties: bool,
47
+ /// Custom whitespace pattern for guided JSON output.
48
+ pub whitespace_pattern: Option<String>,
49
+ /// Structural tag configuration (JSON-encoded string).
50
+ pub structural_tag: Option<String>,
51
+ /// Structured-output backend, mirroring Python's internal `_backend`.
52
+ ///
53
+ /// User-supplied values are ignored during deserialization. This matches
54
+ /// Python's request boundary, where `_backend` is set by validation rather
55
+ /// than accepted as a request-level backend selector.
56
+ #[serde(
57
+ default,
58
+ rename = "_backend",
59
+ deserialize_with = "serde_with::rust::deserialize_ignore_any"
60
+ )]
61
+ pub backend: StructuredOutputBackend,
62
+}
63
+
64
+#[cfg(test)]
65
+mod tests {
66
+ use super::*;
67
+
68
+ #[test]
69
+ fn structured_outputs_backend_ignores_deserialized_value() {
70
+ let params: StructuredOutputsParams = serde_json::from_value(serde_json::json!({
71
+ "json_object": true,
72
+ "_backend": "xgrammar",
73
+ }))
74
+ .unwrap();
75
+
76
+ assert_eq!(params.backend, StructuredOutputBackend::Guidance);
77
+
78
+ let value = serde_json::to_value(params).unwrap();
79
+ assert_eq!(value["_backend"], "guidance");
80
+ }
81
+}
rust/src/engine-core-client/src/tests/client.rs
+5
-4
@@ -22,13 +22,14 @@ use crate::protocol::multimodal::{
22
MmFeatureSpec, MmField, MmFieldElem, MmFlatField, MmKwargValue, MmSlice, PlaceholderRange,
23
SliceSpec,
24
};
25
+use crate::protocol::output::{
26
+ EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, decode_engine_core_outputs,
27
+};
28
+use crate::protocol::request::{EngineCoreRequest, EngineCoreRequestType};
29
+use crate::protocol::sampling::EngineCoreSamplingParams;
30
use crate::protocol::stats::SchedulerStats;
31
use crate::protocol::tensor::WireTensor;
32
use crate::protocol::utility::{UtilityOutput, UtilityResultEnvelope};
28
-use crate::protocol::{
29
- EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, EngineCoreRequest,
30
- EngineCoreRequestType, EngineCoreSamplingParams, decode_engine_core_outputs,
31
-};
33
use crate::test_utils::{
34
IpcNamespace, setup_bootstrapped_mock_engine, setup_mock_engine_sockets,
35
setup_mock_engine_with_init, spawn_mock_engine_task,
rust/src/engine-core-client/src/transport.rs
+2
-3
@@ -18,9 +18,8 @@ use crate::error::{Error, Result, bail_unexpected_handshake_message};
18
use crate::protocol::handshake::{
19
EngineCoreReadyResponse, HandshakeAddresses, HandshakeInitMessage, ReadyMessage,
20
};
21
-use crate::protocol::{
22
- EngineCoreOutputs, decode_engine_core_outputs, decode_msgpack, encode_msgpack,
23
-};
21
+use crate::protocol::output::{EngineCoreOutputs, decode_engine_core_outputs};
22
+use crate::protocol::{decode_msgpack, encode_msgpack};
23
24
/// Dedicated single-frame sentinel emitted by Python `EngineCoreProc` when the
25
/// engine dies.
rust/src/llm/examples/external_engine_smoke.rs
+1
-1
@@ -5,7 +5,7 @@ use clap::Parser;
5
use futures::StreamExt as _;
6
use tokio::time::timeout;
7
use tracing_subscriber::EnvFilter;
8
-use vllm_engine_core_client::protocol::EngineCoreSamplingParams;
8
+use vllm_engine_core_client::protocol::sampling::EngineCoreSamplingParams;
9
use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig, TransportMode};
10
use vllm_llm::{FinishReason, GenerateOutputStream, GenerateRequest, Llm};
11
rust/src/llm/src/output.rs
+1
-1
@@ -8,7 +8,7 @@ use futures::stream::FusedStream;
8
use futures::{Stream, StreamExt as _, pin_mut};
9
use serde::{Deserialize, Serialize};
10
use vllm_engine_core_client::protocol::logprobs::Logprobs;
11
-use vllm_engine_core_client::protocol::{EngineCoreFinishReason, StopReason};
11
+use vllm_engine_core_client::protocol::output::{EngineCoreFinishReason, StopReason};
12
use vllm_engine_core_client::{AbortCause, EngineCoreOutputStream};
13
14
use crate::error::Result;
rust/src/llm/src/request.rs
+4
-4
@@ -4,9 +4,8 @@ use std::time::{SystemTime, UNIX_EPOCH};
4
use uuid::Uuid;
5
use vllm_engine_core_client::protocol::lora::LoraRequest;
6
use vllm_engine_core_client::protocol::multimodal::MmFeatures;
7
-use vllm_engine_core_client::protocol::{
8
- EngineCoreRequest, EngineCoreSamplingParams, ReasoningParserKwargs,
9
-};
7
+use vllm_engine_core_client::protocol::request::{EngineCoreRequest, ReasoningParserKwargs};
8
+use vllm_engine_core_client::protocol::sampling::EngineCoreSamplingParams;
9
10
use crate::error::{Error, Result};
11
@@ -134,7 +133,8 @@ fn current_unix_timestamp_secs() -> f64 {
133
mod tests {
134
use std::collections::BTreeMap;
135
137
- use vllm_engine_core_client::protocol::{EngineCoreSamplingParams, ReasoningParserKwargs};
136
+ use vllm_engine_core_client::protocol::request::ReasoningParserKwargs;
137
+ use vllm_engine_core_client::protocol::sampling::EngineCoreSamplingParams;
138
139
use super::GenerateRequest;
140
use crate::error::Error;
rust/src/llm/src/request_metrics.rs
+6
-4
@@ -1,7 +1,9 @@
1
use std::time::{SystemTime, UNIX_EPOCH};
2
3
+use vllm_engine_core_client::protocol::output::{
4
+ EngineCoreEvent, EngineCoreEventType, EngineCoreOutput,
5
+};
6
use vllm_engine_core_client::protocol::stats::PrefillStats;
4
-use vllm_engine_core_client::protocol::{EngineCoreEvent, EngineCoreEventType, EngineCoreOutput};
7
use vllm_metrics::{
8
EngineLabels, FinishedReasonLabels, METRICS, PromptTokenSourceLabels, RequestMetrics,
9
};
@@ -328,8 +330,8 @@ pub(crate) fn current_unix_timestamp_secs() -> f64 {
330
331
#[cfg(test)]
332
mod tests {
333
+ use vllm_engine_core_client::protocol::output::{EngineCoreEvent, EngineCoreEventType};
334
use vllm_engine_core_client::protocol::stats::PrefillStats;
332
- use vllm_engine_core_client::protocol::{EngineCoreEvent, EngineCoreEventType};
335
336
use super::{RequestMetricsTracker, diff_or_zero};
337
@@ -341,7 +343,7 @@ mod tests {
343
2,
344
10.0,
345
100.2,
344
- &vllm_engine_core_client::protocol::EngineCoreOutput {
346
+ &vllm_engine_core_client::protocol::output::EngineCoreOutput {
347
request_id: "req-1".to_string(),
348
new_token_ids: vec![1],
349
finish_reason: None,
@@ -369,7 +371,7 @@ mod tests {
371
2,
372
11.5,
373
100.4,
372
- &vllm_engine_core_client::protocol::EngineCoreOutput {
374
+ &vllm_engine_core_client::protocol::output::EngineCoreOutput {
375
request_id: "req-1".to_string(),
376
new_token_ids: vec![2, 3],
377
finish_reason: None,
rust/src/llm/tests/generate.rs
+5
-3
@@ -9,11 +9,13 @@ use uuid::Uuid;
9
use vllm_engine_core_client::protocol::logprobs::{
10
Logprobs, MaybeWireLogprobs, PositionLogprobs, TokenLogprob,
11
};
12
-use vllm_engine_core_client::protocol::stats::PrefillStats;
13
-use vllm_engine_core_client::protocol::{
12
+use vllm_engine_core_client::protocol::output::{
13
EngineCoreEvent, EngineCoreEventType, EngineCoreFinishReason, EngineCoreOutput,
15
- EngineCoreOutputs, EngineCoreRequest, EngineCoreSamplingParams,
14
+ EngineCoreOutputs,
15
};
16
+use vllm_engine_core_client::protocol::request::EngineCoreRequest;
17
+use vllm_engine_core_client::protocol::sampling::EngineCoreSamplingParams;
18
+use vllm_engine_core_client::protocol::stats::PrefillStats;
19
use vllm_engine_core_client::test_utils::{IpcNamespace, spawn_mock_engine_task};
20
use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig};
21
use vllm_llm::{
rust/src/mock-engine/src/engine.rs
+4
-3
@@ -11,12 +11,13 @@ use tokio::sync::mpsc;
11
use tokio::task::yield_now;
12
use tokio_util::sync::CancellationToken;
13
use tracing::{debug, info, warn};
14
+use vllm_engine_core_client::protocol::output::{
15
+ EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs,
16
+};
17
+use vllm_engine_core_client::protocol::request::EngineCoreRequest;
18
use vllm_engine_core_client::protocol::utility::{
19
EngineCoreUtilityRequest, UtilityOutput, UtilityResultEnvelope,
20
};
17
-use vllm_engine_core_client::protocol::{
18
- EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, EngineCoreRequest,
19
-};
21
22
use super::Opt;
23
rust/src/mock-engine/src/io.rs
+2
-3
@@ -4,10 +4,9 @@ use tokio::sync::mpsc;
4
use tokio_util::sync::CancellationToken;
5
use tracing::warn;
6
use vllm_engine_core_client::mock_engine::MockEngineDataSockets;
7
+use vllm_engine_core_client::protocol::request::{EngineCoreRequest, EngineCoreRequestType};
8
use vllm_engine_core_client::protocol::utility::EngineCoreUtilityRequest;
8
-use vllm_engine_core_client::protocol::{
9
- EngineCoreRequest, EngineCoreRequestType, decode_msgpack, encode_msgpack,
10
-};
9
+use vllm_engine_core_client::protocol::{decode_msgpack, encode_msgpack};
10
use zeromq::{DealerSocket, PushSocket, SocketRecv as _, SocketSend as _, ZmqMessage};
11
12
use crate::engine::{EngineInput, EngineOutput};
rust/src/mock-engine/src/tests.rs
+3
-3
@@ -5,9 +5,9 @@ use anyhow::Result;
5
use futures::StreamExt as _;
6
use tokio::time::timeout;
7
use tokio_util::sync::CancellationToken;
8
-use vllm_engine_core_client::protocol::{
9
- EngineCoreFinishReason, EngineCoreRequest, EngineCoreSamplingParams,
10
-};
8
+use vllm_engine_core_client::protocol::output::EngineCoreFinishReason;
9
+use vllm_engine_core_client::protocol::request::EngineCoreRequest;
10
+use vllm_engine_core_client::protocol::sampling::EngineCoreSamplingParams;
11
use vllm_engine_core_client::test_utils::IpcNamespace;
12
use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig, TransportMode};
13
rust/src/parser/benches/utils/mod.rs
+1
-2
@@ -5,14 +5,13 @@
5
6
mod adapter;
7
8
+pub(super) use adapter::UnifiedToolParserAdapter;
9
use futures::FutureExt as _;
10
use openai_protocol::common::{Function as OpenAiFunction, Tool as OpenAiTool};
11
use tool_parser::traits::ToolParser as ExternalToolParser;
12
use vllm_parser::tool::test_utils::collect_stream;
13
use vllm_parser::tool::{Tool, ToolParser};
14
14
-pub(super) use adapter::UnifiedToolParserAdapter;
15
-
15
pub(super) fn openai_tools(tools: &[Tool]) -> Vec<OpenAiTool> {
16
tools
17
.iter()
rust/src/parser/python/src/lib.rs
+2
-1
@@ -231,9 +231,10 @@ fn _rust_tool_parser(m: &Bound<'_, PyModule>) -> PyResult<()> {
231
232
#[cfg(test)]
233
mod tests {
234
- use super::*;
234
use serde_json::json;
235
236
+ use super::*;
237
+
238
fn with_python<R>(f: impl for<'py> FnOnce(Python<'py>) -> R) -> R {
239
Python::initialize();
240
Python::attach(f)
rust/src/parser/src/reasoning/seed_oss.rs
+2
-4
@@ -49,10 +49,8 @@ mod tests {
49
use std::sync::Arc;
50
51
use super::SeedOssReasoningParser;
52
- use crate::reasoning::{
53
- ReasoningParser,
54
- tests::{SEED_THINK_END_ID, SEED_THINK_START_ID, fake_tokenizer},
55
- };
52
+ use crate::reasoning::ReasoningParser;
53
+ use crate::reasoning::tests::{SEED_THINK_END_ID, SEED_THINK_START_ID, fake_tokenizer};
54
55
#[test]
56
fn without_prompt_markers_expects_start_token() {
rust/src/parser/src/reasoning/step3p5.rs
+2
-4
@@ -127,10 +127,8 @@ mod tests {
127
use std::sync::Arc;
128
129
use super::Step3p5ReasoningParser;
130
- use crate::reasoning::{
131
- ReasoningParser,
132
- tests::{THINK_START_ID, fake_tokenizer},
133
- };
130
+ use crate::reasoning::ReasoningParser;
131
+ use crate::reasoning::tests::{THINK_START_ID, fake_tokenizer};
132
133
#[test]
134
fn picks_up_prompt_start_boundary() {
rust/src/parser/src/tool/mod.rs
+2
-2
@@ -14,8 +14,6 @@ mod parameters;
14
mod qwen_coder;
15
#[cfg(any(test, feature = "test-util"))]
16
pub mod test_utils;
17
-use crate::utils;
18
-
17
use std::collections::{BTreeMap, btree_map};
18
19
pub use deepseek_dsml::{DeepSeekV4ToolParser, DeepSeekV32ToolParser};
@@ -35,6 +33,8 @@ use serde::{Deserialize, Serialize};
33
use serde_json::Value;
34
pub use xgrammar_structural_tag::Model as StructuralTagModel;
35
36
+use crate::utils;
37
+
38
/// One function-style tool made available to the model.
39
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40
pub struct Tool {
rust/src/parser/src/unified/combined.rs
+1
-2
@@ -2,11 +2,10 @@
2
3
use vllm_tokenizer::DynTokenizer;
4
5
+use super::{Result, UnifiedParser, UnifiedParserError, UnifiedParserOutput};
6
use crate::reasoning::ReasoningParser;
7
use crate::tool::{StructuralTagModel, Tool, ToolParser, ToolParserOutput};
8
8
-use super::{Result, UnifiedParser, UnifiedParserError, UnifiedParserOutput};
9
-
9
/// Unified parser that composes existing reasoning and tool parsers.
10
pub struct CombinedParser {
11
reasoning: Option<Box<dyn ReasoningParser>>,
rust/src/parser/src/unified/gemma4.rs
+1
-2
@@ -1,4 +1,5 @@
1
use serde_json::{Map, Number, Value};
2
+use vllm_tokenizer::DynTokenizer;
3
use winnow::ascii::multispace0 as ws0;
4
use winnow::combinator::{alt, delimited, eof, opt, separated, seq, terminated};
5
use winnow::error::{ContextError, ErrMode, ModalResult};
@@ -6,8 +7,6 @@ use winnow::prelude::*;
7
use winnow::stream::{Partial, Stream};
8
use winnow::token::{literal, take_till, take_until};
9
9
-use vllm_tokenizer::DynTokenizer;
10
-
10
use super::{Result, UnifiedParser, UnifiedParserError, UnifiedParserOutput};
11
use crate::reasoning::last_reasoning_boundary;
12
use crate::tool::{Tool, ToolCallDelta};
rust/src/parser/src/unified/mod.rs
+2
-3
@@ -3,13 +3,12 @@
3
mod combined;
4
mod gemma4;
5
6
+pub use combined::CombinedParser;
7
+pub use gemma4::Gemma4UnifiedParser;
8
use thiserror::Error;
9
use thiserror_ext::Macro;
10
use vllm_tokenizer::DynTokenizer;
11
10
-pub use combined::CombinedParser;
11
-pub use gemma4::Gemma4UnifiedParser;
12
-
12
use crate::reasoning::ReasoningError;
13
use crate::tool::{
14
StructuralTagModel, Tool, ToolCallDelta, ToolParserError, ToolParserEvent, ToolParserOutput,
rust/src/server/src/error.rs
+1
-2
@@ -1,8 +1,7 @@
1
use axum::Json;
2
use axum::http::StatusCode;
3
use axum::response::{IntoResponse, Response};
4
-use thiserror_ext::AsReport as _;
5
-use thiserror_ext::{Construct, Macro};
4
+use thiserror_ext::{AsReport as _, Construct, Macro};
5
6
use crate::routes::openai::utils::types::{ErrorDetail, ErrorResponse};
7
rust/src/server/src/grpc/convert.rs
+3
-2
@@ -3,7 +3,8 @@
3
4
use tonic::Status;
5
use uuid::Uuid;
6
-use vllm_engine_core_client::protocol::{StopReason, StructuredOutputsParams};
6
+use vllm_engine_core_client::protocol::output::StopReason;
7
+use vllm_engine_core_client::protocol::structured_outputs::StructuredOutputsParams;
8
use vllm_text::{
9
DecodedLogprobs, DecodedPromptLogprobs, FinishReason, Finished, Prompt, SamplingParams,
10
TextDecodeOptions, TextRequest,
@@ -502,7 +503,7 @@ impl ResponseOpts {
503
504
#[cfg(test)]
505
mod tests {
505
- use vllm_engine_core_client::protocol::StopReason;
506
+ use vllm_engine_core_client::protocol::output::StopReason;
507
use vllm_text::{FinishReason, Finished, Prompt};
508
509
use super::pb::finish_info::{FinishReason as PbFinishReason, StopReason as PbStopReason};
rust/src/server/src/grpc/tests.rs
+3
-2
@@ -18,9 +18,10 @@ use vllm_chat::{
18
ChatBackend, ChatLlm, ChatRenderer, ChatRequest, ChatTextBackend, DefaultChatOutputProcessor,
19
DynChatOutputProcessor, DynChatRenderer, NewChatOutputProcessorOptions, RenderedPrompt,
20
};
21
-use vllm_engine_core_client::protocol::{
22
- EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, EngineCoreRequest,
21
+use vllm_engine_core_client::protocol::output::{
22
+ EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs,
23
};
24
+use vllm_engine_core_client::protocol::request::EngineCoreRequest;
25
use vllm_engine_core_client::test_utils::{IpcNamespace, spawn_mock_engine_task};
26
use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig, EngineId};
27
use vllm_llm::Llm;
rust/src/server/src/listener.rs
+1
-1
@@ -152,8 +152,8 @@ impl axum::serve::Listener for Listener {
152
153
/// Allow the unified listener to be adaptable to `tls_listener`.
154
impl AsyncAccept for Listener {
155
- type Connection = ListenerIo;
155
type Address = ListenerAddr;
156
+ type Connection = ListenerIo;
157
type Error = std::io::Error;
158
159
fn poll_accept(
rust/src/server/src/middleware/offload.rs
+1
-1
@@ -57,9 +57,9 @@ where
57
S::Error: Send + 'static,
58
B: Send + 'static,
59
{
60
- type Response = S::Response;
60
type Error = S::Error;
61
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
62
+ type Response = S::Response;
63
64
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
65
self.inner.poll_ready(cx)
rust/src/server/src/routes/http_client_tests.rs
+3
-2
@@ -18,9 +18,10 @@ use vllm_chat::{
18
ChatBackend, ChatLlm, ChatRenderer, ChatRequest, ChatTextBackend, DefaultChatOutputProcessor,
19
DynChatOutputProcessor, DynChatRenderer, NewChatOutputProcessorOptions, RenderedPrompt,
20
};
21
-use vllm_engine_core_client::protocol::{
22
- EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, EngineCoreRequest,
21
+use vllm_engine_core_client::protocol::output::{
22
+ EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs,
23
};
24
+use vllm_engine_core_client::protocol::request::EngineCoreRequest;
25
use vllm_engine_core_client::test_utils::{IpcNamespace, spawn_mock_engine_task};
26
use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig, EngineId};
27
use vllm_llm::Llm;
rust/src/server/src/routes/openai/chat_completions.rs
+2
-2
@@ -21,7 +21,7 @@ use vllm_chat::{
21
AssistantBlockKind, AssistantMessageExt as _, ChatEvent, ChatEventStream, ChatEventStreamTrait,
22
CollectedAssistantMessage, FinishReason,
23
};
24
-use vllm_engine_core_client::protocol::StopReason;
24
+use vllm_engine_core_client::protocol::output::StopReason;
25
26
use self::convert::{ResponseOptions, prepare_chat_request};
27
use crate::config::ApiServerOptions;
@@ -825,7 +825,7 @@ mod tests {
825
use vllm_chat::{
826
AssistantBlockKind, AssistantContentBlock, AssistantToolCall, ChatEvent, FinishReason,
827
};
828
- use vllm_engine_core_client::protocol::StopReason;
828
+ use vllm_engine_core_client::protocol::output::StopReason;
829
use vllm_text::{DecodedLogprobs, DecodedPositionLogprobs, DecodedTokenLogprob};
830
831
use super::{
rust/src/server/src/routes/openai/utils/structured_outputs.rs
+1
-1
@@ -1,6 +1,6 @@
1
use serde::{Deserialize, Serialize};
2
use serde_json::Value;
3
-use vllm_engine_core_client::protocol::StructuredOutputsParams;
3
+use vllm_engine_core_client::protocol::structured_outputs::StructuredOutputsParams;
4
5
use crate::error::ApiError;
6
rust/src/server/src/routes/tests.rs
+5
-4
@@ -24,14 +24,15 @@ use vllm_chat::{
24
NewChatOutputProcessorOptions,
25
};
26
use vllm_engine_core_client::mock_engine::default_ready_response;
27
+use vllm_engine_core_client::protocol::decode_value;
28
use vllm_engine_core_client::protocol::logprobs::{
29
Logprobs, MaybeWireLogprobs, PositionLogprobs, TokenLogprob,
30
};
30
-use vllm_engine_core_client::protocol::utility::{UtilityOutput, UtilityResultEnvelope};
31
-use vllm_engine_core_client::protocol::{
32
- EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, EngineCoreRequest, StopReason,
33
- decode_value,
31
+use vllm_engine_core_client::protocol::output::{
32
+ EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, StopReason,
33
};
34
+use vllm_engine_core_client::protocol::request::EngineCoreRequest;
35
+use vllm_engine_core_client::protocol::utility::{UtilityOutput, UtilityResultEnvelope};
36
use vllm_engine_core_client::test_utils::{
37
IpcNamespace, spawn_mock_engine_task, spawn_mock_engine_task_with_ready,
38
};
rust/src/server/src/state.rs
+1
-2
@@ -1,6 +1,5 @@
1
-use std::sync::Arc;
2
-use std::sync::OnceLock;
1
use std::sync::atomic::{AtomicU64, Ordering};
2
+use std::sync::{Arc, OnceLock};
3
4
use serde_json::Value;
5
use sha2::{Digest, Sha256};
rust/src/text/src/lower.rs
+3
-3
@@ -3,15 +3,15 @@ use std::collections::BTreeSet;
3
pub(crate) mod logprobs;
4
pub(crate) mod token_ids;
5
6
-use vllm_engine_core_client::protocol::EngineCoreSamplingParams;
6
+use logprobs::validate_logprobs;
7
+use token_ids::{validate_prompt_token_ids, validate_vocab_range};
8
+use vllm_engine_core_client::protocol::sampling::EngineCoreSamplingParams;
9
use vllm_llm::GenerateRequest;
10
use vllm_tokenizer::Tokenizer;
11
12
use crate::backend::{SamplingHints, SamplingLimits};
13
use crate::error::{Error, Result};
14
use crate::request::{SamplingParams, TextRequest};
13
-use logprobs::validate_logprobs;
14
-use token_ids::{validate_prompt_token_ids, validate_vocab_range};
15
16
/// One text request after it has been lowered into the raw generate boundary.
17
#[derive(Debug)]
rust/src/text/src/lower/logprobs.rs
+2
-1
@@ -3,9 +3,10 @@
3
//! `-1` is expanded only for bounds checks. The original request values are
4
//! passed through to engine-core.
5
6
-use crate::backend::SamplingLimits;
6
use thiserror::Error;
7
8
+use crate::backend::SamplingLimits;
9
+
10
#[derive(Debug, Error)]
11
pub enum LogprobsError {
12
#[error("{parameter} must be non-negative or -1, got {value}")]
rust/src/text/src/lower/token_ids.rs
+1
-1
@@ -1,7 +1,7 @@
1
use std::result::Result;
2
3
use thiserror::Error;
4
-use vllm_engine_core_client::protocol::EngineCoreSamplingParams;
4
+use vllm_engine_core_client::protocol::sampling::EngineCoreSamplingParams;
5
6
use crate::SamplingLimits;
7
rust/src/text/src/output/decoded.rs
+1
-1
@@ -5,7 +5,7 @@ use futures::{Stream, StreamExt};
5
use serde::{Deserialize, Serialize};
6
use tracing::{Level, debug, trace};
7
use vllm_engine_core_client::AbortCause;
8
-use vllm_engine_core_client::protocol::StopReason;
8
+use vllm_engine_core_client::protocol::output::StopReason;
9
use vllm_llm::{FinishReason, GenerateOutput, TokenUsage};
10
use vllm_tokenizer::{DynTokenizer, IncrementalDecoder};
11
rust/src/text/src/request.rs
+2
-1
@@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize};
5
use serde_json::Value;
6
use vllm_engine_core_client::protocol::lora::LoraRequest;
7
use vllm_engine_core_client::protocol::multimodal::MmFeatures;
8
-use vllm_engine_core_client::protocol::{ReasoningParserKwargs, StructuredOutputsParams};
8
+use vllm_engine_core_client::protocol::request::ReasoningParserKwargs;
9
+use vllm_engine_core_client::protocol::structured_outputs::StructuredOutputsParams;
10
11
use crate::error::{Error, Result};
12
use crate::output::TextDecodeOptions;
rust/src/tokenizer/src/hf/added_tokens.rs
+3
-2
@@ -1,11 +1,12 @@
1
+use std::fs;
2
+use std::path::Path;
3
+
4
use serde::{Deserialize, Serialize};
5
use thiserror_ext::AsReport as _;
6
use tracing::warn;
7
8
use crate::Result;
9
7
-use std::{fs, path::Path};
8
-
10
/// Minimal `tokenizer.json` projection used to patch `added_tokens` while
11
/// preserving the rest of the tokenizer definition verbatim.
12
#[derive(Debug, Deserialize, Serialize)]