BoundaryML/baml · error
OpenAI transcription audio must be resolved to base64…
Error message
OpenAI transcription audio must be resolved to base64 before request building
What it means
OpenAI's audio transcription endpoint needs raw audio bytes (multipart upload), so BAML requires the media content to already be Base64-resolved before building the request. If the audio still holds a Url or File reference (unresolved media), build_transcription_parts cannot produce bytes and bails.
Solutions
- Let the BAML runtime resolve the media (pass the media through the normal function call path) instead of building requests manually.
- If using the low-level API, pre-resolve the media to base64 (read the file or fetch the URL and encode) before calling request building.
- Convert a File/Url media to BamlMediaContent::Base64 with the correct mime_type yourself.
Example fix
// before
let media = BamlMedia::url("audio/mpeg", "https://example.com/a.mp3");
// after
let bytes = std::fs::read("a.mp3")?;
let media = BamlMedia::base64("audio/mpeg", BASE64_STANDARD.encode(bytes)); Defensive patterns
Strategy: validation
Validate before calling
// Ensure media is base64-resolved before low-level request building
if (media.content.kind !== "base64") {
throw new Error("Resolve audio to base64 (pass through the BAML runtime) before building requests");
} Type guard
const isBase64Media = (m) => m.content && typeof m.content.base64 === "string";
Try / catch
// Catch and re-resolve
try {
parts = build_transcription_parts(&props, &prompt)?;
} catch (e) {
if (String(e).includes("resolved to base64")) {
media = await resolveMediaToBase64(media); // retry with resolved media
} else { throw e; }
} Prevention
- Always create media via the BAML runtime's media helpers (baml.Audio from file/url) so resolution happens automatically.
- Never feed raw BamlMedia::url/file values into low-level request-building APIs.
- Pre-fetch remote audio and hand BAML base64 with an explicit mime_type.
When it happens
Trigger: Passing a BamlMedia whose content is BamlMediaContent::Url or BamlMediaContent::File into a transcription request without prior media resolution (the media was constructed manually or resolution was skipped/bypassed).
Common situations: Constructing BamlMedia::Url directly in code and sending it to a transcription client instead of letting the runtime resolve it; using a file-path media in a context where the resolution pass doesn't run (custom clients/low-level API usage).
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- OpenAI transcriptions only support audio media parts
- OpenAI transcriptions require chat messages with exactly…
- OpenAI transcriptions require exactly one audio media part…
- OpenAI transcription prompt is ambiguous: both…
- OpenAI transcriptions do not support reserved request field
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/8f0df31b22a29c6d.
Report an issue: GitHub.
Appendix: source
Thrown at engine/baml-runtime/src/internal/llm_client/primitive/openai/types.rs:92
}
if audio_parts.len() != 1 {
bail!(
"OpenAI transcriptions require exactly one audio media part, got {}",
audio_parts.len()
);
}
let audio = audio_parts
.pop()
.expect("audio_parts length was already validated");
let mime = audio.mime_type_as_ok()?;
let file_bytes = match &audio.content {
BamlMediaContent::Base64(media_b64) => BASE64_STANDARD
.decode(&media_b64.base64)
.context("Failed to decode transcription audio as base64")?,
BamlMediaContent::Url(_) | BamlMediaContent::File(_) => {
bail!("OpenAI transcription audio must be resolved to base64 before request building")
}
};
let mut fields = BamlMap::new();
let model = required_string_field(properties, "model")?;
fields.insert("model".to_string(), model.clone());
let property_prompt = optional_string_field(properties, "prompt")?;
if property_prompt.is_some() && !text_parts.is_empty() {
bail!("OpenAI transcription prompt is ambiguous: both properties.prompt and rendered text were provided");
}
let rendered_prompt = (!text_parts.is_empty()).then(|| text_parts.join("\n"));
if let Some(prompt) = property_prompt.or(rendered_prompt) {
fields.insert("prompt".to_string(), prompt);
}
if let Some(language) = optional_string_field(properties, "language")? {
fields.insert("language".to_string(), language);View on GitHub (pinned to bd85ce9dee)