OpenTelemetry
OpenTelemetry LLM tracing: bring your own SDK
init() never displaces a TracerProvider you configured: your exporters keep every span, and Syntropylabs additionally receives them.
Set up Any OpenTelemetry instrumentor
The snippet is copied from the SDK README, not paraphrased. Keys shown as tk_live_... are the environment key from Settings › Environments.
import { NodeSDK } from "@opentelemetry/sdk-node";
const sdk = new NodeSDK({ /* your exporters, sampler, resource */ });
sdk.start();
evalkit.init({ subscriptionKey: process.env.EVALKIT_SUBSCRIPTION_KEY!, serviceName: "my-service" });Copied from sdk-ts/README.md § Coexisting with an OpenTelemetry setup you already have
Install → first trace
- 01
Start your OpenTelemetry SDK
Register your NodeSDK or TracerProvider first, with your exporters, sampler and resource.
- 02
Call evalkit.init()
It finds the registered provider and attaches one span processor to it (tracingMode attached); or pass tracerProvider explicitly to touch no global state.
- 03
Produce one span
Your own instrumentation’s spans and the auto-instrumented model calls now reach both your exporters and Syntropylabs.
- 04
Open the trace
Bridged spans keep their kind, events and token counts; evalkit.* attributes make them filterable by session and user.
What a turn looks like
Sample data in the product’s own table. Hover, focus or tap a claim to see the columns it points at.
Sample run
| Operation | Service | Status | Model | Latency | Tokens | ≈Cost | Score |
|---|---|---|---|---|---|---|---|
| support_agent.turn8f3a1c0d94e2session sess_4b1e | support-api | ok | gpt-4o-mini | 1.84 s | 3,412 | $0.0006 | 92%auto |
| refund_agent.turnc21d7e5a30b8session sess_9a02tool loop | refund-worker | ok | claude-sonnet-4 | 4.31 s | 7,905 | $0.0389 | 68%auto |
| claude-code.turn5be04f7d1a96session sess_f77c | Claude Code | unset | claude-sonnet-4 | 48.20 s | 61,208 | $0.19 | — |
| rag.answere9a2b6c4d015error | docs-bot | error | gpt-4o | 6.02 s | 9,880 | $0.0312 | 41%auto |
| voice.turn17c8d3f2a4e0session sess_20d1 | ivr-agent | ok | gpt-4o-realtime | 0.92 s | 1,104 | $0.0071 | — |
fig. 1 · the traces table this integration fills · sample data
What you see
Taken from the SDK READMEs’ coverage tables. with flag means the signal leaves your machine only when you opt in.
| Signal | Captured | Detail |
|---|---|---|
| Attaches to a TracerProvider you already run | yes | tracingMode owned | attached; your OTLP, Jaeger or Datadog exporters keep receiving every span |
| Third-party spans bridged with kind, events, token and cache counts | yes | — |
| evalkit.* attributes fill prompt, completion, session, user and device columns | yes | evalkit.prompt, evalkit.session_id and so on |
| LLM spans from another instrumentor | partial | filed as function_call and not double-counted unless EVALKIT_COUNT_FOREIGN_LLM=1 |
| OTLP/HTTP receiver | yes | protobuf or JSON at /v1/otlp, Bearer environment key |
| Forward traces on to another OpenTelemetry backend | no | — |
Limitations
- Start your OpenTelemetry SDK before init(): OpenTelemetry JS refuses a duplicate global registration and silently orphans whichever side registers second.
- Do not also add an EvalKit exporter to your provider: ingest is append-only, so a span sent twice is counted twice in token and cost aggregates.
- A gen_ai.* span without evalkit.span_type or evalkit.sdk_version is treated as a second copy of a call EvalKit already reported; set EVALKIT_COUNT_FOREIGN_LLM=1 when that instrumentor is the only reporter.
- Forwarding from Syntropylabs to another backend is not available; coexistence happens on the SDK side, where your exporters keep every span.
Next steps
Trace Any OpenTelemetry instrumentor today
Tracing is free for one project on every plan, coding-agent traces included. Evaluations, datasets and simulation are Pro and up.