baml

SKILL.md

BAML Quick Reference

BAML (Boundary AI Markup Language) is a DSL for building LLM applications with structured, type-safe outputs. It generates client code for Python, TypeScript, Go, and Ruby.

How BAML Works

  • All .baml files in baml_src/ are globally accessible to each other
  • Generate the client with baml-cli generate
  • The generated baml_client/ provides type-safe functions you import and call
  • BAML types become Pydantic models (Python), TypeScript types, Go structs, or Sorbet types (Ruby)

Essential Syntax

Types

// Primitives
string, int, float, bool, null

// Composite
Type?           // optional (nullable)
Type[]          // array
Type1 | Type2   // union
map<K, V>       // dictionary

// Literals
"spam" | "ham"  // literal string union

// Multimodal
image, audio, video, pdf

Classes

Define structured data shapes. No colons between field name and type.

class Resume {
    name string
    email string?                           // optional
    skills string[]                         // array
    experience Experience[]                 // nested type
    seniority SeniorityLevel               // enum
}

class Experience {
    company string
    role string @description("Job title")   // hint for the LLM
    years int @alias("duration_years")      // JSON key mapping
}

Enums

Fixed set of values. Great for classification tasks.

enum SeniorityLevel {
    JUNIOR @description("0-2 years experience")
    MID @description("2-5 years experience")
    SENIOR @description("5+ years experience")
}

enum Category {
    SPAM
    HAM
    UNKNOWN @skip  // excluded from prompts
}

Functions

Define the LLM interaction. Function names must start with a capital letter.

function ExtractResume(resume_text: string) -> Resume {
    client "openai/gpt-4o"
    prompt #"
        Extract structured information from this resume.

        {{ ctx.output_format }}

        Resume:
        ---
        {{ resume_text }}
        ---
    "#
}

Clients

Configure LLM providers. Two styles:

// Shorthand (uses env vars automatically)
client "openai/gpt-4o"
client "anthropic/claude-sonnet-4-20250514"

// Named client (full control)
client<llm> GPT4 {
    provider openai
    options {
        model "gpt-4o"
        api_key env.OPENAI_API_KEY
        temperature 0.0
    }
}

// Fallback chain
client<llm> Resilient {
    provider fallback
    options {
        clients [GPT4, Claude, GPT4Mini]
    }
}

Generator

Configure code generation:

generator target {
    output_type "python/pydantic"  // or "typescript", "go", "ruby/sorbet"
    output_dir "../"
    default_client_mode "sync"     // or "async"
    version "0.203.1"
}

You may set up codegen for multiple locations in multiple languages. For example, you may want to keep backend and frontend types aligned for your respective BAML clients. You can do this by initializing two generator blocks.


The Two Critical Concepts

1. {{ ctx.output_format }}

This Jinja macro must be included in every prompt. It renders the return type schema so the LLM knows what structure to produce.

function ClassifyEmail(email: string) -> Category {
    client GPT4
    prompt #"
        Classify this email.

        {{ ctx.output_format }}

        Email: {{ email }}
    "#
}

For a Category enum, this renders something like:

Answer with any of the categories:
SPAM
HAM

For a class, it renders the JSON schema with field descriptions.

2. Schema-Aligned Parsing (SAP)

BAML's parser is intentionally lenient. It automatically fixes common LLM output issues:

  • Missing quotes around strings
  • Trailing commas
  • Comments in JSON
  • Incomplete sequences
  • Unescaped characters

This means you get 87-93% better accuracy than strict JSON parsing or function calling.


Prompt Syntax (Jinja)

BAML prompts use Jinja templating:

prompt #"
    {# Comments don't appear in output #}

    {{ _.role("system") }}
    You are a helpful assistant.

    {{ _.role("user") }}
    {% for msg in messages %}
        {{ msg.content }}
    {% endfor %}

    {% if verbose %}
        Be detailed in your response.
    {% endif %}

    {{ ctx.output_format }}
"#

Key constructs:

  • {{ variable }} - interpolate values
  • {% for item in list %}...{% endfor %} - loops
  • {% if cond %}...{% endif %} - conditionals
  • {{ _.role("system"|"user"|"assistant") }} - set message role
  • {{ value|filter }} - apply filters (e.g., |upper, |length, |join(","))

Calling BAML Functions

Python

from baml_client import b
from baml_client.types import Resume

# Sync
resume = b.ExtractResume(resume_text)
print(resume.name, resume.skills)

# Async
from baml_client.async_client import b
resume = await b.ExtractResume(resume_text)

# Streaming
stream = b.stream.ExtractResume(resume_text)
for partial in stream:
    print(partial)  # partial Resume object
final = stream.get_final_response()

TypeScript

import { b } from './baml_client'

// Async (default)
const resume = await b.ExtractResume(resumeText)
console.log(resume.name, resume.skills)

// Streaming
const stream = b.stream.ExtractResume(resumeText)
for await (const partial of stream) {
    console.log(partial)
}
const final = await stream.getFinalResponse()

Go

import b "example.com/myproject/baml_client"

resume, err := b.ExtractResume(ctx, resumeText, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Println(resume.Name, resume.Skills)

Testing

Define tests directly in BAML files:

test SimpleExtraction {
    functions [ExtractResume]
    args {
        resume_text "John Doe, Software Engineer at Acme Corp for 5 years"
    }
    @@assert({{ this.name == "John Doe" }})
}

Run tests:

baml-cli test                    # run all
baml-cli test -i "ExtractResume" # filter by function
baml-cli test --parallel 5       # parallel execution

Attributes Reference

Field-level:

  • @alias("name") - rename field in JSON output
  • @description("...") - add context for the LLM
  • @skip - exclude from prompts (enums only)

Block-level:

  • @@dynamic - allow runtime modification of class/enum

Validation:

  • @check(expr, name) - soft validation (returns result, doesn't fail)
  • @assert(expr, name) - hard validation (throws on failure)

Streaming:

  • @@stream.done - object only appears when complete
  • @stream.not_null - field must have value before parent streams
  • @stream.with_state - include completion state metadata

CLI Commands

baml-cli init          # initialize new project
baml-cli generate      # generate client code
baml-cli dev           # dev server with hot reload
baml-cli test          # run tests
baml-cli serve         # start REST API server (port 2024)
baml-cli fmt           # format BAML files

Common Patterns

See the examples/ directory for complete working examples:

  • extraction.baml - structured data extraction
  • classification.baml - enum-based classification
  • chat.baml - multi-turn chat with message history
  • multimodal.baml - image/audio/pdf inputs
  • usage.py - Python calling patterns

Further Resources

Getting Started

Language Reference

LLM Providers

Prompt Engineering

Advanced

Testing

Weekly Installs
32
First Seen
Jan 24, 2026
Installed on
gemini-cli27
codex27
opencode27
github-copilot26
kimi-cli25
amp25