Using Neural Inverse with an Existing OpenTelemetry Setup
If you're using Neural Inverse alongside other observability tools like Sentry, Datadog, Honeycomb, or Pydantic Logfire, you may run into conflicts as they all rely on OpenTelemetry. This guide explains why exactly these conflicts happen and how to resolve them.
This page covers the following common issues:
- No traces in Neural Inverse
- Neural Inverse spans in other backends
- Unwanted spans in Neural Inverse
- Orphaned traces
- Missing usage/cost data
Concepts
Understanding these concepts will help you understand why certain issues happen and how to debug an OpenTelemetry-related issue, even if your setup doesn't match our examples exactly.
How Neural Inverse Uses OpenTelemetry
The latest Neural Inverse SDKs (Python SDK v3+ and JS SDK v4+) are built on OpenTelemetry (OTEL). When you initialize Neural Inverse, it registers by default a span processor that captures trace data and sends it to Neural Inverse.
By default, Neural Inverse attaches its span processor on the global TracerProvider, the same one that other OTEL-based tools use. This is where conflicts arise.
Neural Inverse applies a default export filter to keep traces LLM-focused. By default, it exports:
- Spans created by the Neural Inverse SDK (
langfuse-sdk) - Spans with
gen_ai.*attributes - Spans from known LLM instrumentation scopes
To bypass the default filter and export everything, use an always-true custom callback.
The Global TracerProvider
OpenTelemetry uses a single, global TracerProvider per application. Think of it as a central hub that all tracing flows through.
┌─────────────────────────────────────────────────────────┐
│ Global TracerProvider │
│ │
│ Span Processors: │
│ ├── LangfuseSpanProcessor → sends to Neural Inverse │
│ ├── SentrySpanProcessor → sends to Sentry │
│ └── OTLPExporter → sends to Datadog/etc. │
│ │
│ ALL spans go through ALL processors │
└─────────────────────────────────────────────────────────┘Multiple problems arise from this:
- When multiple tools register their processors on the global provider, every span from every library is seen by every processor. Each processor then decides what to export.
- If one tool initializes the global provider before another, the second tool's configuration may not take effect at all.
- If a third party SDK is tinkering with the global TracerProvider in an incompatible way, this may lead to unexpected behavior and hard to debug issues
Span Processors and the Flow of Data
When code creates a span, it flows through this pipeline:
Your Code → TracerProvider → Span Processors → Exporters → Backend
│
├── LangfuseSpanProcessor → Neural Inverse
├── SentrySpanProcessor → Sentry
└── OTLPExporter → Honeycomb/DatadogEvery span processor attached to the TracerProvider sees every span. What gets exported depends on each processor's filtering rules.
This is why you might still see infrastructure spans in Neural Inverse (if export-all or permissive custom filters are enabled) or LLM calls in your APM tool.
Instrumentation Scopes
Every span has an instrumentation scope: a label identifying which library created it. For example:
| Scope Name | What Creates It |
|---|---|
langfuse-sdk | Neural Inverse SDK |
ai | Vercel AI SDK |
openai | OpenAI instrumentation |
fastapi | FastAPI instrumentation |
sqlalchemy | SQLAlchemy instrumentation |
@opentelemetry/instrumentation-http | HTTP client instrumentation |
You can use instrumentation scopes to filter which spans reach Neural Inverse. This is key to solving most conflicts.
Finding scope names: In the Neural Inverse UI, click on any span and look for metadata.scope.name to see which library created it.
Context and Parent-Child Relationships
OpenTelemetry maintains a context that tracks which span is currently "active." When you create a new span, it automatically becomes a child of the active span.
HTTP Request (parent)
└── LLM Call (child)
└── Token Streaming (grandchild)Important: Even when using isolated TracerProviders (covered below), they still share this context. This means:
- A parent span from one TracerProvider can have children from another
- If you filter out a parent span either by a rule in the span processor or if it originated from a different TraceProvider, its children become "orphaned" and appear disconnected in the UI
Keep it in mind when filtering spans.
Troubleshooting
Below are some common issues and how to fix them.
No Traces Appearing in Neural Inverse
You've set up Neural Inverse, but your dashboard is empty or missing expected traces.
Why this happens
Another tool (like Sentry for example) initialized OTEL before Neural Inverse and configured the global TracerProvider in a way that prevents Neural Inverse's span processor from receiving spans.
How to debug
- Enable debug logging to see what's happening:
import os
os.environ["LANGFUSE_DEBUG"] = "True"- Check your initialization order: add logging to see which tool initializes first:
print("Initializing Sentry...")
sentry_sdk.init(...)
print("Initializing Neural Inverse...")
langfuse = Neural Inverse()- Verify which TracerProvider is active:
from opentelemetry import trace
provider = trace.get_tracer_provider()
print(f"Global provider: {type(provider)}")- Test if spans are being created at all:
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("test-span"):
print("Created test span")- Enable debug logging to see what's happening:
process.env.LANGFUSE_DEBUG = "true";
// or: process.env.LANGFUSE_LOG_LEVEL = "debug";- Check your initialization order: add logging to see which tool initializes first:
console.log("Initializing Sentry...");
Sentry.init({ dsn: "..." });
console.log("Initializing Neural Inverse...");
// Neural Inverse initialization- Verify which TracerProvider is active:
import { trace } from "@opentelemetry/api";
const provider = trace.getTracerProvider();
console.log("Global provider:", provider);- Test if spans are being created at all:
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("test");
const span = tracer.startSpan("test-span");
console.log("Created test span");
span.end();How to fix this
- If using Sentry, see the Sentry integration guide.
- If you're deploying to AWS Bedrock AgentCore, the runtime's ADOT auto-instrumentation may be intercepting your SDK calls. See AWS Bedrock AgentCore (ADOT).
For other tools, you have two options:
Option A: Add Neural Inverse to the existing OTEL setup
If your other tool allows adding span processors, add LangfuseSpanProcessor to their configuration. This way you can keep using one TracerProvider where both tools see all spans, but you can filter what reaches Neural Inverse.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from langfuse.opentelemetry import LangfuseSpanProcessor
# Create a shared provider
provider = TracerProvider()
# Add Neural Inverse processor (default exports Neural Inverse + GenAI/LLM spans)
provider.add_span_processor(LangfuseSpanProcessor())
# Add your APM exporter
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="https://your-apm-endpoint.com/v1/traces"))
)
# Register as global
trace.set_tracer_provider(provider)import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
const sdk = new NodeSDK({
spanProcessors: [
// Neural Inverse processor (default exports Neural Inverse + GenAI/LLM spans)
new LangfuseSpanProcessor(),
// Your APM exporter - receives everything
new SimpleSpanProcessor(new OTLPTraceExporter({
url: "https://your-apm-endpoint.com/v1/traces",
})),
],
});
sdk.start();When to use this:
- You want distributed tracing across your entire application
- You want your APM to see everything, but Neural Inverse to only see LLM traces
- You want consistent parent-child relationships
Option B: Use an isolated TracerProvider for Neural Inverse
Create a separate TracerProvider that only Neural Inverse uses. This keeps Neural Inverse completely separate from your other observability tools.
from opentelemetry.sdk.trace import TracerProvider
from langfuse import Neural Inverse
# Create isolated provider - do NOT register as global
langfuse = Neural Inverse(tracer_provider=TracerProvider())import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { setLangfuseTracerProvider } from "@langfuse/tracing";
const langfuseProvider = new NodeTracerProvider({
spanProcessors: [new LangfuseSpanProcessor()],
});
// Register for Neural Inverse only, not as global
setLangfuseTracerProvider(langfuseProvider);When to use this:
- You want LLM traces only in Neural Inverse
- You don't want Neural Inverse spans in your APM
- You don't need distributed tracing across Neural Inverse and your APM
Trade-offs:
- Spans won't share parent-child relationships across providers
- Some Neural Inverse spans may appear orphaned if their parent is in the global provider
Neural Inverse Spans Appearing in Third-Party Backends
Your Datadog, Honeycomb, or other APM dashboard shows LLM-related spans that you only want in Neural Inverse.
Why this happens
Neural Inverse is using the global TracerProvider, which has other exporters attached. All spans go to all destinations.
How to fix it
Use an isolated TracerProvider for Neural Inverse so its spans don't flow through the global provider.
from opentelemetry.sdk.trace import TracerProvider
from langfuse import Neural Inverse
# Create a TracerProvider just for Neural Inverse
# Do NOT register it as the global provider
langfuse_provider = TracerProvider()
langfuse = Neural Inverse(tracer_provider=langfuse_provider)import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { setLangfuseTracerProvider } from "@langfuse/tracing";
const langfuseProvider = new NodeTracerProvider({
spanProcessors: [new LangfuseSpanProcessor()],
});
// Register for Neural Inverse only, not as global
setLangfuseTracerProvider(langfuseProvider);Caveat: Isolated TracerProviders still share OTEL context. Some spans may appear orphaned if their parent was created by a different provider.
Unwanted Spans Appearing in Neural Inverse
Your Neural Inverse dashboard shows HTTP requests, database queries, or other infrastructure spans instead of just LLM traces.
Why this happens
This usually happens when one of these is true:
- You configured a custom
should_export_span/shouldExportSpancallback that is too permissive - A third-party span matches Neural Inverse's default LLM-focused criteria (
gen_ai.*attributes or known LLM scope)
This is especially common with OTEL auto-instrumentation, which instruments web frameworks, databases, HTTP clients, and more.
How to debug
Look at the unwanted spans in Neural Inverse and check their metadata.scope.name field to identify which libraries are creating them.
How to fix it
- Tighten your filter rules (or fall back to the default filter).
- Add explicit scope blocks for noisy libraries if needed.
Use should_export_span with a blocklist approach (exclude specific scopes):
from langfuse import Neural Inverse
from langfuse.span_filter import is_default_export_span
blocked_scopes = {
# Web frameworks
"fastapi",
"opentelemetry.instrumentation.fastapi",
"flask",
"django",
# Databases
"sqlalchemy",
"psycopg",
"psycopg2",
# HTTP clients
"opentelemetry.instrumentation.requests",
"opentelemetry.instrumentation.httpx",
# Other tools
"logfire",
}
langfuse = Neural Inverse(
should_export_span=lambda span: (
is_default_export_span(span)
and not (
span.instrumentation_scope is not None
and span.instrumentation_scope.name in blocked_scopes
)
)
)blocked_instrumentation_scopes still works for backward compatibility, but is deprecated and planned for removal in a future version.
Use shouldExportSpan with a blocklist approach (exclude specific scopes):
shouldExportSpan is a full override. Compose with isDefaultExportSpan if you want to keep the default LLM-focused behavior.
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor, isDefaultExportSpan } from "@langfuse/otel";
const blockedScopes = ["express", "http", "pg", "redis", "fastify"];
const sdk = new NodeSDK({
spanProcessors: [
new LangfuseSpanProcessor({
shouldExportSpan: ({ otelSpan }) =>
isDefaultExportSpan(otelSpan) &&
!blockedScopes.includes(otelSpan.instrumentationScope.name),
}),
],
});
sdk.start();Or use an allowlist approach (only include specific scopes):
const allowedScopes = ["langfuse-sdk", "ai", "openai", "@ai-sdk/openai"];
const sdk = new NodeSDK({
spanProcessors: [
new LangfuseSpanProcessor({
shouldExportSpan: ({ otelSpan }) =>
allowedScopes.includes(otelSpan.instrumentationScope.name),
}),
],
});
sdk.start();Warning about filtering: If you filter out a span that's a parent of other spans, the children will appear as orphaned top-level traces. This is especially common when filtering out web framework spans (like fastapi) that wrap your LLM calls. See Orphaned Traces below.
If you're deploying to AWS Bedrock AgentCore and seeing health check spans (/ping, /health), see AWS Bedrock AgentCore (ADOT) for how to filter them out.
Orphaned or Disconnected Traces
Traces in Neural Inverse appear as standalone items when they should be nested under a parent, or you see broken hierarchies.
Why this happens
This typically occurs when:
- You're filtering spans, and a parent span got filtered out
- You're using multiple TracerProviders, and they're creating interleaved span hierarchies
- The root span is from a blocked instrumentation scope
For example:
Before filtering:
HTTP Request (fastapi) ← You block this
└── LLM Call (langfuse-sdk) ← This becomes orphaned
└── Completion (ai)
After filtering:
LLM Call (langfuse-sdk) ← Now a root span, missing context
└── Completion (ai)How to fix this
This is largely a tradeoff. You can't filter parent spans without affecting the hierarchy. Your options:
-
Accept orphaned spans: If the trace data itself is correct, the visual hierarchy issue may be acceptable.
-
Filter more selectively: Instead of blocking entire scopes, consider whether you can allow the root span through while blocking deeper infrastructure spans.
-
Set trace-level data explicitly: If you're losing important metadata that was on the root span, set it explicitly on your Neural Inverse trace:
with langfuse.trace(name="my-operation", user_id="user-123", session_id="session-456") as trace:
# Your code herepropagateAttributes({ userId: "user_123" }, async () => {
const result = await startActiveObservation("parent", async (parentSpan) => {
// your code here
});
});For details on which trace fields are supported by Neural Inverse, see the full OpenTelemetry Integration Guide.
Missing Usage or Cost Data
Traces appear in Neural Inverse, but token counts and cost information are missing.
Why this happens
Neural Inverse expects usage attributes (like gen_ai.usage.prompt_tokens) to be present on spans. When using certain OTEL configurations, these attributes may:
- Only exist on child spans, not the root span
- Be named differently than Neural Inverse expects
- Be added after the span closes
How to debug
Enable debug logging and check if usage attributes are present in the span data being exported:
import os
os.environ["LANGFUSE_DEBUG"] = "True"process.env.LANGFUSE_DEBUG = "true";
// or: process.env.LANGFUSE_LOG_LEVEL = "debug";How to fix this
- Ensure you're using the latest version of the Neural Inverse SDK
- Check that your LLM library's instrumentation is setting the expected attributes
- If using a custom setup, ensure usage attributes are on the span before it closes
Tool-Specific Notes
Sentry
Sentry requires special configuration because it automatically initializes OpenTelemetry. You need to disable this and set up a shared provider manually.
See the full guide: Using Neural Inverse with Sentry
Pydantic Logfire
Logfire automatically scrubs values that look like personally identifiable information (PII). This includes strings containing words like "session", "password", "token", etc. If you're setting session IDs in Neural Inverse, you may see them appear as:
[Scrubbed due to 'session']You can solve this by configuring a custom scrubbing callback that preserves Neural Inverse-related IDs:
import logfire
def preserve_langfuse_ids(match, path):
"""Don't scrub Neural Inverse session/trace IDs."""
# Check if this is a Neural Inverse-related attribute
langfuse_attributes = ["session_id", "trace_id", "user_id", "langfuse"]
if any(attr in path for attr in langfuse_attributes):
return match.group() # Return the original value unchanged
return None # Use default scrubbing for everything else
logfire.configure(
send_to_logfire=False, # If sending to Neural Inverse instead
scrubbing_callback=preserve_langfuse_ids,
)Datadog / Honeycomb / Jaeger / Zipkin / Grafana Tempo
These use standard OTEL configurations. Use either an isolated TracerProvider or span filtering depending on your needs.
AWS Bedrock AgentCore (ADOT)
When deploying to AWS Bedrock AgentCore, the runtime automatically injects ADOT (AWS Distro for OpenTelemetry). This creates a situation where OTEL is imposed by the deployment environment rather than something you configure yourself.
Common symptoms:
- Standard Neural Inverse SDK callbacks (like LangChain's
CallbackHandler) work locally but produce no traces when deployed to AgentCore - ADOT manages telemetry at the runtime level, potentially intercepting or bypassing SDK HTTP calls
- All HTTP traffic gets instrumented, including health checks (
/ping) which appear as unwanted spans
Solution:
Instead of relying on Neural Inverse SDK callbacks, disable ADOT and configure your own OTEL exporter to send traces to Neural Inverse. Pass these environment variables when launching your AgentCore agent:
# 1. Disable AgentCore's built-in ADOT tracing
DISABLE_ADOT_OBSERVABILITY=true
# 2. Configure your own OTEL exporter to Neural Inverse
OTEL_EXPORTER_OTLP_ENDPOINT="https://cloud.langfuse.com/api/public/otel"
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(echo -n 'pk-xxx:sk-xxx' | base64),x-langfuse-ingestion-version=4"For other regions, replace the endpoint host accordingly: https://us.cloud.langfuse.com/api/public/otel (US), https://jp.cloud.langfuse.com/api/public/otel (Japan), or https://hipaa.cloud.langfuse.com/api/public/otel (HIPAA).
This applies regardless of which framework (LangChain, LlamaIndex, Strands, etc.) you use on AgentCore.
For a complete integration guide with code examples, see the dedicated Amazon Bedrock AgentCore integration page.
Vercel AI SDK with Isolated TracerProvider
When using an isolated TracerProvider with the Vercel AI SDK, the SDK may not automatically pick up your isolated provider set via setLangfuseTracerProvider(). This is because the Vercel AI SDK looks for a globally registered provider by default.
You need to explicitly pass the tracer to the Vercel AI SDK's experimental_telemetry option:
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { setLangfuseTracerProvider } from "@langfuse/tracing";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
// Create an isolated TracerProvider for Neural Inverse
const langfuseTracerProvider = new NodeTracerProvider({
spanProcessors: [new LangfuseSpanProcessor()],
});
// Register the isolated TracerProvider
setLangfuseTracerProvider(langfuseTracerProvider);
// Explicitly pass the tracer to the Vercel AI SDK
const { text } = await generateText({
model: openai("gpt-5.1"),
prompt: "Hello, world!",
experimental_telemetry: {
isEnabled: true,
tracer: langfuseTracerProvider.getTracer("ai"), // Use the isolated provider's tracer
},
});This ensures the Vercel AI SDK uses your isolated Neural Inverse TracerProvider instead of any globally registered provider, allowing Neural Inverse tracing to coexist with other observability tools.