vllm-project/vllm · error · UnifiedParserError

combined parser is constructed from split parser instances

Error message

combined parser is constructed from split parser instances

What it means

UnifiedParserError::CombinedParserConstructor is an API-misuse guard: a combined (unified) parser must be constructed through its own constructor that takes the tokenizer and shared configuration, not assembled from already-built split reasoning/tool parser instances. Building it from split instances would leave token IDs and delimiter state inconsistent, so the constructor rejects that path.

Source

Thrown at rust/src/parser/src/unified/mod.rs:204

    /// Feed one decoded text delta into the parser, appending committed output into `output`.
    fn parse_into(&mut self, delta: &str, output: &mut UnifiedParserOutput) -> Result<()>;

    /// Flush any buffered parser state at end of stream.
    fn finish(&mut self) -> Result<UnifiedParserOutput> {
        Ok(UnifiedParserOutput::default())
    }

    /// Clear parser state and return currently uncommitted buffered text.
    fn reset(&mut self) -> String {
        String::new()
    }
}

/// Errors produced while creating or running unified parsers.
#[derive(Debug, Error, Macro)]
#[thiserror_ext(macro(path = "crate::unified", mangle))]
pub enum UnifiedParserError {
    #[error("combined parser is constructed from split parser instances")]
    CombinedParserConstructor,
    #[error("tokenizer is missing unified parser token `{token}`")]
    MissingToken { token: String },
    #[error("unified parser parsing failed: {message}")]
    ParsingFailed { message: String },
    #[error(transparent)]
    Reasoning(#[from] ReasoningError),
    #[error(transparent)]
    Tool(#[from] ToolParserError),
}

/// Returns the ID for the given token, or an error if it's not found.
fn token_id(tokenizer: &dyn vllm_tokenizer::Tokenizer, token: &str) -> Result<u32> {
    tokenizer.token_to_id(token).ok_or_else(|| UnifiedParserError::MissingToken {
        token: token.to_string(),
    })
}

View on GitHub (pinned to c794754062)

Solutions

  1. Construct the unified parser via its dedicated constructor taking tokenizer + names/config, not parser instances
  2. If you already have split parsers, keep using the split (non-unified) pipeline instead of combining them

Example fix

// before
let p = CombinedParser::from_split(reasoning_parser, tool_parser);

// after
let p = CombinedParser::new(&tokenizer, UnifiedParserConfig::new("granite"));
Defensive patterns

Strategy: type-guard

Validate before calling

// Prevent constructing the combined parser from split instances
fn wants_unified(cfg: &ParserConfig) -> bool { cfg.reasoning == cfg.tool && !cfg.reasoning.is_empty() }
if wants_unified(&cfg) { build_unified(&cfg).await } else { build_split(&cfg).await }

Type guard

fn is_combined_ctor_misuse(e: &UnifiedParserError) -> bool {
    matches!(e, UnifiedParserError::CombinedParserConstructor)
}

Try / catch

match CombinedParser::new(args) {
    Err(e @ UnifiedParserError::CombinedParserConstructor) => {
        return Err(anyhow::anyhow!("use the unified constructor with tokenizer + config, not split instances: {e}"));
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling the combined-parser constructor with arguments that resolve to split parser instances (e.g. passing pre-built ReasoningParser/ToolParser trait objects) instead of letting it construct the unified components itself.

Common situations: Library users refactoring from split parsers to the unified API and passing their old parser objects; generic code that funnels any parser pair into the combined constructor.

Related errors


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