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

# Overview

# Overview

This page explains how to use Basalt's Experiments API with Python. Experiments help you organize, track, and compare different approaches in your AI workflows.

## Installation

Ensure you have the Basalt SDK installed:

```bash theme={null}
pip install basalt-sdk
```

## Client Initialization

Access the experiments API through the `experiments` property on the main `Basalt` client:

```python theme={null}
from basalt import Basalt

basalt = Basalt(api_key="your-api-key")
```

<Warning>
  Create **one** `Basalt` instance per process and reuse it for the entire
  application lifetime.  `shutdown()` permanently destroys the global
  OpenTelemetry `TracerProvider` — never call it between loop iterations or
  between requests.  See [Observability Concepts](/v1/observabilite/python/concepts)
  for details.
</Warning>

## Creating Experiments

You can create experiments programmatically to register new tests or variants.

### Synchronous Creation

```python theme={null}
experiment = basalt.experiments.create_sync(
    feature_slug="llm-routing",
    name="Model Selection Experiment"
)

print(f"Created experiment: {experiment.id}")
```

### Asynchronous Creation

```python theme={null}
import asyncio

async def create_experiment():
    experiment = await basalt.experiments.create(
        feature_slug="llm-routing",
        name="Model Selection Experiment"
    )
    return experiment

asyncio.run(create_experiment())
```

## Using Experiments with Observability

The most common use case is attaching an experiment context to your traces. This allows you to slice and dice your observability data by experiment.

```python theme={null}
from basalt.observability import start_observe, observe

@start_observe(
    feature_slug="ab-test",
    name="experiment.variant_a",
    experiment="exp-456", # The ID of the experiment
    identity={
        "organization": {"id": "123", "name": "ACME"},
        "user": {"id": "456", "name": "John Doe"}
    }
)
def run_variant_a():
    # Experiment metadata is automatically attached to the root span
    observe.set_input({"variant": "A", "model": "gpt-4o"})
    
    # ... your logic ...
    
    return "result"
```

### Experiments in a Loop

When processing multiple items under one experiment, each `start_observe()` call
naturally creates a **separate trace** — the context manager cleans up the
OpenTelemetry context on exit, so the next iteration starts fresh.

```python theme={null}
experiment = basalt.experiments.create_sync(
    feature_slug="support-ticket",
    name="Ticket Analysis",
)

for ticket in tickets:
    with start_observe(
        name="analyse_ticket",
        feature_slug="support-ticket",
        experiment=experiment,
    ) as span:
        span.set_input({"ticket": ticket})
        result = process(ticket)
        observe.set_output({"result": result})

basalt.shutdown()  # Once, at the end
```

<Warning>
  Do **not** call `basalt.shutdown()` inside the loop or recreate the `Basalt`
  client per iteration.  `shutdown()` permanently kills the `TracerProvider` —
  all subsequent traces would be silently dropped.
</Warning>
