vllm-project/vllm · error · Error
invalid structured outputs params: {message}
Error message
invalid structured outputs params: {message} What it means
Error::InvalidStructuredOutputsParams is returned by TryFrom<WireStructuredOutputsParams> for StructuredOutputsParams (rust/src/engine-core-client/src/protocol/structured_outputs.rs:150-183). It enforces that exactly one structured-output constraint is present: the insert_constraint! macro rejects a second constraint, json_object must be true if set, and a params object with no constraint at all is rejected ('missing structured output constraint').
Source
Thrown at rust/src/engine-core-client/src/error.rs:31
/// Public error type for the Rust engine-core client.
#[derive(Debug, Error, Macro)]
pub enum Error {
#[error("messagepack encode failed for {target_type}: {message}")]
Encode {
target_type: &'static str,
message: String,
},
#[error("messagepack decode failed for {target_type}: {message}")]
Decode {
target_type: &'static str,
message: String,
},
#[error("messagepack value decode failed")]
ValueDecode(#[from] rmpv::decode::Error),
#[error("messagepack ext value decode failed: {message}")]
ExtValueDecode { message: String },
#[error("invalid structured outputs params: {message}")]
InvalidStructuredOutputsParams { message: String },
#[error("io error")]
Io(#[from] std::io::Error),
#[error("transport error")]
Transport(#[from] zeromq::ZmqError),
#[error("ZMQ runtime task failed")]
ZmqRuntimeTask(#[from] tokio::task::JoinError),
#[error("engine core reported fatal failure")]
EngineCoreDead,
#[error("startup handshake timed out while waiting for {stage} after {timeout:?}")]
HandshakeTimeout {
stage: &'static str,
timeout: Duration,
},
#[error("engine input registration timed out after {timeout:?}")]
InputRegistrationTimeout { timeout: Duration },
#[error("unexpected engine id in startup handshake: expected {expected:?}, got {actual:?}")]
UnexpectedHandshakeIdentity { expected: Vec<u8>, actual: Vec<u8> },View on GitHub (pinned to c794754062)
Solutions
- Set exactly one constraint field (json, regex, choice, grammar, structural_tag, or json_object: true)
- To disable structured outputs, omit structured_outputs entirely — do not send json_object: false or an empty object
- Strip null constraint fields at the frontend before building the request
- Validate params before submit: StructuredOutputsParams::try_from(wire)? in a pre-flight check
Example fix
// before
structured_outputs: Some(WireStructuredOutputsParams {
json: Some(schema),
regex: Some("[0-9]+".into()),
..Default::default()
}),
// after
structured_outputs: Some(WireStructuredOutputsParams {
json: Some(schema),
..Default::default()
}), Defensive patterns
Strategy: validation
Validate before calling
fn exactly_one_constraint(p: &WireStructuredOutputsParams) -> bool {
let n = p.json.is_some() as u8 + p.regex.is_some() as u8 + p.choice.is_some() as u8
+ p.grammar.is_some() as u8 + p.structural_tag.is_some() as u8
+ p.json_object.map_or(0, |b| b as u8);
n == 1 && p.json_object != Some(false)
} Type guard
fn is_invalid_structured_outputs(e: &engine_core_client::Error) -> bool {
matches!(e, engine_core_client::Error::InvalidStructuredOutputsParams { .. })
} Try / catch
match StructuredOutputsParams::try_from(wire) {
Err(e @ engine_core_client::Error::InvalidStructuredOutputsParams { message }) => {
return bad_request(400, message); // surface to the API caller, do not retry
}
other => other?,
} Prevention
- Normalize guided_* params at the HTTP layer: pick one constraint by precedence, null the rest
- Omit structured_outputs entirely when unused; never send json_object: false as an off switch
- Run TryFrom conversion at request admission, not deep in the send path
When it happens
Trigger: Building a request whose structured_outputs params contain: (a) two or more of json/regex/choice/grammar/json_object/structural_tag (message 'multiple structured output constraints specified: X, Y'), (b) json_object: false (must be omitted to disable), or (c) an options-only object with no constraint field ('missing structured output constraint').
Common situations: Translating OpenAI-style guided_decoding config where e.g. guided_json and guided_regex were both populated. Passing structured_outputs: {} as a 'disabled' marker instead of omitting the field. Frontends copying the whole sampling params object into every request.
Related errors
- unsupported field `{field}` in {context}
- this model's maximum context length is {max_model_len} token
- duplicate tool name `{name}`
- tool_choice requires at least one available tool
- tool_choice function `{name}` was not found in the available
AI-assisted analysis of vllm-project/vllm@c794754062 (2026-08-14).
Data as JSON: /api/errors/8d774058bdfba7da.
Report an issue: GitHub.