BoundaryML/baml · error

Key must be a string

Error message

Key must be a string

What it means

Thrown inside BamlMethodArguments::decode while converting the method's kwargs: a HostMapEntry key must be a StringKey, and any other variant (int/bool/enum/unset) is rejected with this fixed message. Method kwargs are String-keyed by contract.

Source

Thrown at engine/language_client_cffi/src/ctypes/object_args_decode.rs:34

impl Decode for BamlMethodArguments {
    type From = crate::baml::cffi::BamlObjectMethodInvocation;

    fn decode(from: Self::From) -> Result<Self, anyhow::Error> {
        Ok(BamlMethodArguments {
            object: match from.object.map(RawPtrType::decode).transpose()? {
                Some(object) => object,
                None => {
                    return Err(anyhow::anyhow!("Failed to decode RawPtrType for object"));
                }
            },
            method_name: from.method_name,
            kwargs: from
                .kwargs
                .into_iter()
                .map(|v| {
                    let key = match v.key {
                        Some(crate::baml::cffi::host_map_entry::Key::StringKey(k)) => k,
                        _ => return Err(anyhow::anyhow!("Key must be a string")),
                    };
                    match v.value {
                        Some(value) => Ok((key, Value::decode(value)?)),
                        None => Err(anyhow::anyhow!("Failed to decode BamlValue")),
                    }
                })
                .collect::<Result<_, _>>()?,
        })
    }
}

impl Decode for BamlObjectConstructorArgs {
    type From = crate::baml::cffi::BamlObjectConstructorInvocation;

    fn decode(from: Self::From) -> Result<Self, anyhow::Error> {
        Ok(BamlObjectConstructorArgs {
            object_type: BamlObjectType::try_from(from.r#type)?,
            kwargs: from

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Coerce all kwargs keys to strings before calling the method (e.g. {str(k): v for k, v in kwargs.items()})
  2. Fix data sources (JSON parsing, DB rows) that produce non-string keys and normalize at construction time
  3. Add a host-side assertion that kwargs keys are strings before crossing the FFI boundary

Example fix

// before (Python)
kwargs = {42: "answer"}

// after
kwargs = {str(k): v for k, v in kwargs.items()}
Defensive patterns

Strategy: validation

Validate before calling

def normalize_kwargs(kwargs):
    if not all(isinstance(k, str) for k in kwargs):
        raise TypeError("Method kwargs keys must be strings")
    return {str(k): v for k, v in kwargs.items()}

kwargs = normalize_kwargs(raw_kwargs)

Type guard

def has_string_keys(d: dict) -> bool:
    return all(isinstance(k, str) for k in d)

Try / catch

try:
    result = obj.CallMethod(method, kwargs)
except Exception as e:
    if "Key must be a string" in str(e):
        raise TypeError("Stringify all kwargs keys before the FFI call") from e
    raise

Prevention

When it happens

Trigger: Passing a kwargs entry with a non-string key (e.g. numeric or boolean keys in the host dict) to a BAML object method call.

Common situations: Python dicts keyed by ints, JS objects produced from numeric-key JSON, or generated code building kwargs programmatically with non-string keys.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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