Skip to main content

What is BAML?

BAML (Boundary AI Markup Language) is a domain-specific language for building type-safe LLM applications. It provides structured extraction with automatic validation and retry logic.

Installation

pip install rdk baml-py --extra-index-url https://pypi.fury.io/021labs/

Setup

1. Create BAML Files

Create your BAML schema in baml_src/main.baml:
class CustomerInquiry {
  category "billing" | "technical" | "general"
  priority int @description("1-5, where 5 is highest")
  summary string
}

function ClassifyInquiry(message: string) -> CustomerInquiry {
  client Claude
  prompt #"
    Classify this customer inquiry:
    {{ message }}

    Return the classification as JSON.
  "#
}

2. Configure Generator

Create baml_src/generators.baml:
generator target {
  output_type python/pydantic
  output_dir ../
  version "0.219.0"
}

client<llm> Claude {
  provider anthropic
  options {
    model "claude-sonnet-4-6"
  }
}

3. Generate Client

baml-cli generate

Basic Usage

BAML clients are code-generated per project and cannot be auto-instrumented. You must call instrument_baml(b) after init(), or BAML calls will not be traced.
import os
from baml_client import b
from baml_client.types import CustomerInquiry
from rdk import init, observe, instrument_baml, shutdown

# 1. Initialize RDK
init(
    endpoint=os.environ["RDK_ENDPOINT"],
    api_key=os.environ["RDK_API_KEY"],
)

# 2. Wrap the BAML client — required for tracing
b = instrument_baml(b)

@observe(name="classify-inquiry")
def classify(message: str) -> CustomerInquiry:
    return b.ClassifyInquiry(message)

result = classify("I can't log into my account!")
print(f"Category: {result.category}")
print(f"Priority: {result.priority}")
print(f"Summary: {result.summary}")

shutdown()

Async Support

BAML async functions are fully supported:
import asyncio
import os
from baml_client import b
from rdk import init, observe, instrument_baml, shutdown

init(
    endpoint=os.environ["RDK_ENDPOINT"],
    api_key=os.environ["RDK_API_KEY"],
)
b = instrument_baml(b)

@observe(name="async-classify")
async def async_classify(message: str):
    return await b.ClassifyInquiry(message)

result = asyncio.run(async_classify("Payment failed"))
shutdown()

Complex Types

BAML excels at structured extraction with nested types:
class Address {
  street string
  city string
  country string
  postal_code string | null
}

class Contact {
  name string
  email string
  phone string | null
  address Address
}

function ExtractContact(text: string) -> Contact {
  client Claude
  prompt #"
    Extract contact information from:
    {{ text }}
  "#
}
import os
from baml_client import b
from rdk import init, observe, instrument_baml, shutdown

init(
    endpoint=os.environ["RDK_ENDPOINT"],
    api_key=os.environ["RDK_API_KEY"],
)
b = instrument_baml(b)

@observe(name="extract-contact")
def extract_contact(text: str):
    return b.ExtractContact(text)

result = extract_contact("""
    John Smith
    john@example.com
    123 Main St, New York, NY 10001
""")
print(f"Name: {result.name}")
print(f"City: {result.address.city}")
shutdown()

What Gets Captured

For each BAML call, RDK captures:
FieldDescription
typeLLM
modelModel from BAML client config
input.argsFunction arguments
output.resultStructured result
token_usageToken counts
metadata.provider"baml"

Error Handling

BAML provides validation errors for malformed responses:
from baml_client import b
from baml_client.errors import BamlValidationError
from rdk import init, observe, instrument_baml, shutdown

init(
    endpoint=os.environ["RDK_ENDPOINT"],
    api_key=os.environ["RDK_API_KEY"],
)
b = instrument_baml(b)

@observe(name="safe-classify")
def safe_classify(message: str):
    try:
        return b.ClassifyInquiry(message)
    except BamlValidationError as e:
        print(f"Validation failed: {e}")
        return None

result = safe_classify("Hello")
shutdown()

See Also