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

  1. Set exactly one constraint field (json, regex, choice, grammar, structural_tag, or json_object: true)
  2. To disable structured outputs, omit structured_outputs entirely — do not send json_object: false or an empty object
  3. Strip null constraint fields at the frontend before building the request
  4. 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

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


AI-assisted analysis of vllm-project/vllm@c794754062 (2026-08-14). Data as JSON: /api/errors/8d774058bdfba7da. Report an issue: GitHub.