> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bitfab.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# BAML

> Auto-capture prompts and LLM metadata from BAML function calls with Bitfab

Bitfab integrates with [BAML](https://docs.boundaryml.com/) to automatically capture rendered prompts and LLM metadata on the current span - no manual `setPrompt` or `addContext` calls needed. Wrap a BAML method with `wrapBAML` / `wrap_baml` and Bitfab extracts everything automatically.

**Canonical signatures:** [TypeScript `wrapBAML`](/reference/typescript#wrapbaml) · [Python `wrap_baml`](/reference/python#wrap-baml)

## Supported Languages

| Language   | Method        | Status            |
| ---------- | ------------- | ----------------- |
| TypeScript | `wrapBAML()`  | ✅ Supported       |
| Python     | `wrap_baml()` | ✅ Supported       |
| Ruby       | -             | Not yet supported |
| Go         | -             | Not yet supported |

## Quick Start

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { Bitfab } from "@bitfab/sdk"
  import { b } from "./baml_client"

  const bitfab = new Bitfab({
    apiKey: process.env.BITFAB_API_KEY,
    bamlClient: b,
  })

  const tracedClassify = bitfab.withSpan(
    "classify",
    { type: "llm" },
    bitfab.wrapBAML(b.ClassifyText),
  )

  const result = await tracedClassify("Hello world")
  ```

  ```python Python theme={null}
  import os
  from bitfab import Bitfab
  from baml_client import b

  bitfab = Bitfab(api_key=os.environ["BITFAB_API_KEY"], baml_client=b)

  @bitfab.span("classify", type="llm")
  async def classify(text: str):
      return await bitfab.wrap_baml(b.ClassifyText)(text=text)

  result = await classify("Hello world")
  ```
</CodeGroup>

## What Gets Captured

`wrapBAML` / `wrap_baml` creates a BAML `Collector`, runs the method through a tracked client, then extracts:

| Data                                     | Span Field                          | Source                                  |
| ---------------------------------------- | ----------------------------------- | --------------------------------------- |
| Rendered prompt (system + user messages) | `span_data.prompt`                  | BAML Collector HTTP request body        |
| Model name                               | `span_data.contexts[].model`        | BAML Collector HTTP request body or URL |
| Provider                                 | `span_data.contexts[].provider`     | BAML Collector call metadata            |
| Input tokens                             | `span_data.contexts[].inputTokens`  | BAML Collector usage                    |
| Output tokens                            | `span_data.contexts[].outputTokens` | BAML Collector usage                    |
| Duration                                 | `span_data.contexts[].durationMs`   | BAML Collector timing                   |

If `@boundaryml/baml` (TypeScript) or `baml-py` (Python) is not installed, the BAML method is called directly without instrumentation.

### Limitations

* **Streaming functions are not auto-instrumented.** `wrapBAML` / `wrap_baml` awaits a single result, so streaming calls (`b.stream.ClassifyText`, which return a stream rather than a final value) are not captured. Instrument the non-streaming form (`b.ClassifyText`), or capture the call manually with `setPrompt` / `addContext` inside a `withSpan` / `@span` root after draining the stream.
* **Functions that make more than one LLM call capture only one of them.** When a BAML function issues multiple HTTP calls (retry policies, `fallback` clients, round-robin clients), the captured prompt, model, and provider come from the selected call (or the first one if none is marked selected). Token usage is taken from the Collector's aggregate, so on a fallback chain the recorded `model` / `provider` may not correspond to the call that produced the tokens.

## TypeScript

### Installation

```bash theme={null}
npm install @bitfab/sdk @boundaryml/baml
```

### Method Signature

```typescript theme={null}
// Form 1: bamlClient passed in constructor
bitfab.wrapBAML(method: Function): WrappedBamlFn
bitfab.wrapBAML(method: Function, options: WrapBAMLOptions): WrappedBamlFn

// Form 2: bamlClient passed at call site
bitfab.wrapBAML(bamlClient: unknown, method: Function): WrappedBamlFn
bitfab.wrapBAML(bamlClient: unknown, method: Function, options: WrapBAMLOptions): WrappedBamlFn
```

**Parameters:**

* `method` (Function, required) - The BAML method to wrap (e.g., `b.ClassifyText`)
* `bamlClient` (unknown, optional) - The BAML client instance. Required if not passed in the `Bitfab` constructor
* `options` (WrapBAMLOptions, optional) - Configuration options

**WrapBAMLOptions:**

* `onCollector?: (collector: unknown) => void` - Callback fired after each invocation with the BAML Collector instance

**Returns:** A `WrappedBamlFn` - an async function with the same signature as the original, plus a `.collector` property.

### Usage

#### Constructor-Based (Recommended)

```typescript theme={null}
import { Bitfab } from "@bitfab/sdk"
import { b } from "./baml_client"

const bitfab = new Bitfab({
  apiKey: process.env.BITFAB_API_KEY,
  bamlClient: b,
})

const tracedClassify = bitfab.withSpan(
  "classify",
  { type: "llm" },
  bitfab.wrapBAML(b.ClassifyText),
)

const result = await tracedClassify("Hello world")
```

#### Explicit Client

```typescript theme={null}
const bitfab = new Bitfab({ apiKey: process.env.BITFAB_API_KEY })

const tracedClassify = bitfab.withSpan(
  "classify",
  { type: "llm" },
  bitfab.wrapBAML(b, b.ClassifyText),
)
```

#### Accessing the BAML Collector

The wrapped function exposes a `.collector` property containing the BAML `Collector` from the most recent call:

```typescript theme={null}
const tracedClassify = bitfab.withSpan(
  "classify",
  { type: "llm" },
  bitfab.wrapBAML(b.ClassifyText),
)

await tracedClassify("Hello world")

const collector = tracedClassify.collector // BAML Collector instance
```

The `.collector` is `null` before the first call or if `@boundaryml/baml` is not installed.

#### `onCollector` Callback

For more control, pass an `onCollector` callback:

```typescript theme={null}
const tracedClassify = bitfab.withSpan(
  "classify",
  { type: "llm" },
  bitfab.wrapBAML(b.ClassifyText, {
    onCollector: (collector) => {
      console.log("BAML collector:", collector)
    },
  }),
)
```

With the explicit client form, options go in the third argument:

```typescript theme={null}
bitfab.wrapBAML(b, b.ClassifyText, {
  onCollector: (collector) => { /* ... */ },
})
```

If the callback throws, the error is silently caught and never crashes your application.

### Error Handling

If `@boundaryml/baml` is not installed, `wrapBAML` falls back to calling the method directly on the BAML client without instrumentation. Metadata extraction errors are silently caught.

## Python

### Installation

```bash theme={null}
pip install bitfab-py baml-py
```

### Method Signature

```python theme={null}
# Form 1: baml_client passed in constructor
bitfab.wrap_baml(method: Callable, *, on_collector: Callable | None = None) -> Callable

# Form 2: baml_client passed at call site
bitfab.wrap_baml(baml_client: Any, method: Callable, *, on_collector: Callable | None = None) -> Callable
```

**Parameters:**

* `method` (Callable, required) - The BAML method to wrap (e.g., `b.ClassifyText`)
* `baml_client` (Any, optional) - The BAML client instance. Required if not passed in the `Bitfab` constructor
* `on_collector` (Callable, optional, keyword-only): Callback fired after each invocation with the BAML `Collector` instance

**Returns:** An async wrapper with the same signature as the original method, plus a `.collector` attribute holding the most recent call's `Collector` (`None` before the first call or if `baml-py` is not installed).

### Usage

#### Constructor-Based (Recommended)

```python theme={null}
import os
from bitfab import Bitfab
from baml_client import b

bitfab = Bitfab(api_key=os.environ["BITFAB_API_KEY"], baml_client=b)

@bitfab.span("classify", type="llm")
async def classify(text: str):
    return await bitfab.wrap_baml(b.ClassifyText)(text=text)

result = await classify("Hello world")
```

#### Explicit Client

```python theme={null}
bitfab = Bitfab(api_key=os.environ["BITFAB_API_KEY"])

@bitfab.span("classify", type="llm")
async def classify(text: str):
    return await bitfab.wrap_baml(b, b.ClassifyText)(text=text)
```

#### Accessing the BAML Collector

The wrapper exposes a `.collector` attribute with the BAML `Collector` from the most recent call, and accepts an `on_collector` callback fired after each invocation:

```python theme={null}
classify = bitfab.wrap_baml(
    b.ClassifyText,
    on_collector=lambda collector: print("BAML collector:", collector),
)

@bitfab.span("classify", type="llm")
async def run(text: str):
    return await classify(text=text)

await run("Hello world")

collector = classify.collector  # BAML Collector instance (None before first call)
```

If the callback throws, the error is silently caught and never crashes your application.

### Error Handling

If `baml-py` is not installed, `wrap_baml` falls back to calling the method directly on the BAML client without instrumentation. Metadata extraction errors are silently caught.
