BoundaryML/baml · error · minijinja::Error
CannotUnpack
CannotUnpack
Error message
Media variable had unrecognizable data: {media_data} What it means
BAML passes media (images/audio/etc.) through chat templates using special `:baml-media:`-prefixed, `:baml-end-media:`-suffixed string markers. When splitting message parts, each media marker's payload must parse as a BamlMedia JSON object; if serde_json::from_str::<BamlMedia> fails, the raw payload cannot be interpreted as media and this CannotUnpack error is thrown.
Source
Thrown at engine/baml-lib/jinja-runtime/src/lib.rs:443
}
}
} else if role.is_none() && chunk.is_empty() {
// If there's only whitespace before the first `_.chat()` directive, we discard that chunk
} else {
let mut parts = vec![];
for part in chunk.split(MAGIC_MEDIA_DELIMITER) {
let part = if part.starts_with(":baml-start-media:")
&& part.ends_with(":baml-end-media:")
{
let media_data = part
.strip_prefix(":baml-start-media:")
.unwrap_or(part)
.strip_suffix(":baml-end-media:")
.unwrap_or(part);
match serde_json::from_str::<BamlMedia>(media_data) {
Ok(m) => Some(ChatMessagePart::Media(m)),
Err(_) => Err(minijinja::Error::new(
ErrorKind::CannotUnpack,
format!("Media variable had unrecognizable data: {media_data}"),
))?,
}
} else if !part.trim().is_empty() {
Some(ChatMessagePart::Text(part.trim().to_string()))
} else {
None
};
if let Some(part) = part {
if let Some(meta) = &meta {
parts.push(part.with_meta(meta.clone()));
} else {
parts.push(part);
}
}
}View on GitHub (pinned to bd85ce9dee)
Solutions
- Pass media variables to the template unmodified — never apply string filters or transformations to image/audio parameters.
- Verify the value is an actual BAML media object (created via image(...)/audio(...) helpers) rather than an arbitrary object.
- Print/log the media_data in the error to check for truncation or mangling; fix whatever altered the string.
- Align BAML client and runtime versions if the marker format changed between releases.
Example fix
// before
{{ image_var | upper }}
// after
{{ image_var }} Defensive patterns
Strategy: type-guard
Validate before calling
// ensure the value is an unmodified BAML media object before templating
if (typeof mediaVar === "string" && !mediaVar.includes(":baml-media:")) throw new Error("expected raw BAML media variable"); Type guard
function isBamlMedia(v) { return v && typeof v === "object" && typeof v.kind === "string"; } Try / catch
match serde_json::from_str::<BamlMedia>(media_data) { Ok(m) => ..., Err(e) => log::error!("media marker corrupt: {media_data}") } Prevention
- Never apply string filters (upper/trim/replace) to media variables in templates.
- Create media with BAML's image()/audio() helpers only.
- Keep BAML client/runtime versions aligned so marker encodings match.
When it happens
Trigger: A media variable's marker data is corrupted or truncated before rendering — e.g. the value was string-transformed (trimmed, escaped, re-serialized) between BAML inserting the marker and minijinja rendering it, or an object that isn't a BamlMedia was embedded with the media marker.
Common situations: Applying string filters (upper, trim, replace) to an image variable, which mangles the marker JSON; passing a non-media value where an image is expected; BAML version mismatches changing the internal marker encoding.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- {0}
- body record omitted log metadata
- value metadata omitted codec
- value metadata omitted availability
- original size does not fit usize
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/929af450da120483.
Report an issue: GitHub.