Import OpenTelemetry compatible trace data through a dedicated endpoint
Weave supports import of OpenTelemetry compatible trace data through a dedicated endpoint. This endpoint lets you send OTLP (OpenTelemetry Protocol) formatted trace data directly to your Weave project. Use this integration when you want to instrument your application with the OpenTelemetry standard and have those traces appear alongside your other Weave data, without replacing your existing OTel-based observability pipeline.This page covers the endpoint details, authentication, end-to-end examples in Python and TypeScript, how to forward traces through an OpenTelemetry Collector, how to organize traces into Weave threads, and the attribute mappings Weave applies to incoming spans.
Weave uses the wandb-api-key header to authenticate requests and resource attributes on your TracerProvider to route spans to the correct entity and project. 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 osfrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.resources import ResourceWANDB_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/settingsWANDB_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,}))
The following examples show how to send OpenTelemetry traces to Weave using Python and TypeScript. Each example covers a different approach: using the OpenInference instrumentation library, using OpenLLMetry instrumentation, or using the OpenTelemetry SDK directly without an instrumentation package.Before running the following code samples, set the following fields:
Entity: You can only log traces to the project under a team/entity that you have access to. To find your entity name, visit your W&B dashboard and check the Teams field in the left sidebar.
OpenInference is an open source instrumentation library from Arize AI that captures LLM calls as OpenTelemetry spans. This example shows how to use the OpenAI instrumentation. Additional instrumentations are available in the official repository.First, install the required dependencies:
Performance recommendation: Always use BatchSpanProcessor instead of SimpleSpanProcessor when sending traces to Weave. SimpleSpanProcessor exports spans synchronously, which can impact the performance of other workloads. These examples illustrate BatchSpanProcessor, which is recommended in production because it batches spans asynchronously and efficiently.
Python
TypeScript
Paste the following code into a Python file such as openinference_example.py:
import osimport openaifrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessorfrom openinference.instrumentation.openai import OpenAIInstrumentorOPENAI_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/settingsWANDB_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:
python openinference_example.py
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:
// IMPORTANT: Import OpenAI FIRST so instrumentation can patch itimport 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/settingsconst 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 providerconst openAIInstrumentation = new OpenAIInstrumentation();openAIInstrumentation.setTracerProvider(provider);// Manually instrument OpenAI since we're using ESMopenAIInstrumentation.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");})();
OpenLLMetry is an open source observability library from Traceloop that provides OpenTelemetry instrumentation for popular LLM providers and frameworks. The following example shows how to use its OpenAI instrumentation. Additional examples are available in the OpenLLMetry repository.First, install the required dependencies:
Paste the following code into a Python file such as openllmetry_example.py. This is the same code as the preceding example, except the OpenAIInstrumentor is imported from opentelemetry.instrumentation.openai instead of openinference.instrumentation.openai:
import osimport openaifrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessorfrom opentelemetry.instrumentation.openai import OpenAIInstrumentorOPENAI_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/settingsWANDB_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:
python openllmetry_example.py
Paste the following code into a TypeScript file such as openllmetry_example.ts. This uses the Traceloop OpenAI instrumentation package:
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/settingsconst 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 providerconst openAIInstrumentation = new OpenAIInstrumentation();registerInstrumentations({ tracerProvider: provider, instrumentations: [openAIInstrumentation],});// Manually instrument OpenAI since we're using ESMopenAIInstrumentation.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})();
If you prefer to use OTel directly instead of an instrumentation package, you can do so. This approach gives you full control over which attributes are set on each span. Weave parses span attributes according to the OpenTelemetry semantic conventions described at https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/.First, install the required dependencies:
Paste the following code into a Python file such as opentelemetry_example.py:
import jsonimport osimport openaifrom opentelemetry import tracefrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessorOPENAI_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/settingsWANDB_API_KEY = os.environ["WANDB_API_KEY"]# Configure the OTLP exporterexporter = 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 providertrace.set_tracer_provider(tracer_provider)# Create a tracer from the global tracer providertracer = 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:
python opentelemetry_example.py
Paste the following code into a TypeScript file such as opentelemetry_example.ts:
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/settingsconst 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 providerconst 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:
npx ts-node opentelemetry_example.ts
Weave uses the span attribute prefixes gen_ai and openinference to determine which convention to apply, 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.
The previous examples export traces directly from your application to Weave. In production, you can use an OpenTelemetry Collector as an intermediary between your application and Weave. The collector receives traces from your app, then forwards them to one or more backends. This pattern centralizes authentication, batching, and routing logic outside of your application code, and lets you fan traces out to multiple observability backends from a single pipeline.
This section walks through running a local OpenTelemetry Collector in Docker and configuring an application to send traces to it. 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:
collector-config.yaml
receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318exporters: 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: insertservice: 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:
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 don’t 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.
The OpenAIInstrumentor 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.
Weave threads let you group related traces so you can analyze multi-turn conversations or user sessions as a single unit. Add specific span attributes to organize your OpenTelemetry traces into threads, then use Weave’s Thread UI to analyze related operations such as multi-turn conversations or user sessions.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.
Initial set up
Use this configuration to run these examples:
Python
TypeScript
import jsonimport osfrom opentelemetry import tracefrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor# ConfigurationENTITY = "<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 consoletracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))trace.set_tracer_provider(tracer_provider)# Creates a tracer from the global tracer providertracer = trace.get_tracer(__name__)
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";// Configurationconst 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 Setupconst 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 processorsconst provider = new NodeTracerProvider({ resource: new Resource({ "wandb.entity": ENTITY, "wandb.project": PROJECT, }), spanProcessors: [ new BatchSpanProcessor(exporter), new BatchSpanProcessor(new ConsoleSpanExporter()), ],});// Register the tracer providerprovider.register();// Create a tracer from the global tracer providerconst tracer = trace.getTracer("threads-examples");
Trace a basic single-turn thread
Python
TypeScript
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()
functionexample_1_basic_thread_and_turn() {console.log("\n=== Example 1: Basic Thread and Turn ==="); // Create a thread context constthreadId = "thread_example_1"; // This span represents a turn (direct child of thread)tracer.startActiveSpan("process_user_message", (turnSpan) => { // Set thread attributesturnSpan.setAttribute("wandb.thread_id", threadId);turnSpan.setAttribute("wandb.is_turn", true); // Add some example attributesturnSpan.setAttribute("input.value", "Hello, help me with setup"); letresponse: string; // Simulate some work with nested spanstracer.startActiveSpan("generate_response", (nestedSpan) => { // This is a nested call within the turn, so is_turn should be false or unsetnestedSpan.setAttribute("wandb.thread_id", threadId); // wandb.is_turn is not set or set to false for nested callsresponse = "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(); });}functionmain() {example_1_basic_thread_and_turn();}main();
Trace a multi-turn conversation sharing one thread ID
Python
TypeScript
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()
functionexample_2_multiple_turns() {console.log("\n=== Example 2: Multiple Turns in Thread ==="); constthreadId = "thread_conversation_123"; // Turn 1tracer.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 operationstracer.startActiveSpan("analyze_query", (analyzeSpan) => {analyzeSpan.setAttribute("wandb.thread_id", threadId); // No is_turn attribute or set to false for nested spansanalyzeSpan.end(); }); constresponse1 = "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 2tracer.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 operationstracer.startActiveSpan("comparison_analysis", (compareSpan) => {compareSpan.setAttribute("wandb.thread_id", threadId);compareSpan.setAttribute("wandb.is_turn", false); // Explicitly false for nestedcompareSpan.end(); }); constresponse2 = "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(); });}functionmain() {example_2_multiple_turns();}main();
Trace deeply nested operations and mark only the outermost span as a turn
Python
TypeScript
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()
functionexample_3_complex_nested_structure() {console.log("\n=== Example 3: Complex Nested Structure ==="); constthreadId = "thread_complex_456"; // Turn with multiple levels of nestingtracer.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 operationtracer.startActiveSpan("code_analysis", (analysisSpan) => {analysisSpan.setAttribute("wandb.thread_id", threadId); // No is_turn for nested operations // Level 2 nested operationtracer.startActiveSpan("syntax_check", (syntaxSpan) => {syntaxSpan.setAttribute("wandb.thread_id", threadId);syntaxSpan.setAttribute("result", "No syntax errors found");syntaxSpan.end(); }); // Another Level 2 nested operationtracer.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 operationtracer.startActiveSpan("generate_suggestions", (suggestSpan) => {suggestSpan.setAttribute("wandb.thread_id", threadId); constsuggestions = ["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(); });}functionmain() {example_3_complex_nested_structure();}main();
Trace background operations that belong to a thread but aren't turns
Python
TypeScript
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()
functionexample_4_non_turn_operations() {console.log("\n=== Example 4: Non-Turn Thread Operations ==="); constthreadId = "thread_background_789"; // Background operation that's part of thread but not a turntracer.startActiveSpan("background_indexing", (bgSpan) => {bgSpan.setAttribute("wandb.thread_id", threadId); // wandb.is_turn is unset or false - this is not a turnbgSpan.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 threadtracer.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(); });}functionmain() {example_4_non_turn_operations();}main();
After sending these traces, you can view them in the Weave UI under the Threads tab, where they’re grouped by thread_id and each turn appears as a separate row.
Weave maps OpenTelemetry span attributes from various instrumentation frameworks to its internal data model. This mapping means you don’t need to rename or transform attributes from your existing instrumentation to get a rich view in Weave. When multiple attribute names map to the same field, Weave applies them in priority order, which lets frameworks coexist in the same traces.