setPrompt or addContext calls. Under an opt-out trace root, BAML calls are recorded on their own (see below). With opt-in spans, wrap a BAML method with wrapBAML / wrap_baml and Bitfab extracts everything onto the current span.
Canonical signatures: TypeScript wrapBAML · Python wrap_baml
Client capture off applies to BAML. With
captureEnabled: false in TypeScript or capture_enabled=False in Python, wrapped BAML functions continue to run but send no span, and bitfab.call() sends no internal trace for its local execution. Replay items and seedTrace / seed_trace still record when explicitly invoked.Under a trace root (opt-out tracing)
Beneath awithTrace() / trace() root, BAML calls are recorded without wrapBAML / wrap_baml. Each call on your generated BAML client (b.ClassifyText(...), sync or async) becomes its own llm span named after the client class and function, such as BamlAsyncClient.ClassifyText, carrying the same prompt, model, provider, token counts, and duration listed under What Gets Captured.
- TypeScript needs the SDK’s build transform in the server build, the same requirement as the rest of opt-out tracing. The transform recognizes files BAML generated, records only the client’s function calls from them, and skips the rest of the generated code (
withOptions,b.stream, parsers, request builders). - Python needs Python 3.12+, as opt-out tracing does. The generated client must already be imported when the root starts, which a module-level
from baml_client import bguarantees. It is recorded whether or notbaml_clientsits inside the root’s package. - Your own collectors keep working. Bitfab adds its collector beside any you pass in the call options or set with
b.withOptions({ collector })/b.with_options(collector=...). excludematches the function name (ClassifyText). Excluding it runs the call without recording it. Replay mocking follows the root’smockOnReplay/mock_on_replaydefault.- Existing
wrapBAML/wrap_bamlcalls are safe. Under a root, the prompt and usage land on the BAML call’s span, never also on the function that called it. withNode/node()on a BAML call configures that call’s one span. Pass the bound method or awrapBAML/wrap_bamlwrapper, and the node’s name, replay mocking, and other options apply to the BAML call’s span, which keeps its prompt and usage. Its type staysllmunless the node sets one. No second span is opened beneath it.
- A node on your own function stays a separate span. When the node wraps a function of yours that calls BAML, the trace shows your function’s span with the BAML call’s
llmspan beneath it, and the prompt and usage appear only on the BAML span. - Outside a trace root, BAML calls run exactly as before and record nothing.
b.stream.* calls are not recorded as llm spans.
Supported Languages
Quick Start
What Gets Captured
wrapBAML / wrap_baml creates a BAML Collector, runs the method through a tracked client, then extracts:
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_bamlawaits 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 withsetPrompt/addContextinside awithSpan/@spanroot 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,
fallbackclients, 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 recordedmodel/providermay not correspond to the call that produced the tokens.
TypeScript
Installation
@boundaryml/baml by name at runtime. This import lets the bundler resolve it, and the SDK uses the module it loads. Without it, wrapBAML runs untraced and logs a one-time [bitfab] warning.
Method Signature
method(Function, required) - The BAML method to wrap (e.g.,b.ClassifyText)bamlClient(unknown, optional) - The BAML client instance. Required if not passed in theBitfabconstructoroptions(WrapBAMLOptions, optional) - Configuration options
onCollector?: (collector: unknown) => void- Callback fired after each invocation with the BAML Collector instance
WrappedBamlFn - an async function with the same signature as the original, plus a .collector property.
Usage
Constructor-Based (Recommended)
Explicit Client
Accessing the BAML Collector
The wrapped function exposes a.collector property containing the BAML Collector from the most recent call:
.collector is null before the first call or if @boundaryml/baml is not installed.
onCollector Callback
For more control, pass an onCollector callback:
Error Handling
If@boundaryml/baml cannot be loaded (not installed, or bundled without import "@bitfab/sdk/baml"), wrapBAML calls the method directly on the BAML client without instrumentation, does not call onCollector, and logs a one-time [bitfab] warning. Metadata extraction errors are silently caught.
Python
Installation
Method Signature
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 theBitfabconstructoron_collector(Callable, optional, keyword-only): Callback fired after each invocation with the BAMLCollectorinstance
.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)
Explicit Client
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:
Error Handling
Ifbaml-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.