BoundaryML/baml · error

OpenAI transcriptions require chat messages with exactly…

Error message

OpenAI transcriptions require chat messages with exactly one audio media part

What it means

BAML's OpenAI transcription request builder only accepts a chat-style prompt (a list of RenderedChatMessage) containing the audio media; a bare string prompt (Either::Left) has no place to attach audio, so build_transcription_parts rejects it immediately. The error message is slightly misleading — the real requirement is a chat message list, not 'exactly one' at this stage.

Solutions

  1. Rewrite the BAML function to produce chat messages (e.g. use a message-based prompt with an audio media part) instead of a bare string prompt.
  2. Ensure the audio file is passed as a media part in one of the chat messages.
  3. If you only need text-to-text, do not route the function through a transcription client.

Example fix

// before
function Transcribe(audio: Audio) -> string {
  client openai/transcribe
  prompt #"transcribe"# // plain string prompt
}
// after
function Transcribe(audio: Audio) -> string {
  client openai/transcribe
  prompt #"
    {{ ctx.output_format }}
    {{ _.role('user') }}
    {{ audio }}
  "#
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the function prompt renders as chat messages containing an audio part
function assertChatAudioPrompt(fn) {
  const rendered = baml.renderPrompt(fn);
  if (typeof rendered === "string") {
    throw new Error("Transcription functions must use a chat-message prompt, not a plain string");
  }
}

Type guard

const isChatMessages = (p) =>
  Array.isArray(p) && p.some(m => m.parts?.some(pt => pt.type === "audio"));

Prevention

When it happens

Trigger: Calling a BAML function whose client is configured for OpenAI transcriptions while the prompt is a plain string (no chat message structure), i.e. the function renders to a raw string instead of chat messages.

Common situations: Writing a BAML function with a plain `string` prompt and pointing it at a transcription-style client; passing a template that renders to text rather than a message[]; older clients/tests that predate the chat-message transcription format.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at engine/baml-runtime/src/internal/llm_client/primitive/openai/types.rs:62

    #[serde(rename = "transcript.text.done")]
    Done {
        text: String,
        usage: Option<TranscriptionUsage>,
    },
    #[serde(rename = "transcript.text.segment")]
    Segment,
    #[serde(other)]
    Unknown,
}

pub fn build_transcription_parts(
    properties: &BamlMap<String, Value>,
    prompt: either::Either<&String, &[RenderedChatMessage]>,
) -> Result<TranscriptionParts> {
    let messages = match prompt {
        either::Either::Right(messages) => messages,
        either::Either::Left(_) => {
            bail!("OpenAI transcriptions require chat messages with exactly one audio media part")
        }
    };

    reject_reserved_request_fields(properties)?;

    let mut audio_parts = Vec::new();
    let mut text_parts = Vec::new();
    for message in messages {
        for part in &message.parts {
            collect_transcription_prompt_parts(part, &mut audio_parts, &mut text_parts)?;
        }
    }

    if audio_parts.len() != 1 {
        bail!(
            "OpenAI transcriptions require exactly one audio media part, got {}",
            audio_parts.len()
        );

View on GitHub (pinned to bd85ce9dee)