When you run LangWatch alongside another OTel-based SDK, both SDKs may hook
into the same global TracerProvider. This causes cross-contamination —
LLM traces appear in the other tool and application traces appear in LangWatch.
Both LangWatch SDKs support true provider isolation using a dedicated
TracerProvider. LangWatch attaches its exporter to the dedicated provider and
does not touch the global — each SDK operates independently.
Python
Pass a dedicated TracerProvider to langwatch.setup(). LangWatch uses it
exclusively, leaving the global provider for the other SDK:
from opentelemetry.sdk.trace import TracerProvider
import langwatch
# The other OTel SDK initializes first and owns the global provider.
# Create a dedicated provider for LangWatch:
langwatch_provider = TracerProvider()
langwatch.setup(
api_key="...",
tracer_provider=langwatch_provider,
)
That’s it. From here:
@langwatch.trace() creates spans on the dedicated provider automatically
- The other SDK continues using the global provider
- Neither SDK sees the other’s spans
Full example
from opentelemetry.sdk.trace import TracerProvider
import langwatch
langwatch_provider = TracerProvider()
langwatch.setup(api_key="...", tracer_provider=langwatch_provider)
@langwatch.trace(name="llm-call")
def chat(user_message: str):
response = client.chat.completions.create(
model="gpt-4.1-nano",
messages=[{"role": "user", "content": user_message}],
)
return response.choices[0].message.content
TypeScript
Pass a dedicated NodeTracerProvider via the tracerProvider option.
LangWatch attaches its exporter to it and skips global provider detection:
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { setupObservability } from "langwatch/observability/node";
// The other OTel SDK initializes first and owns the global provider.
// Create a dedicated provider for LangWatch:
const lwProvider = new NodeTracerProvider();
setupObservability({
tracerProvider: lwProvider,
langwatch: { apiKey: process.env.LANGWATCH_API_KEY },
});
Use lwProvider.getTracer() to create spans that go to LangWatch only:
const tracer = lwProvider.getTracer("my-llm-service");
await tracer.startActiveSpan("llm-call", async (span) => {
span.setAttribute("gen_ai.system", "openai");
span.setAttribute("langwatch.input", userMessage);
// Your LLM call here
span.setAttribute("langwatch.output", response);
span.end();
});
Only spans created through lwProvider.getTracer() go to LangWatch. The global
provider is untouched.
Auto-instrumentations that emit spans through the global OTel API (like Vercel
AI SDK’s experimental_telemetry) send spans to the global provider, not the
dedicated one. For those, use lwProvider.getTracer() directly to create LLM
spans on the isolated provider.
Alternative: Coexistence (TypeScript)
If you don’t need full isolation and just want LangWatch to receive spans from
an existing provider, use advanced.attachToExistingProvider:
setupObservability({
langwatch: { apiKey: process.env.LANGWATCH_API_KEY },
advanced: { attachToExistingProvider: true },
});
This adds LangWatch’s processor to the shared global provider. LangWatch
receives all spans — use the LangWatchTraceExporter filter options to
scope to LLM-only spans. See the
TypeScript SDK reference for filter
configuration.
Alternative: Filtering (Python)
If you prefer to share the global provider in Python, use span_exclude_rules:
from langwatch.domain import SpanProcessingExcludeRule
import langwatch
langwatch.setup(
span_exclude_rules=[
SpanProcessingExcludeRule(
field_name="span_name",
match_operation="starts_with",
match_value="GET",
),
SpanProcessingExcludeRule(
field_name="span_name",
match_operation="starts_with",
match_value="POST",
),
],
)
This is a name-based blocklist. The dedicated provider approach is more reliable.