Skip to main content
Applies to:


Summary Configure tracing and observability for OpenAI Realtime API WebSocket sessions using Braintrust’s OpenTelemetry integration. This approach allows you to log session-based traces, capture event metadata, and maintain full control over event schemas without relying on the Braintrust proxy.

Configuration Steps

Step 1: Configure OpenTelemetry for Braintrust

Set up OpenTelemetry to send traces directly to Braintrust’s OTEL endpoint with proper authentication and project context.
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.braintrust.dev/otel
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer , x-bt-parent=project_name:"

Step 2: Initialize session-based tracing

Create a root span for the entire WebSocket session to track the long-lived connection and session-level metadata.
// Java example using OpenTelemetry
Span sessionSpan = tracer.spanBuilder("voice_interaction")
    .setAttribute("braintrust.input", userSpeechText)
    .setAttribute("braintrust.metadata", JSON.toJson(Map.of(
        "user_id", "user-123",
        "session_id", "session-456",
        "audio_duration_ms", 2500
    )))
    .setAttribute("braintrust.tags", JSON.toJson(List.of("voice", "realtime-api")))
    .startSpan();

Step 3: Log individual events as nested spans

Create child spans for specific WebSocket events like RAG retrieval and LLM generation to maintain granular observability.
// Nested span for RAG retrieval
Span retrievalSpan = tracer.spanBuilder("rag_retrieval")
    .setParent(Context.current().with(sessionSpan))
    .setAttribute("braintrust.metadata", JSON.toJson(Map.of(
        "documents_considered", List.of(
            Map.of("id", "kb_123", "title", "Product FAQ", "relevance", 0.92),
            Map.of("id", "kb_456", "title", "User Guide", "relevance", 0.78)
        ),
        "retrieval_method", "semantic_search"
    )))
    .startSpan();

Step 4: Capture conversation data in root span

Store complete conversation context and metadata in the root span to support custom scorers and analysis tools.
// Store conversation data for scoring
sessionSpan.setAttribute("braintrust.output", llmResponse);
sessionSpan.setAttribute("braintrust.metadata", JSON.toJson(Map.of(
    "conversation_history", conversationMessages,
    "rag_documents", retrievedDocuments,
    "total_tokens", tokenCount,
    "session_duration_ms", sessionDuration
)));

Step 5: Close spans and flush data

Properly end spans when events complete and ensure data is sent to Braintrust.
// End nested spans first
retrievalSpan.end();

// End session span when WebSocket closes
sessionSpan.setAttribute("braintrust.metrics", JSON.toJson(Map.of(
    "total_messages", messageCount,
    "total_duration_ms", sessionDuration
)));
sessionSpan.end();

// Flush telemetry data
OpenTelemetry.getGlobalOpenTelemetry().shutdown();