BoundaryML/baml · error · LlmOpError

SAP error

Error message

SAP error: {0}

What it means

LlmOpError::SapError wraps a bex_sap::deserializer::coercer::ParsingError from the SAP deserializer coercer — the stage that coerces a parsed JSON-ish value into the target schema type. It indicates the value exists but cannot be coerced to the required shape.

Solutions

  1. Read the wrapped ParsingError to find the failing field/coercion
  2. Align the prompt's output schema with the expected BAML class exactly
  3. Make the schema fields optional where the model legitimately may omit them
  4. Retry the generation if the omission looks stochastic

Example fix

// before
class Result { score int }
// after
class Result { score int? }
Defensive patterns

Strategy: try-catch

Validate before calling

fn has_required_fields(v: &serde_json::Value, required: &[&str]) -> bool {
    required.iter().all(|k| v.get(k).is_some())
}

Try / catch

match result {
    Err(LlmOpError::SapError(pe)) => { eprintln!("coercion failed at {:?}", pe); adjust_prompt_and_retry() }
    other => other,
}

Prevention

When it happens

Trigger: The coercer fails while deserializing a jsonish value into the expected output class (missing required field, uncoercible value, wrong field types); the error is wrapped as SapError and later converted to a VmRustFnError/VmBamlError.

Common situations: Model omits a required field or invents extra ones; values are close-but-wrong types the coercer won't cast; schema changed after the prompt was written.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/d15d8c1821b60649. Report an issue: GitHub.

Appendix: source

Thrown at baml_language/crates/sys_ops/src/sap.rs:64

/// Errors that can occur during LLM operations. Relocated verbatim from
/// `sys_llm`; only `ParseResponseError`, `JsonishError` and `SapError` are
/// still constructible now that the SAP parse entry points are the sole users.
#[derive(Debug, thiserror::Error)]
pub enum LlmOpError {
    #[error("Expected {expected}, got {actual}")]
    TypeError {
        expected: &'static str,
        actual: String,
    },

    #[error("Parse response error: {0}")]
    ParseResponseError(String),

    #[error("Jsonish error: {0}")]
    JsonishError(::bex_sap::jsonish::JsonishError),

    #[error("SAP error: {0}")]
    SapError(::bex_sap::deserializer::coercer::ParsingError),
}

impl From<LlmOpError> for ::sys_types::VmRustFnError {
    fn from(e: LlmOpError) -> Self {
        let baml: ::sys_types::VmBamlError = match e {
            LlmOpError::TypeError { expected, actual } => {
                ::sys_types::VmBamlError::InvalidArgument {
                    message: format!("expected {expected}, got {actual}"),
                }
            }
            LlmOpError::ParseResponseError(e) => ::sys_types::VmBamlError::LlmClient { message: e },
            LlmOpError::JsonishError(e) => ::sys_types::VmBamlError::LlmClient {
                message: e.to_string(),
            },
            LlmOpError::SapError(e) => ::sys_types::VmBamlError::LlmClient {
                message: e.to_string(),
            },

View on GitHub (pinned to bd85ce9dee)