BoundaryML/baml · error

Expected string value for tag key {}

Error message

Expected string value for tag key {}

What it means

Thrown while decoding the tags field of BamlFunctionArguments: each tag entry is decoded via from_host_kv_to_baml_kv and must carry a BamlValue::String. If the decoded value is any other BamlValue variant (int, bool, map, etc.), the decode fails naming the offending tag key.

Source

Thrown at engine/language_client_cffi/src/ctypes/function_args_decode.rs:72

            .type_builder
            .map(RawPtrType::decode)
            .transpose()?
            .map(|r| match r {
                RawPtrType::TypeBuilder(t) => Ok(t),
                other => Err(anyhow::anyhow!(
                    "Expected TypeBuilder, got {}",
                    other.name()
                )),
            })
            .transpose()?;

        let tags = from
            .tags
            .into_iter()
            .map(|v| {
                from_host_kv_to_baml_kv(v).and_then(|(k, v)| match v {
                    BamlValue::String(s) => Ok((k, s)),
                    _ => anyhow::bail!("Expected string value for tag key {}", k),
                })
            })
            .collect::<Result<_, _>>()?;

        Ok(BamlFunctionArguments {
            kwargs,
            client_registry,
            env_vars,
            collectors,
            type_builder,
            tags,
        })
    }
}

impl Decode for ClientRegistry {
    type From = crate::baml::cffi::HostClientRegistry;

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Convert all tag values to strings on the host side before passing them (e.g. str(value) or String(value))
  2. If richer values are needed, flatten them into multiple string tags (e.g. "attempt": "3")
  3. Add a host-side validator for the tags map that rejects non-string values with a clearer message

Example fix

// before (Python)
tags = {"attempt": 3}

// after
tags = {"attempt": str(3)}  # or "3"
Defensive patterns

Strategy: validation

Validate before calling

def validate_tags(tags):
    bad = {k: type(v).__name__ for k, v in tags.items() if not isinstance(v, str)}
    if bad:
        raise TypeError(f"Tag values must be strings, fix: {bad}")
    return {k: str(v) for k, v in tags.items()}

tags = validate_tags(tags)

Type guard

def all_string_values(d: dict) -> bool:
    return all(isinstance(v, str) for v in d.values())

Try / catch

try:
    result = client.CallFunction(fn, args)
except Exception as e:
    if "Expected string value for tag key" in str(e):
        raise TypeError("Coerce tag values to strings, e.g. {'attempt': '3'}") from e
    raise

Prevention

When it happens

Trigger: Passing non-string tag values (numbers, booleans, objects) in the tags argument of a BAML function call, e.g. tags={"attempt": 3} instead of {"attempt": "3"}.

Common situations: Developers attaching observability/metadata tags with natural numeric or boolean values, or templating that substitutes unquoted values into tag maps.

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/2f1751c7737516c0. Report an issue: GitHub.