Skip to content

Instantly share code, notes, and snippets.

@aaronvg
Last active January 26, 2026 10:07
Show Gist options
  • Select an option

  • Save aaronvg/75596a0063588440d47f5db1361c8a5f to your computer and use it in GitHub Desktop.

Select an option

Save aaronvg/75596a0063588440d47f5db1361c8a5f to your computer and use it in GitHub Desktop.
BAML Gist Dec 20, 2025
# BAML (Basically, A Made-Up Language) Reference Guide for AI Agents
<Overview>
BAML is a domain-specific language for building type-safe LLM prompts as functions. It provides:
- Strongly-typed inputs and outputs for LLM calls
- Automatic JSON parsing and validation
- Jinja-based prompt templating
- Multi-language code generation (Python, TypeScript, Go, Ruby)
The workflow is: Define BAML files → Run `baml-cli generate` → Import generated client in your code.
</Overview>
## Installation
### Python
```bash
# Install the package
pip install baml-py # or: poetry add baml-py / uv add baml-py
# Initialize BAML in your project (creates baml_src/ directory)
baml-cli init
# Generate the client (REQUIRED after any .baml file changes)
baml-cli generate
```
### TypeScript / JavaScript
```bash
# Install the package
npm install @boundaryml/baml # or: pnpm add / yarn add / bun add
# Initialize BAML in your project
npx baml-cli init
# Generate the client (REQUIRED after any .baml file changes)
npx baml-cli generate
```
### VSCode / Cursor Extension
Install the BAML extension for syntax highlighting, testing playground, and prompt previews:
https://marketplace.visualstudio.com/items?itemName=boundary.baml-extension
The extension auto-runs `baml-cli generate` on save.
## CRITICAL: Running `baml-cli generate`
**You MUST run `baml-cli generate` every time you modify any `.baml` file.**
This command:
1. Reads all `.baml` files in `baml_src/`
2. Generates the `baml_client/` directory with type-safe code
3. Creates Pydantic models (Python) or TypeScript interfaces
```bash
# Python
baml-cli generate
# TypeScript
npx baml-cli generate
```
Add to your build process:
```json
// package.json
{
"scripts": {
"build": "npx baml-cli generate && tsc --build"
}
}
```
## Testing
Run tests defined in `.baml` files with `baml-cli test`. Use `baml-cli test --help` for all options.
```bash
baml-cli test # Run all tests
baml-cli test -i "MyFunction:TestName" # Run specific test
```
## Generator Block
The `generator` block in `baml_src/generators.baml` configures code generation. Created by `baml-cli init`.
```baml
generator target {
// Target language (REQUIRED)
// Options: "python/pydantic", "typescript", "typescript/react", "go", "ruby/sorbet"
output_type "python/pydantic"
// Output directory relative to baml_src/ (REQUIRED)
output_dir "../"
// Runtime version - should match installed package version (REQUIRED)
version "0.76.2"
// Default client mode: "sync" or "async"
default_client_mode "sync"
// TypeScript only: "cjs" (CommonJS) or "esm" (ES modules)
module_format "cjs"
// Shell command to run after generation (e.g., formatters)
on_generate "black . && isort ."
}
```
## Types
### Primitive Types
```baml
bool // true/false
int // integers
float // decimal numbers
string // text
null // null value
```
### Composite Types
```baml
string[] // array of strings
int? // optional int
string | int // union type
map<string, int> // key-value map
"a" | "b" | "c" // literal union
```
### Multimodal Types
```baml
image // for vision models
audio // for audio models
video // for video models
pdf // for document models
```
### Type Aliases
```baml
type Primitive = int | string | bool | float
type Graph = map<string, string[]>
// Recursive types are supported through containers
type JsonValue = int | string | bool | float | JsonObject | JsonArray
type JsonObject = map<string, JsonValue>
type JsonArray = JsonValue[]
```
## Classes
Classes define structured data. Properties have NO colon.
```baml
class MyObject {
// Required string
name string
// Optional field (use ?)
nickname string?
// Field with description (goes AFTER the type)
age int @description("Age in years")
// Field with alias (renames for LLM, keeps original in code)
email string @alias("email_address")
// Arrays (cannot be optional)
tags string[]
// Nested objects
address Address
// Enum field
status Status
// Union type
result "success" | "error"
// Literal types
version 1 | 2 | 3
// Map type
metadata map<string, string>
// Multimodal
photo image
}
// Recursive classes are supported
class Node {
value int
children Node[]
}
```
### Field Attributes
- `@alias("name")` - Rename field for LLM (keeps original name in code)
- `@description("...")` - Add context for the LLM
### Class Attributes
- `@@dynamic` - Allow adding fields at runtime
## Enums
Enums are for classification tasks with a fixed set of values.
```baml
enum Category {
PENDING
ACTIVE @description("Currently being processed")
COMPLETE
CANCELLED @alias("CANCELED") @description("Was stopped before completion")
INTERNAL @skip // Exclude from prompt
}
// Dynamic enum (can modify at runtime)
enum DynamicCategory {
Value1
Value2
@@dynamic
}
```
### Value Attributes
- `@alias("name")` - Rename value for LLM
- `@description("...")` - Add context
- `@skip` - Exclude from prompt
## Functions
Functions define LLM calls with typed inputs/outputs.
```baml
function FunctionName(param1: Type1, param2: Type2) -> ReturnType {
client "provider/model"
prompt #"
Your prompt here with {{ param1 }} and {{ param2 }}
{{ ctx.output_format }}
"#
}
```
### LLM Clients (Shorthand Syntax)
```baml
client "openai/gpt-4o"
client "openai/gpt-4o-mini"
client "anthropic/claude-sonnet-4-20250514"
client "anthropic/claude-3-5-haiku-latest"
client "google-ai/gemini-2.0-flash"
```
See the [Providers](#providers-and-clients) section below for full configuration options.
### Prompt Syntax Rules
1. **Always include inputs** - Reference all input parameters in the prompt:
```baml
prompt #"
Analyze: {{ input }}
"#
```
2. **Always include output format** - Let BAML generate schema instructions:
```baml
prompt #"
{{ ctx.output_format }}
"#
```
3. **Use roles for chat models**:
```baml
prompt #"
{{ _.role("system") }}
You are a helpful assistant.
{{ _.role("user") }}
{{ user_message }}
"#
```
4. **DO NOT repeat output schema fields** - `{{ ctx.output_format }}` handles this automatically.
### Complete Function Example
```baml
class TweetAnalysis {
mainTopic string @description("The primary topic of the tweet")
sentiment "positive" | "negative" | "neutral"
isSpam bool
}
function ClassifyTweets(tweets: string[]) -> TweetAnalysis[] {
client "openai/gpt-4o-mini"
prompt #"
Analyze each tweet and classify it.
{{ _.role("user") }}
{{ tweets }}
{{ ctx.output_format }}
"#
}
```
## Prompt Syntax (Jinja)
### Variables
```jinja
{{ variable }}
{{ object.field }}
{{ array[0] }}
```
### Conditionals
```jinja
{% if condition %}
content
{% elif other_condition %}
other content
{% else %}
fallback
{% endif %}
```
### Loops
```jinja
{% for item in items %}
{{ item }}
{% endfor %}
{% for item in items %}
{{ _.role("user") if loop.index % 2 == 1 else _.role("assistant") }}
{{ item }}
{% endfor %}
```
### Roles
```jinja
{{ _.role("system") }} // System message
{{ _.role("user") }} // User message
{{ _.role("assistant") }} // Assistant message
```
### Context Variables
```jinja
{{ ctx.output_format }} // Output schema instructions (REQUIRED)
{{ ctx.client.provider }} // Current provider name
{{ ctx.client.name }} // Client name
```
## Template Strings
Reusable prompt snippets:
```baml
template_string FormatMessages(messages: Message[]) #"
{% for m in messages %}
{{ _.role(m.role) }}
{{ m.content }}
{% endfor %}
"#
function Chat(messages: Message[]) -> string {
client "openai/gpt-4o"
prompt #"
{{ FormatMessages(messages) }}
{{ ctx.output_format }}
"#
}
```
## Checks and Assertions
### @assert - Strict validation (raises exception on failure)
```baml
class Person {
age int @assert(valid_age, {{ this >= 0 and this <= 150 }})
email string @assert(valid_email, {{ this|regex_match("@") }})
}
// On return type
function GetScore(input: string) -> int @assert(valid_score, {{ this >= 0 and this <= 100 }}) {
client "openai/gpt-4o"
prompt #"..."#
}
```
### @check - Non-exception validation (can inspect results)
```baml
class Citation {
quote string @check(has_content, {{ this|length > 0 }})
}
```
### Block-level assertions (cross-field validation)
```baml
class DateRange {
start_date string
end_date string
@@assert(valid_range, {{ this.start_date < this.end_date }})
}
```
## Multimodal Inputs
### Images
```baml
function DescribeImage(img: image) -> string {
client "openai/gpt-4o"
prompt #"
{{ _.role("user") }}
Describe this image:
{{ img }}
"#
}
```
### Audio
```baml
function TranscribeAudio(audio: audio) -> string {
client "openai/gpt-4o"
prompt #"
{{ _.role("user") }}
Transcribe: {{ audio }}
"#
}
```
## Union Return Types (Tool Selection)
```baml
class SearchQuery {
query string
}
class WeatherRequest {
city string
}
class CalendarEvent {
title string
date string
}
function RouteRequest(input: string) -> SearchQuery | WeatherRequest | CalendarEvent {
client "openai/gpt-4o"
prompt #"
Determine what the user wants and extract the appropriate data.
{{ _.role("user") }}
{{ input }}
{{ ctx.output_format }}
"#
}
```
## Chat History Pattern
```baml
class Message {
role "user" | "assistant"
content string
}
function Chat(messages: Message[]) -> string {
client "openai/gpt-4o"
prompt #"
{{ _.role("system") }}
You are a helpful assistant.
{% for message in messages %}
{{ _.role(message.role) }}
{{ message.content }}
{% endfor %}
"#
}
```
## Tests
```baml
test TestClassify {
functions [ClassifyTweets]
args {
tweets ["Hello world!", "Buy now! Limited offer!"]
}
}
test TestImage {
functions [DescribeImage]
args {
img { url "https://example.com/image.png" }
}
}
test TestLocalImage {
functions [DescribeImage]
args {
img { file "test_image.png" }
}
}
```
## Usage in Code
### Python
```python
from baml_client import b
from baml_client.types import TweetAnalysis
def main():
# Sync call
result = b.ClassifyTweets(["Hello!", "Check out this deal!"])
for analysis in result:
print(f"Topic: {analysis.mainTopic}")
print(f"Sentiment: {analysis.sentiment}")
```
### TypeScript
```typescript
import { b } from './baml_client'
import { TweetAnalysis } from './baml_client/types'
async function main() {
const result = await b.ClassifyTweets(["Hello!", "Check out this deal!"])
for (const analysis of result) {
console.log(`Topic: ${analysis.mainTopic}`)
console.log(`Sentiment: ${analysis.sentiment}`)
}
}
```
### Multimodal in Code
```python
from baml_py import Image
from baml_client import b
# From URL
result = b.DescribeImage(Image.from_url("https://example.com/photo.jpg"))
# From base64
result = b.DescribeImage(Image.from_base64("image/png", base64_string))
```
```typescript
import { Image } from "@boundaryml/baml"
import { b } from './baml_client'
// From URL
const result = await b.DescribeImage(Image.fromUrl("https://example.com/photo.jpg"))
// From base64
const result = await b.DescribeImage(Image.fromBase64("image/png", base64String))
```
## Providers and Clients
BAML supports many LLM providers. For detailed configuration of any provider, search the docs at `docs.boundaryml.com` for the provider name.
### Supported Providers
**Native Providers** (first-class support):
| Provider | Shorthand Example | Default API Key Env Var |
|----------|-------------------|------------------------|
| **openai** | `"openai/gpt-4o"` | `OPENAI_API_KEY` |
| **anthropic** | `"anthropic/claude-sonnet-4-20250514"` | `ANTHROPIC_API_KEY` |
| **google-ai** | `"google-ai/gemini-2.0-flash"` | `GOOGLE_API_KEY` |
| **vertex** | `"vertex/gemini-2.0-flash"` | Google Cloud credentials |
| **azure-openai** | (requires full config) | `AZURE_OPENAI_API_KEY` |
| **aws-bedrock** | (requires full config) | AWS credentials |
**OpenAI-Compatible Providers** (use `openai-generic`):
These providers use OpenAI's API format. Use `provider openai-generic` with their `base_url`:
| Service | base_url |
|---------|----------|
| Groq | `https://api.groq.com/openai/v1` |
| Together AI | `https://api.together.ai/v1` |
| OpenRouter | `https://openrouter.ai/api/v1` |
| Ollama | `http://localhost:11434/v1` |
| Cerebras | `https://api.cerebras.ai/v1` |
| Hugging Face | `https://api-inference.huggingface.co/v1` |
| LM Studio | `http://localhost:1234/v1` |
| vLLM | `http://localhost:8000/v1` |
For the full list, see: https://docs.boundaryml.com/ref/llm-client
### Shorthand vs Named Clients
**Shorthand** (quick, uses defaults):
```baml
function MyFunc(input: string) -> string {
client "openai/gpt-4o"
prompt #"..."#
}
```
**Named Client** (full control):
```baml
client<llm> MyClient {
provider openai
options {
model "gpt-4o"
api_key env.MY_OPENAI_KEY
temperature 0.7
max_tokens 1000
}
}
function MyFunc(input: string) -> string {
client MyClient
prompt #"..."#
}
```
### Common Provider Configurations
#### OpenAI
```baml
client<llm> GPT4 {
provider openai
options {
model "gpt-4o" // or "gpt-4o-mini", "gpt-4-turbo", "o1", "o1-mini"
api_key env.OPENAI_API_KEY
temperature 0.7
max_tokens 4096
}
}
```
#### Anthropic
```baml
client<llm> Claude {
provider anthropic
options {
model "claude-sonnet-4-20250514" // or "claude-3-5-haiku-latest"
api_key env.ANTHROPIC_API_KEY
max_tokens 4096
}
}
```
#### Google AI (Gemini)
```baml
client<llm> Gemini {
provider google-ai
options {
model "gemini-2.0-flash" // or "gemini-2.5-pro", "gemini-2.5-flash"
api_key env.GOOGLE_API_KEY
generationConfig {
temperature 0.7
}
}
}
```
#### OpenAI-Generic (Groq, Together, OpenRouter, Ollama, etc.)
```baml
// Groq
client<llm> Groq {
provider openai-generic
options {
base_url "https://api.groq.com/openai/v1"
api_key env.GROQ_API_KEY
model "llama-3.1-70b-versatile"
}
}
// Together AI
client<llm> Together {
provider openai-generic
options {
base_url "https://api.together.ai/v1"
api_key env.TOGETHER_API_KEY
model "meta-llama/Llama-3-70b-chat-hf"
}
}
// OpenRouter
client<llm> OpenRouter {
provider openai-generic
options {
base_url "https://openrouter.ai/api/v1"
api_key env.OPENROUTER_API_KEY
model "anthropic/claude-3.5-sonnet"
}
}
// Ollama (local)
client<llm> Ollama {
provider openai-generic
options {
base_url "http://localhost:11434/v1"
model "llama3"
}
}
```
#### Azure OpenAI
```baml
client<llm> AzureGPT {
provider azure-openai
options {
resource_name "my-resource"
deployment_id "my-deployment"
api_key env.AZURE_OPENAI_API_KEY
}
}
```
### Retry Policies
```baml
retry_policy MyRetryPolicy {
max_retries 3
strategy {
type exponential_backoff
delay_ms 200
multiplier 1.5
max_delay_ms 10000
}
}
client<llm> ReliableClient {
provider openai
retry_policy MyRetryPolicy
options {
model "gpt-4o"
}
}
```
### Fallback Clients
Use multiple providers with automatic fallback:
```baml
client<llm> PrimaryClient {
provider openai
options { model "gpt-4o" }
}
client<llm> BackupClient {
provider anthropic
options { model "claude-sonnet-4-20250514" }
}
client<llm> ResilientClient {
provider fallback
options {
strategy [
PrimaryClient
BackupClient
]
}
}
```
### Round-Robin Load Balancing
```baml
client<llm> LoadBalanced {
provider round-robin
options {
strategy [ClientA, ClientB, ClientC]
}
}
```
### Custom Headers
```baml
client<llm> WithHeaders {
provider openai
options {
model "gpt-4o"
headers {
"X-Custom-Header" "value"
}
}
}
```
### Environment Variables
Reference environment variables with `env.VAR_NAME`:
```baml
client<llm> MyClient {
provider openai
options {
api_key env.MY_CUSTOM_KEY
base_url env.CUSTOM_BASE_URL
}
}
```
## Streaming
BAML supports structured streaming with automatic partial JSON parsing.
### Basic Streaming
```python
# Python
stream = b.stream.MyFunction(input)
for partial in stream:
print(partial) # Partial object with nullable fields
final = stream.get_final_response() # Complete validated object
```
```typescript
// TypeScript
const stream = b.stream.MyFunction(input)
for await (const partial of stream) {
console.log(partial) // Partial object
}
const final = await stream.getFinalResponse()
```
### Semantic Streaming Attributes
Control how fields stream with these attributes:
| Attribute | Effect | Use Case |
|-----------|--------|----------|
| `@stream.done` | Field only appears when complete | Atomic values, IDs |
| `@stream.not_null` | Parent object waits for this field | Discriminators, required fields |
| `@stream.with_state` | Adds completion state metadata | UI loading indicators |
```baml
class BlogPost {
// Post won't stream until title is complete
title string @stream.done @stream.not_null
// Content streams token-by-token with state tracking
content string @stream.with_state
// Tags only appear when fully parsed
tags string[] @stream.done
}
class Message {
// Message won't stream until type is known
type "error" | "success" @stream.not_null
content string
}
// Entire item streams atomically (all-or-nothing)
class ReceiptItem {
name string
price float
@@stream.done
}
```
`@stream.with_state` wraps the field in a `StreamState` object:
```typescript
interface StreamState<T> {
value: T
state: "Pending" | "Incomplete" | "Complete"
}
```
## React / Next.js SDK
BAML provides first-class React/Next.js integration with auto-generated hooks and server actions. **Requires Next.js 15+**.
### Installation
```bash
# Install packages
npm install @boundaryml/baml @boundaryml/baml-nextjs-plugin
# Initialize BAML
npx baml-cli init
```
### Configure Next.js
```typescript
// next.config.ts
import { withBaml } from '@boundaryml/baml-nextjs-plugin';
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// ... existing config
};
export default withBaml()(nextConfig);
```
### Configure Generator for React
```baml
// baml_src/generators.baml
generator typescript {
output_type "typescript/react" // Enable React hooks generation
output_dir "../"
version "0.76.2"
}
```
Then run `npx baml-cli generate`.
### Auto-Generated Hooks
For each BAML function, a React hook is auto-generated with the pattern `use{FunctionName}`:
```baml
// baml_src/story.baml
class Story {
title string
content string
}
function WriteMeAStory(input: string) -> Story {
client "openai/gpt-4o"
prompt #"
Tell me a story about {{ input }}
{{ ctx.output_format }}
"#
}
```
```tsx
// app/components/story-form.tsx
'use client'
import { useWriteMeAStory } from "@/baml_client/react/hooks";
export function StoryForm() {
const story = useWriteMeAStory();
return (
<div>
<button
onClick={() => story.mutate("a brave robot")}
disabled={story.isLoading}
>
{story.isLoading ? 'Generating...' : 'Generate Story'}
</button>
{story.data && (
<div>
<h4>{story.data.title}</h4>
<p>{story.data.content}</p>
</div>
)}
{story.error && <div>Error: {story.error.message}</div>}
</div>
);
}
```
### Hook Options
```tsx
// Streaming (default)
const hook = useWriteMeAStory();
// Non-streaming
const hook = useWriteMeAStory({ stream: false });
// With callbacks
const hook = useWriteMeAStory({
onStreamData: (partial) => console.log('Streaming:', partial),
onFinalData: (final) => console.log('Complete:', final),
onError: (error) => console.error('Error:', error),
});
```
### Hook Return Values
| Property | Type | Description |
|----------|------|-------------|
| `data` | `T \| Partial<T>` | Current data (streaming or final) |
| `streamData` | `Partial<T>` | Latest streaming update |
| `finalData` | `T` | Final complete response |
| `isLoading` | `boolean` | Request in progress |
| `isPending` | `boolean` | Waiting to start |
| `isStreaming` | `boolean` | Currently streaming |
| `isSuccess` | `boolean` | Completed successfully |
| `isError` | `boolean` | Failed |
| `error` | `Error` | Error details |
| `mutate(args)` | `function` | Execute the BAML function |
| `reset()` | `function` | Reset hook state |
### Chatbot Example
```baml
// baml_src/chat.baml
class Message {
role "user" | "assistant"
content string
}
function Chat(messages: Message[]) -> string {
client "openai/gpt-4o"
prompt #"
You are a helpful assistant.
{% for m in messages %}
{{ _.role(m.role) }}
{{ m.content }}
{% endfor %}
"#
}
```
```tsx
'use client'
import { useChat } from "@/baml_client/react/hooks";
import { useState, useEffect } from "react";
import type { Message } from "@/baml_client/types";
export function ChatInterface() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const chat = useChat();
// Add assistant response to history when complete
useEffect(() => {
if (chat.isSuccess && chat.finalData) {
setMessages(prev => [...prev, { role: "assistant", content: chat.finalData! }]);
}
}, [chat.isSuccess, chat.finalData]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || chat.isLoading) return;
const newMessages = [...messages, { role: "user" as const, content: input }];
setMessages(newMessages);
setInput("");
await chat.mutate(newMessages);
};
return (
<div>
{messages.map((m, i) => (
<div key={i}><strong>{m.role}:</strong> {m.content}</div>
))}
{chat.isLoading && <div><strong>assistant:</strong> {chat.data ?? "..."}</div>}
<form onSubmit={handleSubmit}>
<input value={input} onChange={e => setInput(e.target.value)} />
<button type="submit" disabled={chat.isLoading}>Send</button>
</form>
</div>
);
}
```
## TypeBuilder (Dynamic Types at Runtime)
`TypeBuilder` allows you to modify output schemas at runtime - useful for dynamic categories from databases or user-provided schemas.
### Setup: Mark types as @@dynamic in BAML
```baml
enum Category {
RED
BLUE
@@dynamic // Allows runtime modification
}
class User {
name string
age int
@@dynamic // Allows adding properties at runtime
}
```
### Modify Types at Runtime
**Python:**
```python
from baml_client.type_builder import TypeBuilder
from baml_client import b
tb = TypeBuilder()
# Add enum values
tb.Category.add_value('GREEN')
tb.Category.add_value('YELLOW')
# Add class properties
tb.User.add_property('email', tb.string())
tb.User.add_property('address', tb.string().optional())
# Pass TypeBuilder when calling function
result = b.Categorize("The sun is bright", {"tb": tb})
```
**TypeScript:**
```typescript
import { TypeBuilder } from './baml_client/type_builder'
import { b } from './baml_client'
const tb = new TypeBuilder()
// Add enum values
tb.Category.addValue('GREEN')
tb.Category.addValue('YELLOW')
// Add class properties
tb.User.addProperty('email', tb.string())
tb.User.addProperty('address', tb.string().optional())
// Pass TypeBuilder when calling function
const result = await b.Categorize("The sun is bright", { tb })
```
### Create New Types at Runtime
```python
tb = TypeBuilder()
# Create a new enum
hobbies = tb.add_enum("Hobbies")
hobbies.add_value("Soccer")
hobbies.add_value("Reading")
# Create a new class
address = tb.add_class("Address")
address.add_property("street", tb.string())
address.add_property("city", tb.string())
# Attach to existing type
tb.User.add_property("hobbies", hobbies.type().list())
tb.User.add_property("address", address.type())
```
### TypeBuilder Methods
| Method | Description |
|--------|-------------|
| `tb.string()` | String type |
| `tb.int()` | Integer type |
| `tb.float()` | Float type |
| `tb.bool()` | Boolean type |
| `tb.string().list()` | List of strings |
| `tb.string().optional()` | Optional string |
| `tb.add_class("Name")` | Create new class |
| `tb.add_enum("Name")` | Create new enum |
| `.add_property(name, type)` | Add property to class |
| `.add_value(name)` | Add value to enum |
| `.description("...")` | Add description |
## ClientRegistry (Dynamic Client Selection)
`ClientRegistry` allows you to modify LLM clients at runtime - useful for A/B testing, dynamic model selection, or user-specific API keys.
**Python:**
```python
from baml_py import ClientRegistry
from baml_client import b
import os
cr = ClientRegistry()
# Add a new client
cr.add_llm_client(
name='MyClient',
provider='openai',
options={
"model": "gpt-4o",
"temperature": 0.7,
"api_key": os.environ.get('OPENAI_API_KEY')
}
)
# Set as the primary client for this call
cr.set_primary('MyClient')
# Use the registry
result = b.ExtractResume("...", {"client_registry": cr})
```
**TypeScript:**
```typescript
import { ClientRegistry } from '@boundaryml/baml'
import { b } from './baml_client'
const cr = new ClientRegistry()
// Add a new client
cr.addLlmClient('MyClient', 'openai', {
model: "gpt-4o",
temperature: 0.7,
api_key: process.env.OPENAI_API_KEY
})
// Set as the primary client
cr.setPrimary('MyClient')
// Use the registry
const result = await b.ExtractResume("...", { clientRegistry: cr })
```
### ClientRegistry Methods
| Method | Description |
|--------|-------------|
| `add_llm_client(name, provider, options)` | Add a new LLM client |
| `set_primary(name)` | Set which client to use |
Note: Using the same name as a BAML-defined client overwrites it for that call.
## Best Practices
1. **Always run `baml-cli generate`** - After ANY change to `.baml` files
2. **Always use `{{ ctx.output_format }}`** - Never write output schema manually
3. **Use `{{ _.role("user") }}`** - Mark where user inputs begin
4. **Use enums for classification** - Not confidence scores or numbers
5. **Use literal unions for small fixed sets** - `"high" | "medium" | "low"` instead of enums
6. **Use @description on fields** - Guides the LLM without repeating in prompt
7. **Keep prompts concise** - Let the type system do the work
8. **Avoid confidence levels** - Don't add confidence scores to extraction schemas
9. **Use composition over inheritance** - Nest classes instead of inheriting
10. **Dedent all declarations** - Keep class/enum/function definitions at the root level
## Documentation
For detailed documentation on any feature, visit: **https://docs.boundaryml.com**
Key documentation pages:
- Providers: `docs.boundaryml.com/ref/llm-client`
- React/Next.js: `docs.boundaryml.com/guide/framework-integration/react-next-js`
- TypeBuilder: `docs.boundaryml.com/ref/baml-client/typebuilder`
- ClientRegistry: `docs.boundaryml.com/guide/baml-advanced/client-registry`
- Dynamic Types: `docs.boundaryml.com/guide/baml-advanced/dynamic-runtime-types`
- Prompt Syntax: `docs.boundaryml.com/ref/prompt-syntax/what-is-jinja`
- Streaming: `docs.boundaryml.com/guide/baml-basics/streaming`
## File Organization
BAML files typically go in a `baml_src/` directory:
```
baml_src/
clients.baml # LLM client configurations
types.baml # Classes and enums
functions.baml # Function definitions
tests.baml # Test cases
```
Run `baml generate` after changes to regenerate the client code.
## Notes on Generated Types
- In Python: BAML types are Pydantic classes (except primitives)
- In TypeScript: BAML types are interfaces (except primitives)
- Union types generate discriminated unions
- Optional fields default to `None` in Python, `undefined` in TypeScript
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment