Skip to main content
Braintrust maps OpenTelemetry 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 and log storage. For export setup, see Send traces and logs.

Span attributes and events

Braintrust maps GenAI attributes and events, OpenInference attributes, and explicit braintrust.* attributes to span fields.

GenAI attributes

Braintrust implements the OpenTelemetry GenAI semantic conventions. When you send traces with these attributes, they are automatically mapped to Braintrust fields. gen_ai.evaluation.name supplies the name in scores.<name>, and gen_ai.evaluation.score.value supplies its value. The name must be nonempty. The score must be a finite number from 0 to 1, inclusive. Numeric strings are accepted. Invalid values are not mapped to scores.
For self-hosted deployments, this score validation requires data plane v2.16.0 or later.

GenAI events

Braintrust also extracts messages and scores from GenAI span events. These events follow the OpenTelemetry GenAI semantic conventions for events: 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 associated with the span. Mapping a GenAI event to span fields does not remove the event record.

OpenInference attributes

Spans with openinference.span.kind are mapped using OpenInference semantic conventions. These include traces from OpenInference instrumentors for LangChain, LangGraph, LlamaIndex, DSPy, and the OpenAI Agents SDK. For self-hosted deployments, OpenInference mapping requires data plane v2.16.0 or later. Collections use flattened, indexed attributes, such as llm.input_messages.0.message.role and llm.input_messages.0.message.content.

Span fields

When converting input messages, Braintrust prepends a system prompt from llm.invocation_parameters if no system or developer message is present. If media cannot be represented as chat messages, Braintrust preserves the original OpenInference messages in input or output instead.

Metadata

Unmapped attributes remain in metadata. Mapped attributes follow the attribute preservation settings.

Token usage

Token metrics are extracted only from LLM and EMBEDDING spans to avoid counting aggregate usage on chain or agent spans twice. prompt_tokens includes cached tokens.

Scores

Braintrust maps span-scoped annotations and evaluations to scores on the span carrying the attributes: Each entry must have a nonempty name and a finite score from 0 to 1, inclusive. Numeric strings are accepted. Missing, nonnumeric, or out-of-range values are not mapped to scores. Names, labels, and explanations remain in metadata. Trace- and session-scoped feedback, such as trace.annotations.* or session.evaluations.*, remains in metadata and is not mapped to span scores.

Braintrust attributes

You can also use the braintrust namespace to set fields in Braintrust directly: 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
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.

Log records

Each OTLP log record becomes a separate row with span_attributes.type set to log: 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. Records from Claude Code are the exception: Braintrust maps their prompts, responses, and errors to input, output, and error. Claude Code API request records get token metrics only when they have no trace ID. Traced requests already carry their tokens on the matching LLM span. See Trace Claude Code with OpenTelemetry. Each record has a unique row id. Its trace and span IDs determine how it connects to a trace:
  • Valid trace and span IDs: Shares its owning span’s span_id and trace’s root_span_id.
  • Trace ID only: Joins that trace with a generated span ID.
  • No trace ID: Receives its own span and root IDs.

Span events

OpenTelemetry span events received through /otel/v1/traces, including GenAI events, are also stored as separate log rows. Each 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. Exception details populate the owning span’s error field, not the event row’s. Braintrust derives span_attributes.log_level for event rows in this order:
  1. Map a severity_number event attribute from 1 to 24 using the log record severity ranges.
  2. Otherwise, use the first of severity_text, log.level, or level that matches a log level, following the same text rules as log records.
  3. If no attribute matches, use error for events named exception. Other events leave span_attributes.log_level unset.
For example, Rust tracing-opentelemetry sets level on every event.
For self-hosted deployments, span event log-level inference requires data plane v2.16.0 or later.

Log levels and exceptions

The severity number maps to span_attributes.log_level: Filter on span_attributes.log_level to find messages by severity, or on error to find records with exception details. An error-level message alone does not populate error.
If the severity number is absent, 0, or outside the ranges above, Braintrust checks the severity text:
  • Braintrust log levels match regardless of case.
  • Warning maps to warn, and Critical maps to fatal.
  • Unrecognized text, such as verbose, leaves the log level unset.
The original severity number and text remain in context.otel.log.
Braintrust populates error from nonempty exception attributes:
  • exception.stacktrace
  • exception.type
  • exception.message
For self-hosted deployments, numeric severity mapping and exception-only error behavior require data plane v2.15.0 or later, and severity-text fallback requires data plane v2.16.0 or later.

Resources