Send OpenTelemetry Traces to Weave

no
Summary: Ingest OpenTelemetry compatible trace data through a dedicated endpoint

Original Documentation

Documentation Index#

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

Ingest OpenTelemetry compatible trace data through a dedicated endpoint

Overview#

Weave supports ingestion of OpenTelemetry compatible trace data through a dedicated endpoint. This endpoint allows you to send OTLP (OpenTelemetry Protocol) formatted trace data directly to your Weave project.

Endpoint details#

Path: /otel/v1/traces Method: POST Content-Type: application/x-protobuf Base URL: The base URL for the OTel trace endpoint depends on your W&B deployment type:

  • Multi-tenant Cloud:
    https://trace.wandb.ai/otel/v1/traces

  • Dedicated Cloud and Self-Managed instances:
    https://<your-subdomain>.wandb.io/traces/otel/v1/traces

Replace <your-subdomain> with your organization’s unique W&B domain, e.g., acme.wandb.io.

Authentication and routing#

Pass your W&B API key in the wandb-api-key header, then specify the following keys as OpenTelemetry Resource attributes in your TracerProvider class:

  • wandb.entity: Your W&B team or user name.
  • wandb.project: The project name to send traces to.

The following example shows how to configure authentication and project routing:

import os
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.resources import Resource

WANDB_BASE_URL = "https://trace.wandb.ai"
ENTITY = "<your-team-name>"
PROJECT = "<your-project-name>"

OTEL_EXPORTER_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

# Create an API key at https://wandb.ai/settings
WANDB_API_KEY = os.environ["WANDB_API_KEY"]

exporter = OTLPSpanExporter(
    endpoint=OTEL_EXPORTER_OTLP_ENDPOINT,
    headers={"wandb-api-key": WANDB_API_KEY},
)

tracer_provider = trace_sdk.TracerProvider(resource=Resource({
    "wandb.entity": ENTITY,
    "wandb.project": PROJECT,
}))
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { Resource } from "@opentelemetry/resources";

const WANDB_BASE_URL = "https://trace.wandb.ai";
const ENTITY = "<your-team-name>";
const PROJECT = "<your-project-name>";

const OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

// Create an API key at https://wandb.ai/settings
const WANDB_API_KEY = process.env.WANDB_API_KEY!;

const exporter = new OTLPTraceExporter({
  url: OTEL_EXPORTER_OTLP_ENDPOINT,
  headers: { "wandb-api-key": WANDB_API_KEY },
});

const provider = new NodeTracerProvider({
  resource: new Resource({
    "wandb.entity": ENTITY,
    "wandb.project": PROJECT,
  }),
});

Examples#

The following examples show how to send OpenTelemetry traces to Weave using Python and TypeScript.

Before running the code samples below, set the following fields:

  1. WANDB_API_KEY: You can get this from User Settings.
  2. Entity: You can only log traces to the project under an entity that you have access to. You can find your entity name by visiting your W&B dashboard at [https://wandb.ai/home], and checking the Teams field in the left sidebar.
  3. Project Name: Choose a fun name!
  4. OPENAI_API_KEY: You can obtain this from the OpenAI dashboard.

OpenInference Instrumentation#

This example shows how to use the OpenAI instrumentation. There are many more available which you can find in the official repository: https://github.com/Arize-ai/openinference

First, install the required dependencies:

    pip install openai openinference-instrumentation-openai opentelemetry-exporter-otlp-proto-http
    ```
  <span class="tab-end"></span>

  <span class="tab-start" data-tab-title="TypeScript"></span>
```bash
    npm install openai @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/resources @opentelemetry/exporter-trace-otlp-proto @arizeai/openinference-instrumentation-openai @opentelemetry/api
    ```
  <span class="tab-end"></span>
<span class="tab-group-end"></span>

<span class="callout-start" data-callout-type="warning"></span>
  **Performance Recommendation**: Always use `BatchSpanProcessor` instead of `SimpleSpanProcessor` when sending traces to Weave. `SimpleSpanProcessor` exports spans synchronously, potentially impacting the performance of other workloads. These examples illustrate `BatchSpanProcessor`, which is recommended in production because it batches spans asynchronously and efficiently.
<span class="callout-end"></span>

<span class="tab-group-start"></span>
  <span class="tab-start" data-tab-title="Python"></span>
Paste the following code into a Python file such as `openinference_example.py`:

```python
    import os
    import openai
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
    from openinference.instrumentation.openai import OpenAIInstrumentor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    OTEL_EXPORTER_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # Create an API key at https://wandb.ai/settings
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    exporter = OTLPSpanExporter(
        endpoint=OTEL_EXPORTER_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # Optionally, print the spans to the console.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

    def main():
        client = openai.OpenAI(api_key=OPENAI_API_KEY)
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
            max_tokens=20,
            stream=True,
            stream_options={"include_usage": True},
        )
        for chunk in response:
            if chunk.choices and (content := chunk.choices[0].delta.content):
                print(content, end="")

    if __name__ == "__main__":
        main()
    ```

Run the code:

```bash
    python openinference_example.py
    ```
  <span class="tab-end"></span>

  <span class="tab-start" data-tab-title="TypeScript"></span>
The TypeScript implementation of this example contains the following key differences from the Python implementation:

* OpenAI must be imported before registering instrumentation (ESM modules require this).
* Uses `@opentelemetry/exporter-trace-otlp-proto` (protobuf format) instead of the HTTP exporter, since W\&B's endpoint only accepts protobuf.
* Requires explicit `provider.shutdown()` with a delay before shutdown to ensure spans are flushed, since `BatchSpanProcessor` flushes asynchronously.

Paste the following code into a TypeScript file such as `openinference_example.ts`:

```typescript
    // IMPORTANT: Import OpenAI FIRST so instrumentation can patch it
    import OpenAI from "openai";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { resourceFromAttributes } from "@opentelemetry/resources";
    import { OpenAIInstrumentation, isPatched } from "@arizeai/openinference-instrumentation-openai";

    const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // Create an API key at https://wandb.ai/settings
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: OTEL_EXPORTER_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: resourceFromAttributes({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter)
      ],
    });

    provider.register();

    // Register the OpenAI instrumentation with the tracer provider
    const openAIInstrumentation = new OpenAIInstrumentation();
    openAIInstrumentation.setTracerProvider(provider);

    // Manually instrument OpenAI since we're using ESM
    openAIInstrumentation.manuallyInstrument(OpenAI);

    async function main() {
      console.log("OpenAI is patched?", isPatched());

      const client = new OpenAI({ apiKey: OPENAI_API_KEY });

      console.log("Making OpenAI API call...");
      const response = await client.chat.completions.create({
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
        max_tokens: 50,
      });

      console.log("Response:", response.choices[0]?.message?.content);
      console.log("Waiting for spans to flush...");
    }

    (async () => {
      await main();

      // Give spans time to flush
      console.log("Waiting 2 seconds for spans to flush...");
      await new Promise(resolve => setTimeout(resolve, 2000));

      await provider.shutdown(); // flush all pending spans before exit
      console.log("Shutdown complete");
    })();
    ```

Run the code:

```bash
    npx ts-node openinference_example.ts
    ```
  <span class="tab-end"></span>
<span class="tab-group-end"></span>

### OpenLLMetry Instrumentation

The following example shows how to use the OpenAI instrumentation. Additional examples are available at [https://github.com/traceloop/openllmetry/tree/main/packages](https://github.com/traceloop/openllmetry/tree/main/packages).

First, install the required dependencies:

<span class="tab-group-start"></span>
  <span class="tab-start" data-tab-title="Python"></span>
```bash
    pip install openai opentelemetry-instrumentation-openai opentelemetry-exporter-otlp-proto-http
    ```
  <span class="tab-end"></span>

  <span class="tab-start" data-tab-title="TypeScript"></span>
```bash
    npm install openai @traceloop/instrumentation-openai @opentelemetry/sdk-trace-node @opentelemetry/resources @opentelemetry/exporter-trace-otlp-http
    ```
  <span class="tab-end"></span>
<span class="tab-group-end"></span>

<span class="tab-group-start"></span>
  <span class="tab-start" data-tab-title="Python"></span>
Paste the following code into a Python file such as `openllmetry_example.py`. Note that this is the same code as above, except the `OpenAIInstrumentor` is imported from `opentelemetry.instrumentation.openai` instead of `openinference.instrumentation.openai`:

```python
    import os
    import openai
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
    from opentelemetry.instrumentation.openai import OpenAIInstrumentor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    OTEL_EXPORTER_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # Create an API key at https://wandb.ai/settings
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    exporter = OTLPSpanExporter(
        endpoint=OTEL_EXPORTER_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # Optionally, print the spans to the console.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

    def main():
        client = openai.OpenAI(api_key=OPENAI_API_KEY)
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
            max_tokens=20,
            stream=True,
            stream_options={"include_usage": True},
        )
        for chunk in response:
            if chunk.choices and (content := chunk.choices[0].delta.content):
                print(content, end="")

    if __name__ == "__main__":
        main()
    ```

Run the code:

```bash
    python openllmetry_example.py
    ```
  <span class="tab-end"></span>

  <span class="tab-start" data-tab-title="TypeScript"></span>
Paste the following code into a TypeScript file such as `openllmetry_example.ts`. Note that this uses the Traceloop OpenAI instrumentation package:

```typescript
    import OpenAI from "openai";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { Resource } from "@opentelemetry/resources";
    import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
    import { registerInstrumentations } from "@opentelemetry/instrumentation";

    const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // Create an API key at https://wandb.ai/settings
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: OTEL_EXPORTER_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: new Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter),
        // Optionally, print the spans to the console.
        new BatchSpanProcessor(new ConsoleSpanExporter()),
      ],
    });

    provider.register();

    // Register the OpenAI instrumentation with the tracer provider
    const openAIInstrumentation = new OpenAIInstrumentation();
    registerInstrumentations({
      tracerProvider: provider,
      instrumentations: [openAIInstrumentation],
    });

    // Manually instrument OpenAI since we're using ESM
    openAIInstrumentation.manuallyInstrument(OpenAI);

    async function main() {
      const client = new OpenAI({ apiKey: OPENAI_API_KEY });
      const stream = await client.chat.completions.create({
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
        max_tokens: 20,
        stream: true,
      });

      for await (const chunk of stream) {
        const content = chunk.choices[0]?.delta?.content;
        if (content) {
          process.stdout.write(content);
        }
      }
      console.log(); // newline after streaming
    }

    (async () => {
      await main();

      // Give spans time to flush
      await new Promise(resolve => setTimeout(resolve, 2000));

      await provider.shutdown(); // flush all pending spans before exit
    })();
    ```

Run the code:

```bash
    npx ts-node openllmetry_example.ts
    ```
  <span class="tab-end"></span>
<span class="tab-group-end"></span>

### Without Instrumentation

If you would prefer to use OTel directly instead of an instrumentation package, you may do so. Span attributes will be parsed according to the OpenTelemetry semantic conventions described at [https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/).

First, install the required dependencies:

<span class="tab-group-start"></span>
  <span class="tab-start" data-tab-title="Python"></span>
```bash
    pip install openai opentelemetry-sdk opentelemetry-api opentelemetry-exporter-otlp-proto-http
    ```
  <span class="tab-end"></span>

  <span class="tab-start" data-tab-title="TypeScript"></span>
```bash
    npm install openai @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/resources @opentelemetry/exporter-trace-otlp-http
    ```
  <span class="tab-end"></span>
<span class="tab-group-end"></span>

<span class="tab-group-start"></span>
  <span class="tab-start" data-tab-title="Python"></span>
Paste the following code into a Python file such as `opentelemetry_example.py`:

```python
    import json
    import os
    import openai
    from opentelemetry import trace
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    OTEL_EXPORTER_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # Create an API key at https://wandb.ai/settings
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    # Configure the OTLP exporter
    exporter = OTLPSpanExporter(
        endpoint=OTEL_EXPORTER_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # Optionally, print the spans to the console.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    # Set the tracer provider
    trace.set_tracer_provider(tracer_provider)

    # Create a tracer from the global tracer provider
    tracer = trace.get_tracer(__name__)

    def my_function():
        with tracer.start_as_current_span("outer_span") as outer_span:
            client = openai.OpenAI()
            input_messages = [{"role": "user", "content": "Describe OTel in a single sentence."}]
            outer_span.set_attribute("input.value", json.dumps(input_messages))
            outer_span.set_attribute("gen_ai.system", "openai")
            response = client.chat.completions.create(
                model="gpt-3.5-turbo",
                messages=input_messages,
                max_tokens=20,
                stream=True,
                stream_options={"include_usage": True},
            )
            out = ""
            for chunk in response:
                if chunk.choices and (content := chunk.choices[0].delta.content):
                    out += content
            outer_span.set_attribute("output.value", json.dumps({"content": out}))

    if __name__ == "__main__":
        my_function()
    ```

Run the code:

```bash
    python opentelemetry_example.py
    ```
  <span class="tab-end"></span>

  <span class="tab-start" data-tab-title="TypeScript"></span>
Paste the following code into a TypeScript file such as `opentelemetry_example.ts`:

```typescript
    import OpenAI from "openai";
    import { trace } from "@opentelemetry/api";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
    import { Resource } from "@opentelemetry/resources";

    const OPENAI_API_KEY = "YOUR_OPENAI_API_KEY";
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const OTEL_EXPORTER_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // Create an API key at https://wandb.ai/settings
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: OTEL_EXPORTER_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: new Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter),
        // Optionally, print the spans to the console.
        new BatchSpanProcessor(new ConsoleSpanExporter()),
      ],
    });

    provider.register();

    // Creates a tracer from the global tracer provider
    const tracer = trace.getTracer("my-app");

    async function myFunction() {
      const span = tracer.startSpan("outer_span");

      try {
        const client = new OpenAI({ apiKey: OPENAI_API_KEY });
        const inputMessages = [
          { role: "user" as const, content: "Describe OTel in a single sentence." },
        ];

        // This will only appear in the side panel
        span.setAttribute("input.value", JSON.stringify(inputMessages));
        
        // This follows conventions and will appear in the dashboard
        span.setAttribute("gen_ai.system", "openai");

        const stream = await client.chat.completions.create({
          model: "gpt-3.5-turbo",
          messages: inputMessages,
          max_tokens: 20,
          stream: true,
        });

        let output = "";
        for await (const chunk of stream) {
          const content = chunk.choices[0]?.delta?.content;
          if (content) {
            output += content;
          }
          }

        // This will only appear in the side panel
        span.setAttribute("output.value", JSON.stringify({ content: output }));
      } finally {
        span.end();
      }
    }

    myFunction();
    ```

Run the code:

```bash
    npx ts-node opentelemetry_example.ts
    ```
  <span class="tab-end"></span>
<span class="tab-group-end"></span>

The span attribute prefixes `gen_ai` and `openinference` are used to determine which convention to use, if any, when interpreting the trace. If neither key is detected, then all span attributes are visible in the trace view. The full span is available in the side panel when you select a trace.

## Use an OpenTelemetry Collector

The examples above export traces directly from your application to Weave. In production, you can use an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) as an intermediary between your application and Weave. The collector receives traces from your app, then forwards them to one or more backends.

### Set up a collector

The following example shows how to:

* Set up a Docker configuration file that deploys a local server (collector) that listens for OTLP traces, batches them, and forwards them to Weave.
* Locally run the collector using Docker.
* Send a basic call to OpenAI that forwards traces to the collector running in the Docker container.

To use a collector, first create a `collector-config.yaml` file that configures the collector to receive OTLP traces and export them to Weave:

```yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

exporters:
  otlphttp/weave:
    endpoint: ${env:WANDB_OTLP_ENDPOINT}
    headers:
      wandb-api-key: ${env:WANDB_API_KEY}
    sending_queue:
      batch:

processors:
  resource:
    attributes:
      - key: wandb.entity # Resource attributes field
        value: ${env:DEFAULT_WANDB_ENTITY}  # Value to inject
        action: insert # Inject only if not already present
      - key: wandb.project
        value: ${env:DEFAULT_WANDB_PROJECT}
        action: insert 

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource]
      exporters: [otlphttp/weave]

This configuration file:

  • Listens for OTLP traces on port 4318 (HTTP).
  • Exports traces to Weave’s OTLP endpoint using the wandb-api-key header, reading the endpoint URL from WANDB_OTLP_ENDPOINT and the API key from WANDB_API_KEY.
  • Sets wandb.entity and wandb.project as resource attributes using the resource processor, reading values from DEFAULT_WANDB_ENTITY and DEFAULT_WANDB_PROJECT. The insert action injects these attributes only if your application code does not already set them.
  • Enables the exporter’s built-in sending_queue with batching to reduce network overhead.

After configuring the collector’s settings, update the API and entity values in the following Docker command and run it:

docker run \
  -v ./config.yaml:/etc/otelcol-contrib/config.yaml \
  -e WANDB_API_KEY="<your-wandb-api-key>" \
  -e WANDB_OTLP_ENDPOINT="https://trace.wandb.ai/otel" \
  -e DEFAULT_WANDB_ENTITY="<your-team-name>" \
  -e DEFAULT_WANDB_PROJECT="YOUR_PROJECT" \
  -p 4318:4318 \
  otel/opentelemetry-collector-contrib:latest

Once the collector is running, configure your application to export traces to it by setting the OTEL_EXPORTER_OTLP_ENDPOINT environment variable. The OTel SDK reads this variable automatically, so you do not need to pass the endpoint to the exporter.

If you set wandb.entity or wandb.project as resource attributes in your application’s TracerProvider, they take precedence over the defaults defined in the collector config.

import os
import openai
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from openinference.instrumentation.openai import OpenAIInstrumentor

os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4318"

OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"

tracer_provider = trace_sdk.TracerProvider()
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))

OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

def main():
    client = openai.OpenAI(api_key=OPENAI_API_KEY)
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
        max_tokens=20,
    )
    print(response.choices[0].message.content)

if __name__ == "__main__":
    main()
import OpenAI from "openai";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { OpenAIInstrumentation, isPatched } from "@arizeai/openinference-instrumentation-openai";

process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4318";

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

const provider = new NodeTracerProvider({
  spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
});

provider.register();

const openAIInstrumentation = new OpenAIInstrumentation();
openAIInstrumentation.setTracerProvider(provider);
openAIInstrumentation.manuallyInstrument(OpenAI);

async function main() {
  console.log("OpenAI is patched?", isPatched());

  const client = new OpenAI({ apiKey: OPENAI_API_KEY });
  const response = await client.chat.completions.create({
    model: "gpt-3.5-turbo",
    messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
    max_tokens: 20,
  });

  console.log("Response:", response.choices[0]?.message?.content);
}

(async () => {
  await main();
  await new Promise(resolve => setTimeout(resolve, 2000));
  await provider.shutdown();
})();

The OpenAIInstrumentor automatically wraps OpenAI calls, creates traces, and exports them to the collector. The collector handles authentication and routing to Weave.

After running the script, you can view the traces in the Weave UI.

To send traces to additional backends, add more exporters and include them in the service.pipelines.traces.exporters list. For example, you can export to both Weave and Jaeger from the same Collector instance.

Organize OTel traces into threads#

Add specific span attributes to organize your OpenTelemetry traces into Weave threads, then use Weave’s Thread UI to analyze related operations like multi-turn conversations or user sessions in Weave’s thread UI.

Add the following attributes to your OTel spans to enable thread grouping:

  • wandb.thread_id: Groups spans into a specific thread
  • wandb.is_turn: Marks a span as a conversation turn (appears as a row in the thread view)

The following examples show how to organize OTel traces into Weave threads. They use wandb.thread_id to group related operations and wandb.is_turn to mark high-level operations that appear as rows in the thread view.

Use this configuration to run these examples:

    import json
    import os
    from opentelemetry import trace
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor

    # Configuration
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    OTEL_EXPORTER_OTLP_ENDPOINT = "https://trace.wandb.ai/otel/v1/traces"

    exporter = OTLPSpanExporter(
        endpoint=OTEL_EXPORTER_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # Optionally, print the spans to the console
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    trace.set_tracer_provider(tracer_provider)

    # Creates a tracer from the global tracer provider
    tracer = trace.get_tracer(__name__)
    ```
<span class="tab-end"></span>

<span class="tab-start" data-tab-title="TypeScript"></span>
```typescript
    import { trace, context } from "@opentelemetry/api";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import {
      BatchSpanProcessor,
      ConsoleSpanExporter,
    } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { Resource } from "@opentelemetry/resources";

    // Configuration
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";
    const WANDB_API_KEY = process.env.WANDB_API_KEY;

    if (!WANDB_API_KEY) {
      console.error("Error: WANDB_API_KEY environment variable is not set");
      console.error("Run: export WANDB_API_KEY=your_api_key_here");
      process.exit(1);
    }

    // OTel Setup
    const OTEL_EXPORTER_OTLP_ENDPOINT = "https://trace.wandb.ai/otel/v1/traces";

    const exporter = new OTLPTraceExporter({
      url: OTEL_EXPORTER_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    // Initialize tracer provider with span processors
    const provider = new NodeTracerProvider({
      resource: new Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter),
        new BatchSpanProcessor(new ConsoleSpanExporter()),
      ],
    });

    // Register the tracer provider
    provider.register();

    // Create a tracer from the global tracer provider
    const tracer = trace.getTracer("threads-examples");
    ```
<span class="tab-end"></span>
<span class="tab-group-end"></span>
</Accordion>

<Accordion title="Trace a basic single-turn thread">
<span class="tab-group-start"></span>
<span class="tab-start" data-tab-title="Python"></span>
```python
    def example_1_basic_thread_and_turn():
        """Example 1: Basic thread with a single turn"""
        print("\n=== Example 1: Basic Thread and Turn ===")

        # Create a thread context
        thread_id = "thread_example_1"

        # This span represents a turn (direct child of thread)
        with tracer.start_as_current_span("process_user_message") as turn_span:
            # Set thread attributes
            turn_span.set_attribute("wandb.thread_id", thread_id)
            turn_span.set_attribute("wandb.is_turn", True)

            # Add some example attributes
            turn_span.set_attribute("input.value", "Hello, help me with setup")

            # Simulate some work with nested spans
            with tracer.start_as_current_span("generate_response") as nested_span:
                # This is a nested call within the turn, so is_turn should be false or unset
                nested_span.set_attribute("wandb.thread_id", thread_id)
                # wandb.is_turn is not set or set to False for nested calls

                response = "I'll help you get started with the setup process."
                nested_span.set_attribute("output.value", response)

            turn_span.set_attribute("output.value", response)
            print(f"Turn completed in thread: {thread_id}")

    def main():
        example_1_basic_thread_and_turn()

    if __name__ == "__main__":
        main()
    ```
<span class="tab-end"></span>

<span class="tab-start" data-tab-title="TypeScript"></span>
```typescript
    function example_1_basic_thread_and_turn() {
      console.log("\n=== Example 1: Basic Thread and Turn ===");

      // Create a thread context
      const threadId = "thread_example_1";

      // This span represents a turn (direct child of thread)
      tracer.startActiveSpan("process_user_message", (turnSpan) => {
        // Set thread attributes
        turnSpan.setAttribute("wandb.thread_id", threadId);
        turnSpan.setAttribute("wandb.is_turn", true);

        // Add some example attributes
        turnSpan.setAttribute("input.value", "Hello, help me with setup");

        let response: string;
        
        // Simulate some work with nested spans
        tracer.startActiveSpan("generate_response", (nestedSpan) => {
          // This is a nested call within the turn, so is_turn should be false or unset
          nestedSpan.setAttribute("wandb.thread_id", threadId);
          // wandb.is_turn is not set or set to false for nested calls

          response = "I'll help you get started with the setup process.";
          nestedSpan.setAttribute("output.value", response);
          nestedSpan.end();
        });
        
        turnSpan.setAttribute("output.value", response!);
        console.log(`Turn completed in thread: ${threadId}`);
        turnSpan.end();
      });
    }

    function main() {
      example_1_basic_thread_and_turn();
    }

    main();
    ```
<span class="tab-end"></span>
<span class="tab-group-end"></span>
</Accordion>

<Accordion title="Trace a multi-turn conversation sharing one thread ID">
<span class="tab-group-start"></span>
<span class="tab-start" data-tab-title="Python"></span>
```python
    def example_2_multiple_turns():
        """Example 2: Multiple turns in a single thread"""
        print("\n=== Example 2: Multiple Turns in Thread ===")

        thread_id = "thread_conversation_123"

        # Turn 1
        with tracer.start_as_current_span("process_message_turn1") as turn1_span:
            turn1_span.set_attribute("wandb.thread_id", thread_id)
            turn1_span.set_attribute("wandb.is_turn", True)
            turn1_span.set_attribute("input.value", "What programming languages do you recommend?")

            # Nested operations
            with tracer.start_as_current_span("analyze_query") as analyze_span:
                analyze_span.set_attribute("wandb.thread_id", thread_id)
                # No is_turn attribute or set to False for nested spans

            response1 = "I recommend Python for beginners and JavaScript for web development."
            turn1_span.set_attribute("output.value", response1)
            print(f"Turn 1 completed in thread: {thread_id}")

        # Turn 2
        with tracer.start_as_current_span("process_message_turn2") as turn2_span:
            turn2_span.set_attribute("wandb.thread_id", thread_id)
            turn2_span.set_attribute("wandb.is_turn", True)
            turn2_span.set_attribute("input.value", "Can you explain Python vs JavaScript?")

            # Nested operations
            with tracer.start_as_current_span("comparison_analysis") as compare_span:
                compare_span.set_attribute("wandb.thread_id", thread_id)
                compare_span.set_attribute("wandb.is_turn", False)  # Explicitly false for nested

            response2 = "Python excels at data science while JavaScript dominates web development."
            turn2_span.set_attribute("output.value", response2)
            print(f"Turn 2 completed in thread: {thread_id}")

    def main():
        example_2_multiple_turns()

    if __name__ == "__main__":
        main()
    ```
<span class="tab-end"></span>

<span class="tab-start" data-tab-title="TypeScript"></span>
```typescript
    function example_2_multiple_turns() {
      console.log("\n=== Example 2: Multiple Turns in Thread ===");

      const threadId = "thread_conversation_123";

      // Turn 1
      tracer.startActiveSpan("process_message_turn1", (turn1Span) => {
        turn1Span.setAttribute("wandb.thread_id", threadId);
        turn1Span.setAttribute("wandb.is_turn", true);
        turn1Span.setAttribute(
          "input.value",
          "What programming languages do you recommend?"
        );

        // Nested operations
        tracer.startActiveSpan("analyze_query", (analyzeSpan) => {
          analyzeSpan.setAttribute("wandb.thread_id", threadId);
          // No is_turn attribute or set to false for nested spans
          analyzeSpan.end();
        });

        const response1 =
          "I recommend Python for beginners and JavaScript for web development.";
        turn1Span.setAttribute("output.value", response1);
        console.log(`Turn 1 completed in thread: ${threadId}`);
        turn1Span.end();
      });

      // Turn 2
      tracer.startActiveSpan("process_message_turn2", (turn2Span) => {
        turn2Span.setAttribute("wandb.thread_id", threadId);
        turn2Span.setAttribute("wandb.is_turn", true);
        turn2Span.setAttribute("input.value", "Can you explain Python vs JavaScript?");

        // Nested operations
        tracer.startActiveSpan("comparison_analysis", (compareSpan) => {
          compareSpan.setAttribute("wandb.thread_id", threadId);
          compareSpan.setAttribute("wandb.is_turn", false); // Explicitly false for nested
          compareSpan.end();
        });

        const response2 =
          "Python excels at data science while JavaScript dominates web development.";
        turn2Span.setAttribute("output.value", response2);
        console.log(`Turn 2 completed in thread: ${threadId}`);
        turn2Span.end();
      });
    }

    function main() {
      example_2_multiple_turns();
    }

    main();
    ```
<span class="tab-end"></span>
<span class="tab-group-end"></span>
</Accordion>

<Accordion title="Trace deeply nested operations and mark only the outermost span as a turn">
<span class="tab-group-start"></span>
<span class="tab-start" data-tab-title="Python"></span>
```python
    def example_3_complex_nested_structure():
        """Example 3: Complex nested structure with multiple levels"""
        print("\n=== Example 3: Complex Nested Structure ===")

        thread_id = "thread_complex_456"

        # Turn with multiple levels of nesting
        with tracer.start_as_current_span("handle_complex_request") as turn_span:
            turn_span.set_attribute("wandb.thread_id", thread_id)
            turn_span.set_attribute("wandb.is_turn", True)
            turn_span.set_attribute("input.value", "Analyze this code and suggest improvements")

            # Level 1 nested operation
            with tracer.start_as_current_span("code_analysis") as analysis_span:
                analysis_span.set_attribute("wandb.thread_id", thread_id)
                # No is_turn for nested operations

                # Level 2 nested operation
                with tracer.start_as_current_span("syntax_check") as syntax_span:
                    syntax_span.set_attribute("wandb.thread_id", thread_id)
                    syntax_span.set_attribute("result", "No syntax errors found")

                # Another Level 2 nested operation
                with tracer.start_as_current_span("performance_check") as perf_span:
                    perf_span.set_attribute("wandb.thread_id", thread_id)
                    perf_span.set_attribute("result", "Found 2 optimization opportunities")

            # Another Level 1 nested operation
            with tracer.start_as_current_span("generate_suggestions") as suggest_span:
                suggest_span.set_attribute("wandb.thread_id", thread_id)
                suggestions = ["Use list comprehension", "Consider caching results"]
                suggest_span.set_attribute("suggestions", json.dumps(suggestions))

            turn_span.set_attribute("output.value", "Analysis complete with 2 improvement suggestions")
            print(f"Complex turn completed in thread: {thread_id}")

    def main():
        example_3_complex_nested_structure()

    if __name__ == "__main__":
        main()
    ```
<span class="tab-end"></span>

<span class="tab-start" data-tab-title="TypeScript"></span>
```typescript
    function example_3_complex_nested_structure() {
      console.log("\n=== Example 3: Complex Nested Structure ===");

      const threadId = "thread_complex_456";

      // Turn with multiple levels of nesting
      tracer.startActiveSpan("handle_complex_request", (turnSpan) => {
        turnSpan.setAttribute("wandb.thread_id", threadId);
        turnSpan.setAttribute("wandb.is_turn", true);
        turnSpan.setAttribute(
          "input.value",
          "Analyze this code and suggest improvements"
        );

        // Level 1 nested operation
        tracer.startActiveSpan("code_analysis", (analysisSpan) => {
          analysisSpan.setAttribute("wandb.thread_id", threadId);
          // No is_turn for nested operations

          // Level 2 nested operation
          tracer.startActiveSpan("syntax_check", (syntaxSpan) => {
            syntaxSpan.setAttribute("wandb.thread_id", threadId);
            syntaxSpan.setAttribute("result", "No syntax errors found");
            syntaxSpan.end();
          });

          // Another Level 2 nested operation
          tracer.startActiveSpan("performance_check", (perfSpan) => {
            perfSpan.setAttribute("wandb.thread_id", threadId);
            perfSpan.setAttribute("result", "Found 2 optimization opportunities");
            perfSpan.end();
          });

          analysisSpan.end();
        });

        // Another Level 1 nested operation
        tracer.startActiveSpan("generate_suggestions", (suggestSpan) => {
          suggestSpan.setAttribute("wandb.thread_id", threadId);
          const suggestions = ["Use list comprehension", "Consider caching results"];
          suggestSpan.setAttribute("suggestions", JSON.stringify(suggestions));
          suggestSpan.end();
        });

        turnSpan.setAttribute(
          "output.value",
          "Analysis complete with 2 improvement suggestions"
        );
        console.log(`Complex turn completed in thread: ${threadId}`);
        turnSpan.end();
      });
    }

    function main() {
      example_3_complex_nested_structure();
    }

    main();
    ```
<span class="tab-end"></span>
<span class="tab-group-end"></span>
</Accordion>

<Accordion title="Trace background operations that belong to a thread but aren't turns">
<span class="tab-group-start"></span>
<span class="tab-start" data-tab-title="Python"></span>
```python
    def example_4_non_turn_operations():
        """Example 4: Operations that are part of a thread but not turns"""
        print("\n=== Example 4: Non-Turn Thread Operations ===")

        thread_id = "thread_background_789"

        # Background operation that's part of thread but not a turn
        with tracer.start_as_current_span("background_indexing") as bg_span:
            bg_span.set_attribute("wandb.thread_id", thread_id)
            # wandb.is_turn is unset or false - this is not a turn
            bg_span.set_attribute("wandb.is_turn", False)
            bg_span.set_attribute("operation", "Indexing conversation history")
            print(f"Background operation in thread: {thread_id}")

        # Actual turn in the same thread
        with tracer.start_as_current_span("user_query") as turn_span:
            turn_span.set_attribute("wandb.thread_id", thread_id)
            turn_span.set_attribute("wandb.is_turn", True)
            turn_span.set_attribute("input.value", "Search my previous conversations")
            turn_span.set_attribute("output.value", "Found 5 relevant conversations")
            print(f"Turn completed in thread: {thread_id}")

    def main():
        example_4_non_turn_operations()

    if __name__ == "__main__":
        main()
    ```
<span class="tab-end"></span>

<span class="tab-start" data-tab-title="TypeScript"></span>
```typescript
    function example_4_non_turn_operations() {
      console.log("\n=== Example 4: Non-Turn Thread Operations ===");

      const threadId = "thread_background_789";

      // Background operation that's part of thread but not a turn
      tracer.startActiveSpan("background_indexing", (bgSpan) => {
        bgSpan.setAttribute("wandb.thread_id", threadId);
        // wandb.is_turn is unset or false - this is not a turn
        bgSpan.setAttribute("wandb.is_turn", false);
        bgSpan.setAttribute("operation", "Indexing conversation history");
        console.log(`Background operation in thread: ${threadId}`);
        bgSpan.end();
      });

      // Actual turn in the same thread
      tracer.startActiveSpan("user_query", (turnSpan) => {
        turnSpan.setAttribute("wandb.thread_id", threadId);
        turnSpan.setAttribute("wandb.is_turn", true);
        turnSpan.setAttribute("input.value", "Search my previous conversations");
        turnSpan.setAttribute("output.value", "Found 5 relevant conversations");
        console.log(`Turn completed in thread: ${threadId}`);
        turnSpan.end();
      });
    }

    function main() {
      example_4_non_turn_operations();
    }

    main();
    ```
<span class="tab-end"></span>
<span class="tab-group-end"></span>
</Accordion>

After sending these traces, you can view them in the Weave UI under the **Threads** tab, where they'll be grouped by `thread_id` and each turn will appear as a separate row.

## Attribute Mappings

Weave automatically maps OpenTelemetry span attributes from various instrumentation frameworks to its internal data model. When multiple attribute names map to the same field, Weave applies them in priority order, allowing frameworks to coexist in the same traces.

### Supported Frameworks

Weave supports attribute conventions from the following observability frameworks and SDKs:

* **OpenTelemetry GenAI**: Standard semantic conventions for generative AI (`gen_ai.*`)
* **OpenInference**: Arize AI's instrumentation library (`input.value`, `output.value`, `llm.*`, `openinference.*`)
* **Vercel AI SDK**: Vercel's AI SDK attributes (`ai.prompt`, `ai.response`, `ai.model.*`, `ai.usage.*`)
* **MLflow**: MLflow tracking attributes (`mlflow.spanInputs`, `mlflow.spanOutputs`)
* **Traceloop**: OpenLLMetry instrumentation (`traceloop.entity.*`, `traceloop.span.kind`)
* **Google Vertex AI**: Vertex AI agent attributes (`gcp.vertex.agent.*`)
* **OpenLit**: OpenLit observability attributes (`gen_ai.content.completion`)
* **Langfuse**: Langfuse tracing attributes (`langfuse.startTime`, `langfuse.endTime`)

### Attribute Reference

| Attribute Field Name              | W\&B Mapping                  | Description                            | Type                        | Example                                        |
| :-------------------------------- | :---------------------------- | :------------------------------------- | :-------------------------- | :--------------------------------------------- |
| `ai.prompt`                       | `inputs`                      | User prompt text or messages.          | String, list, dict          | `"Write a short haiku about summer."`          |
| `gen_ai.prompt`                   | `inputs`                      | AI model prompt or message array.      | List, dict, string          | `[{"role":"user","content":"abc"}]`            |
| `input.value`                     | `inputs`                      | Input value for model invocation.      | String, list, dict          | `{"text":"Tell a joke"}`                       |
| `mlflow.spanInputs`               | `inputs`                      | Span input data.                       | String, list, dict          | `["prompt text"]`                              |
| `traceloop.entity.input`          | `inputs`                      | Entity input data.                     | String, list, dict          | `"Translate this to French"`                   |
| `gcp.vertex.agent.tool_call_args` | `inputs`                      | Tool call arguments.                   | Dict                        | `{"args":{"query":"weather in SF"}}`           |
| `gcp.vertex.agent.llm_request`    | `inputs`                      | LLM request payload.                   | Dict                        | `{"contents":[{"role":"user","parts":[...]}]}` |
| `input`                           | `inputs`                      | Generic input value.                   | String, list, dict          | `"Summarize this text"`                        |
| `inputs`                          | `inputs`                      | Generic input array.                   | List, dict, string          | `["Summarize this text"]`                      |
| `ai.response`                     | `outputs`                     | Model response text or data.           | String, list, dict          | `"Here is a haiku..."`                         |
| `gen_ai.completion`               | `outputs`                     | AI completion result.                  | String, list, dict          | `"Completion text"`                            |
| `output.value`                    | `outputs`                     | Output value from model.               | String, list, dict          | `{"text":"Answer text"}`                       |
| `mlflow.spanOutputs`              | `outputs`                     | Span output data.                      | String, list, dict          | `["answer"]`                                   |
| `gen_ai.content.completion`       | `outputs`                     | Content completion result.             | String                      | `"Answer text"`                                |
| `traceloop.entity.output`         | `outputs`                     | Entity output data.                    | String, list, dict          | `"Answer text"`                                |
| `gcp.vertex.agent.tool_response`  | `outputs`                     | Tool execution response.               | Dict, string                | `{"toolResponse":"ok"}`                        |
| `gcp.vertex.agent.llm_response`   | `outputs`                     | LLM response payload.                  | Dict, string                | `{"candidates":[...]}`                         |
| `output`                          | `outputs`                     | Generic output value.                  | String, list, dict          | `"Answer text"`                                |
| `outputs`                         | `outputs`                     | Generic output array.                  | List, dict, string          | `["Answer text"]`                              |
| `gen_ai.usage.input_tokens`       | `usage.input_tokens`          | Number of input tokens consumed.       | Int                         | `42`                                           |
| `gen_ai.usage.prompt_tokens`      | `usage.prompt_tokens`         | Number of prompt tokens consumed.      | Int                         | `30`                                           |
| `llm.token_count.prompt`          | `usage.prompt_tokens`         | Prompt token count.                    | Int                         | `30`                                           |
| `ai.usage.promptTokens`           | `usage.prompt_tokens`         | Prompt tokens consumed.                | Int                         | `30`                                           |
| `gen_ai.usage.completion_tokens`  | `usage.completion_tokens`     | Number of completion tokens generated. | Int                         | `40`                                           |
| `llm.token_count.completion`      | `usage.completion_tokens`     | Completion token count.                | Int                         | `40`                                           |
| `ai.usage.completionTokens`       | `usage.completion_tokens`     | Completion tokens generated.           | Int                         | `40`                                           |
| `llm.usage.total_tokens`          | `usage.total_tokens`          | Total tokens used in request.          | Int                         | `70`                                           |
| `llm.token_count.total`           | `usage.total_tokens`          | Total token count.                     | Int                         | `70`                                           |
| `gen_ai.system`                   | `attributes.system`           | System prompt or instructions.         | String                      | `"You are a helpful assistant."`               |
| `llm.system`                      | `attributes.system`           | System prompt or instructions.         | String                      | `"You are a helpful assistant."`               |
| `weave.span.kind`                 | `attributes.kind`             | Span type or category.                 | String                      | `"llm"`                                        |
| `traceloop.span.kind`             | `attributes.kind`             | Span type or category.                 | String                      | `"llm"`                                        |
| `openinference.span.kind`         | `attributes.kind`             | Span type or category.                 | String                      | `"llm"`                                        |
| `gen_ai.response.model`           | `attributes.model`            | Model identifier.                      | String                      | `"gpt-4o"`                                     |
| `llm.model_name`                  | `attributes.model`            | Model identifier.                      | String                      | `"gpt-4o-mini"`                                |
| `ai.model.id`                     | `attributes.model`            | Model identifier.                      | String                      | `"gpt-4o"`                                     |
| `llm.provider`                    | `attributes.provider`         | Model provider name.                   | String                      | `"openai"`                                     |
| `ai.model.provider`               | `attributes.provider`         | Model provider name.                   | String                      | `"openai"`                                     |
| `gen_ai.request`                  | `attributes.model_parameters` | Model generation parameters.           | Dict                        | `{"temperature":0.7,"max_tokens":256}`         |
| `llm.invocation_parameters`       | `attributes.model_parameters` | Model invocation parameters.           | Dict                        | `{"temperature":0.2}`                          |
| `wandb.display_name`              | `display_name`                | Custom display name for UI.            | String                      | `"User Message"`                               |
| `gcp.vertex.agent.session_id`     | `thread_id`                   | Session or thread identifier.          | String                      | `"thread_123"`                                 |
| `wandb.thread_id`                 | `thread_id`                   | Thread identifier for conversations.   | String                      | `"thread_123"`                                 |
| `wb_run_id`                       | `wb_run_id`                   | Associated W\&B run identifier.        | String                      | `"abc123"`                                     |
| `wandb.wb_run_id`                 | `wb_run_id`                   | Associated W\&B run identifier.        | String                      | `"abc123"`                                     |
| `gcp.vertex.agent.session_id`     | `is_turn`                     | Marks span as conversation turn.       | Boolean                     | `true`                                         |
| `wandb.is_turn`                   | `is_turn`                     | Marks span as conversation turn.       | Boolean                     | `true`                                         |
| `langfuse.startTime`              | `start_time` (override)       | Override span start timestamp.         | Timestamp (ISO8601/unix ns) | `"2024-01-01T12:00:00Z"`                       |
| `langfuse.endTime`                | `end_time` (override)         | Override span end timestamp.           | Timestamp (ISO8601/unix ns) | `"2024-01-01T12:00:01Z"`                       |

## Limitations

* The Weave UI does not support rendering OTel trace tool calls the Chat view. They appear as raw JSON instead.
Link last verified June 7, 2026. View original ↗
Source: Weights & Biases Docs
Link last verified: 2026-03-04