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

# OpenTelemetry attributes and events

> Map OpenTelemetry span attributes and events to Braintrust fields and understand how log records are stored

Braintrust maps [OpenTelemetry](https://opentelemetry.io/docs/) span attributes and events into inputs, outputs, metrics, and other fields. Log records preserve their message bodies and attributes separately. Use this reference to understand [span mappings](#span-attributes-and-events) and [log storage](#log-records). For export setup, see [Send traces and logs](/docs/integrations/sdk-integrations/opentelemetry/send-traces-and-logs).

## Span attributes and events

Use GenAI conventions for standard model-call data or Braintrust attributes to set Braintrust fields directly. Recognized GenAI span events can also supply message data.

### GenAI attributes

Braintrust implements the [OpenTelemetry GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md). When you send traces with these attributes, they are automatically mapped to Braintrust fields.

| Attribute                                  | Braintrust Field                       | Description                                                                                                                                                                                                                                                                                     |
| ------------------------------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gen_ai.input.messages`                    | `input`                                | The chat history provided to the model as an input. Messages must be structured according to the [OpenTelemetry GenAI input messages attribute specification](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/registry/attributes/gen-ai.md#gen-ai-input-messages). |
| `gen_ai.prompt`                            | `input`                                | User message (string). If you have an array of messages, you'll need to use `gen_ai.prompt_json` (see below) or set flattened attributes like `gen_ai.prompt.0.role` or `gen_ai.prompt.0.content`.                                                                                              |
| `gen_ai.prompt_json`                       | `input`                                | A JSON-serialized string containing an array of [OpenAI messages](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create).                                                                                                                          |
| `gen_ai.output.messages`                   | `output`                               | Messages returned by the model. Messages must be structured according to the [OpenTelemetry GenAI output messages attribute specification](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/registry/attributes/gen-ai.md#gen-ai-output-messages).                   |
| `gen_ai.completion`                        | `output`                               | Assistant message (string). Note that if you have an array of messages, you'll need to use `gen_ai.completion_json` (see below) or set flattened attributes like `gen_ai.completion.0.role` or `gen_ai.completion.0.content`.                                                                   |
| `gen_ai.completion_json`                   | `output`                               | A JSON-serialized string containing an array of [OpenAI messages](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create).                                                                                                                          |
| `gen_ai.request`                           | `metadata.*`                           | A JSON object or flattened attributes containing model parameters. The `model` parameter is cleaned of provider prefixes (e.g., "openai/gpt-4o" becomes "gpt-4o").                                                                                                                              |
| `gen_ai.request.model`                     | `metadata.model`                       | The model name (e.g. "gpt-4o"). Provider prefixes like "openai/", "anthropic/", "google/" are automatically removed.                                                                                                                                                                            |
| `gen_ai.request.max_tokens`                | `metadata.max_tokens`                  | Maximum tokens to generate.                                                                                                                                                                                                                                                                     |
| `gen_ai.request.temperature`               | `metadata.temperature`                 | Sampling temperature.                                                                                                                                                                                                                                                                           |
| `gen_ai.request.top_p`                     | `metadata.top_p`                       | Nucleus sampling parameter.                                                                                                                                                                                                                                                                     |
| `gen_ai.operation.name`                    | `span_attributes.type`                 | The operation type. Value "chat" maps to type "llm", "execute\_tool" maps to type "tool".                                                                                                                                                                                                       |
| `gen_ai.agent.tools`                       | `metadata.tools`                       | A JSON-serialized array of tool names available to the agent. Tool names are automatically converted into tool definition objects with `type: "function"` and basic schemas.                                                                                                                    |
| `gen_ai.tool.name`                         | `metadata.tools`                       | The name of the tool being executed. Automatically converted into a tool definition object. Also sets `span_attributes.type` to "tool".                                                                                                                                                         |
| `gen_ai.usage`                             | `metrics.*`                            | A JSON object containing token usage. Can include `prompt_tokens`, `completion_tokens`, `input_tokens`, `output_tokens`, and `total_tokens`.                                                                                                                                                    |
| `gen_ai.usage.prompt_tokens`               | `metrics.prompt_tokens`                | Input tokens (preferred field name).                                                                                                                                                                                                                                                            |
| `gen_ai.usage.completion_tokens`           | `metrics.completion_tokens`            | Output tokens (preferred field name).                                                                                                                                                                                                                                                           |
| `gen_ai.usage.input_tokens`                | `metrics.prompt_tokens`                | Input tokens (alternative field name, normalized to `prompt_tokens`).                                                                                                                                                                                                                           |
| `gen_ai.usage.output_tokens`               | `metrics.completion_tokens`            | Output tokens (alternative field name, normalized to `completion_tokens`).                                                                                                                                                                                                                      |
| `gen_ai.usage.total_tokens`                | `metrics.tokens`                       | Total tokens (normalized to `tokens`). If not provided, automatically calculated from `prompt_tokens` + `completion_tokens`.                                                                                                                                                                    |
| `gen_ai.usage.cache_creation.input_tokens` | `metrics.prompt_cache_creation_tokens` | Input tokens written to a provider-managed cache.                                                                                                                                                                                                                                               |
| `gen_ai.usage.cache_read.input_tokens`     | `metrics.prompt_cached_tokens`         | Input tokens served from a provider-managed cache.                                                                                                                                                                                                                                              |

### GenAI events

In addition to attributes, Braintrust also processes GenAI events on spans to extract input/output messages. These events follow the [OpenTelemetry GenAI semantic conventions for events](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-events.md):

| Event Name                 | Field    | Description                                                                                                                    |
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `gen_ai.user.message`      | `input`  | User message event. Content is extracted from the `content` attribute (supports both string and JSON array format).            |
| `gen_ai.choice`            | `output` | Model response event. Message is extracted from the `message` attribute and can include both text content and tool calls.      |
| `gen_ai.assistant.message` | `output` | Assistant message event. Content is extracted from the `content` attribute.                                                    |
| `gen_ai.tool.message`      | `input`  | Tool result event. Content is extracted from the `content` attribute and associated with the tool call via the `id` attribute. |

The message events listed above are processed in the order they appear in the span. When they produce a nonempty input or output message list, that list takes precedence over the corresponding attribute-derived field.

Each event is also stored as a separate [log row](#log-records) associated with the span. Mapping a GenAI event into the span's input or output does not remove the event record.

### Braintrust attributes

You can also use the `braintrust` namespace to set fields in Braintrust directly:

| Attribute                    | Braintrust Field  | Notes                                                                                                                                                                                                                                                                             |
| ---------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `braintrust.input`           | `input`           | The input value. Plain strings are stored without conversion to chat messages. Use `braintrust.input_json` for JSON-encoded objects or arrays, or flattened attributes such as `braintrust.input.0.role`.                                                                         |
| `braintrust.input_json`      | `input`           | A JSON-serialized input value, decoded without wrapping it in chat messages.                                                                                                                                                                                                      |
| `braintrust.output`          | `output`          | The output value. Plain strings are stored without conversion to chat messages. Use `braintrust.output_json` for JSON-encoded objects or arrays, or flattened attributes such as `braintrust.output.0.role`.                                                                      |
| `braintrust.output_json`     | `output`          | A JSON-serialized output value, decoded without wrapping it in chat messages.                                                                                                                                                                                                     |
| `braintrust.metadata`        | `metadata`        | A JSON-serialized dictionary with string keys. Alternatively, you can use flattened attribute names, like `braintrust.metadata.model` or `braintrust.metadata.temperature`. If you include `tools`, you must provide full tool definition objects.                                |
| `braintrust.context_json`    | `context`         | A JSON-serialized dictionary that merges into the span's context object. Braintrust automatically populates `context.span_origin` from OTEL resource attributes and instrumentation scope. Use this attribute to set additional context fields or override auto-derived values.   |
| `braintrust.metrics`         | `metrics`         | A JSON-serialized dictionary with string keys. Alternatively, you can use flattened attribute names, like `braintrust.metrics.prompt_tokens` or `braintrust.metrics.completion_tokens`.                                                                                           |
| `braintrust.scores`          | `scores`          | A JSON-serialized dictionary with string keys, where values are scores for the span. Alternatively, you can use flattened attribute names, like `braintrust.scores.accuracy` or `braintrust.scores.relevance`.                                                                    |
| `braintrust.expected`        | `expected`        | The expected output for the span. Can be any value (string, number, object, etc.).                                                                                                                                                                                                |
| `braintrust.expected_json`   | `expected`        | A JSON-serialized string containing the expected output. Use this when you need to pass complex objects or arrays as the expected value.                                                                                                                                          |
| `braintrust.tags`            | `tags`            | An array of strings that can be set on any span. Tags from all spans in a trace are aggregated together.                                                                                                                                                                          |
| `braintrust.span_attributes` | `span_attributes` | A JSON-serialized dictionary with string keys. Alternatively, you can use flattened attribute names, like `braintrust.span_attributes.type` or `braintrust.span_attributes.name`. The `type` field can be one of: `"llm"`, `"task"`, `"tool"`, `"eval"`, `"score"`, `"function"`. |

Fields mapped from `braintrust.*` attributes are removed from metadata and stored in the corresponding Braintrust fields. Nonempty input or output arrays whose elements have valid chat roles are recognized as LLM message arrays.

### Attribute example

This example sets GenAI and Braintrust attributes using flattened names and JSON-encoded values:

```python Python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import json
import os

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

BRAINTRUST_API_URL = os.environ.get("BRAINTRUST_API_URL", "https://api.braintrust.dev")
BRAINTRUST_API_KEY = os.environ.get("BRAINTRUST_API_KEY", "<Your API Key>")
PROJECT_ID = "<Your Project ID>"

provider = TracerProvider()
processor = BatchSpanProcessor(
    OTLPSpanExporter(
        endpoint=f"{BRAINTRUST_API_URL}/otel/v1/traces",
        headers={"Authorization": f"Bearer {BRAINTRUST_API_KEY}", "x-bt-parent": f"project_id:{PROJECT_ID}"},
    )
)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)

# Export a span with flattened attribute names.
with tracer.start_as_current_span("GenAI Attributes") as span:
    span.set_attribute("gen_ai.prompt.0.role", "system")
    span.set_attribute("gen_ai.prompt.0.content", "You are a helpful assistant.")
    span.set_attribute("gen_ai.prompt.1.role", "user")
    span.set_attribute("gen_ai.prompt.1.content", "What is the capital of France?")

    span.set_attribute("gen_ai.completion.0.role", "assistant")
    span.set_attribute("gen_ai.completion.0.content", "The capital of France is Paris.")

    span.set_attribute("gen_ai.request.model", "gpt-4o-mini")
    span.set_attribute("gen_ai.request.temperature", 0.5)
    span.set_attribute("gen_ai.usage.prompt_tokens", 10)
    span.set_attribute("gen_ai.usage.completion_tokens", 30)

# Export a span using JSON-serialized attributes.
with tracer.start_as_current_span("GenAI JSON-Serialized Attributes") as span:
    span.set_attribute(
        "gen_ai.prompt_json",
        json.dumps(
            [
                {"role": "system", "content": "You are a helpful assistant."},
                {"role": "user", "content": "What is the capital of Italy?"},
            ]
        ),
    )
    span.set_attribute(
        "gen_ai.completion_json",
        json.dumps(
            [
                {"role": "assistant", "content": "The capital of Italy is Rome."},
            ]
        ),
    )

# Export a span using the `braintrust` namespace.
with tracer.start_as_current_span("Braintrust Attributes") as span:
    span.set_attribute("braintrust.input.0.role", "system")
    span.set_attribute("braintrust.input.0.content", "You are a helpful assistant.")
    span.set_attribute("braintrust.input.1.role", "user")
    span.set_attribute("braintrust.input.1.content", "What is the capital of Libya?")

    span.set_attribute("braintrust.output.0.role", "assistant")
    span.set_attribute("braintrust.output.0.content", "The capital of Libya is Tripoli.")

    span.set_attribute("braintrust.metadata.model", "gpt-4o-mini")
    span.set_attribute("braintrust.metadata.country", "Libya")
    span.set_attribute("braintrust.metrics.prompt_tokens", 10)
    span.set_attribute("braintrust.metrics.completion_tokens", 20)

# Export a span using JSON-serialized `braintrust` attributes.
with tracer.start_as_current_span("Braintrust JSON-Serialized Attributes") as span:
    span.set_attribute(
        "braintrust.input_json",
        json.dumps(
            [
                {"role": "system", "content": "You are a helpful assistant."},
                {"role": "user", "content": "What is the capital of Argentina?"},
            ]
        ),
    )
    span.set_attribute(
        "braintrust.output_json",
        json.dumps(
            [
                {"role": "assistant", "content": "The capital of Argentina is Buenos Aires."},
            ]
        ),
    )
    span.set_attribute(
        "braintrust.metadata",
        json.dumps({"model": "gpt-4o-mini", "country": "Argentina"}),
    )
    span.set_attribute(
        "braintrust.metrics",
        json.dumps({"prompt_tokens": 15, "completion_tokens": 45}),
    )
    span.set_attribute(
        "braintrust.expected_json",
        json.dumps([{"role": "assistant", "content": "The capital of Argentina is Buenos Aires."}]),
    )
    span.set_attribute(
        "braintrust.scores",
        json.dumps({"accuracy": 1.0, "relevance": 0.95}),
    )
```

<span id="why-are-some-attributes-missing-from-metadata" />

<Accordion title="Why are some attributes missing from metadata?">
  For Braintrust-hosted organizations, source attributes such as `ai.prompt`, `gen_ai.input.messages`, and `llm.input_messages` are removed from `metadata` once Braintrust has mapped them to a structured field (`input`, `output`, `metrics`, etc.). The data itself can be found on the structured field it was mapped into.

  Self-hosted deployments behave the same way when the `STRIP_OTEL_ATTRIBUTES_FROM_METADATA` environment variable is set on the `api-ts` service.

  To keep the raw attributes on `metadata` for a specific span, set the `braintrust.otel.preserve_attributes` attribute to `true` on that span.

  For details, see [Strip OTel attributes from metadata](/docs/kb/strip-otel-attributes-from-metadata).
</Accordion>

## Log records

Each OTLP record becomes a separate row with `span_attributes.type` set to `log`:

| OTLP data                         | Braintrust field                                                        |
| --------------------------------- | ----------------------------------------------------------------------- |
| Message body                      | `output`                                                                |
| Record attributes                 | `metadata`, except attributes consumed for routing.                     |
| Resource and scope                | `context.otel.resource` and `context.otel.scope`                        |
| Original severity number and text | `context.otel.log.severity_number` and `context.otel.log.severity_text` |

Log attributes do not undergo the GenAI mapping used for trace spans. For example, a `gen_ai.usage.input_tokens` attribute on a log remains in `metadata` rather than becoming a token metric.

A record with valid trace and span IDs shares its owning span's `span_id` and trace's `root_span_id`, while retaining its own unique row `id`. A record with a trace ID only joins that trace with a generated span ID. A record without a trace ID receives its own span and root IDs.

OpenTelemetry span events received through `/otel/v1/traces`, including GenAI events, are also stored as separate log rows. Each event row shares its span's `span_id` and `root_span_id` and has its own row `id`. The event name is stored in `output`, and its attributes are stored in `metadata`. Recognized GenAI events can also populate fields on the owning span, as described in [GenAI events](#genai-events).

### Log levels and exceptions

On Braintrust-hosted deployments, the severity number maps to `span_attributes.log_level`:

| OTel severity number | Braintrust log level |
| -------------------- | -------------------- |
| 1–4                  | `trace`              |
| 5–8                  | `debug`              |
| 9–12                 | `info`               |
| 13–16                | `warn`               |
| 17–20                | `error`              |
| 21–24                | `fatal`              |

If the severity number is absent, `0`, or outside these ranges, Braintrust does not derive a log level. The original severity number and text remain in `context.otel.log`.

On Braintrust-hosted deployments, the top-level `error` field is populated from nonempty exception attributes: `exception.stacktrace`, `exception.type`, or `exception.message`. Severity and message body alone do not populate it. Use `span_attributes.log_level` to find error-level messages, and `error` to find records carrying exception details.

<Note>
  The canonical log-level mapping and exception-only `error` behavior require data plane v2.15.0 or later. On earlier versions, inspect the original severity fields in `context.otel.log`. Error-level severity can populate `error` even without exception attributes.
</Note>

## Resources

* [Send traces and logs](/docs/integrations/sdk-integrations/opentelemetry/send-traces-and-logs).
* [Link Braintrust and OpenTelemetry spans](/docs/integrations/sdk-integrations/opentelemetry/link-spans).
