---
title: BAML — Typed LLM Functions
description: Authoring guide for new LLM function contracts in Minion gateway
---

# BAML — Typed LLM Functions

[BAML](https://github.com/BoundaryML/baml) (Boundary Markup Language) is the DSL Minion uses to declare LLM function contracts. It provides:

- **Type-safe** inputs and outputs enforced at runtime (automatic schema validation + retry on parse failure)
- **Multi-language codegen** — one `.baml` file generates TypeScript clients (and Python for analysis tooling)
- **Built-in test harness** (`baml test`) for prompt regression testing in CI

## File layout

```
baml_src/                  # Source — committed to git
  generators.baml          # Output config (TypeScript → src/baml_client/)
  clients.baml             # LLM client definitions (Claude, GPT-4o, …)
  classify-intent.baml     # Example: ClassifyIntent function
  <your-function>.baml     # Add new functions here

src/baml_client/           # Generated — gitignored, regenerated in CI
```

## Writing a new BAML function

### 1. Create `baml_src/<your-function>.baml`

```baml
// Define output types (enum, class, or primitive)
enum SentimentLabel {
  POSITIVE
  NEGATIVE
  NEUTRAL
}

// Declare the function
function AnalyzeSentiment(text: string) -> SentimentLabel {
  client Claude          // references clients.baml
  prompt #"
    Classify the sentiment of this text.

    Text: {{ text }}

    {{ ctx.output_format }}
  "#
}

// Add at least one test per function (runs via `pnpm baml:test` in CI)
test AnalyzeSentiment_positive {
  functions [AnalyzeSentiment]
  args {
    text "I love this feature!"
  }
  @@assert(this == POSITIVE)
}
```

### 2. Regenerate the TypeScript client

```bash
pnpm baml:generate
```

This writes `src/baml_client/` (gitignored).

### 3. Wire the generated client behind a feature flag

Create `src/tools/<your-function>.ts` and guard it with the `MINION_BAML_ENABLED` env var so it is off by default:

```typescript
import type { SentimentLabel } from "../../src/baml_client/index.js";

export async function analyzeSentiment(text: string): Promise<SentimentLabel | null> {
  if (process.env.MINION_BAML_ENABLED !== "true") return null;

  const { b } = await import("../../src/baml_client/index.js");
  return b.AnalyzeSentiment(text);
}
```

### 4. Run prompt tests locally

```bash
ANTHROPIC_API_KEY=<your-key> pnpm baml:test
```

## Adding a new LLM client

Edit `baml_src/clients.baml` to add a new provider. See [BAML provider docs](https://docs.boundaryml.com/guide/baml-advanced/clients).

```baml
client<llm> Gemini {
  provider google-ai
  options {
    model "gemini-2.0-flash"
    api_key env.GEMINI_API_KEY
  }
}
```

## CI integration

The `baml-test` CI job runs on every PR touching `src/` or `baml_src/`:

1. **`pnpm baml:generate`** — regenerates the TypeScript client from `.baml` sources
2. **`pnpm baml:test`** — runs BAML prompt tests against real models (requires `ANTHROPIC_API_KEY` CI secret)

The normal `pnpm build` step also calls `pnpm baml:generate` first, so the generated client is always fresh before TypeScript compilation.

## Constraints

- `src/baml_client/` is **gitignored** — never commit generated files
- Always add a `@@assert` test for each new function
- The `MINION_BAML_ENABLED` feature flag must default to `false` — don't enable by default without QA sign-off
- No migration of existing tools — BAML is adopted on new tool additions only
