@nightowlsdev/telemetry-langfuse
TelemetryLangfuse v5 (OTel-native) telemetry exporter for @nightowlsdev/core swarm runs.
What it does
A thin adapter over @nightowlsdev/telemetry-core that forwards a swarm run's spans through Langfuse's OTel-native LangfuseSpanProcessor (@langfuse/otel). Because telemetry-core emits gen_ai.* attributes and Langfuse's default filter forwards any span carrying them, each model call lands in Langfuse as a generation with its model, token usage, and cost. Call langfuseTelemetry({ publicKey, secretKey, baseUrl?, exportMode?, environment?, release? }) and pass the result to defineSwarm({ telemetry }); exportMode defaults to 'immediate' (flushes per run, serverless-safe), use 'batched' for long-running hosts. It also exports nightOwlsPlugin, the declarative CLI plugin manifest consumed by @nightowlsdev/cli. Telemetry is best-effort, so a throwing exporter never breaks the run.
Install
pnpm add @nightowlsdev/telemetry-langfuseKey exports
- langfuseTelemetry
- nightOwlsPlugin
- LangfuseTelemetryOpts (type)
Usage
import { defineSwarm } from "@nightowlsdev/core";
import { langfuseTelemetry } from "@nightowlsdev/telemetry-langfuse";
const telemetry = langfuseTelemetry({
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_SECRET_KEY!,
});
const swarm = defineSwarm({ agents, telemetry });What it provides
A Langfuse v5 (OpenTelemetry-native) telemetry exporter for swarm runs. Call langfuseTelemetry({ publicKey, secretKey }) and hand the result to defineSwarm({ telemetry }); each model call lands in Langfuse as a generation with its model, token usage, and cost, and the whole run shows up as a trace. It is a thin shell over @nightowlsdev/telemetry-core — it differs only in passing Langfuse's LangfuseSpanProcessor (@langfuse/otel) as the span sink.
When to use it
- You want purpose-built LLM observability — a per-generation view of model, prompt/response, token usage, and cost, plus Langfuse's traces, sessions, evals, and prompt-management product.
- You run Langfuse (Cloud EU/US or self-hosted) and want swarm runs to appear natively without wiring OTLP yourself.
- You deploy serverless / per-request — exportMode 'immediate' (the default here) flushes on each run's export so nothing is stranded when the function freezes.
- You want env-driven setup — owl install telemetry-langfuse wires it from LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY / LANGFUSE_BASEURL and composes with any other telemetry adapter.
When not to
- You already run an APM / distributed-tracing stack (Datadog, Honeycomb, Grafana Tempo, Jaeger) and want swarm spans unified with your HTTP/db/queue traces — use @nightowlsdev/telemetry-otel (or run both).
- You're pinned to the old Langfuse v3 SDK — that langfuse.trace() / .generation() API is gone; this package targets Langfuse v5's OTel-native LangfuseSpanProcessor only.
- You have no Langfuse deployment and don't want to stand one up — an OTLP backend or core's customTelemetry may be lower-lift.
Alternatives
- @nightowlsdev/telemetry-otelYou want vendor-neutral OTLP into your existing observability backend rather than an LLM-specialized SaaS, or you need swarm spans correlated with the rest of your system's traces.
- Both at once (compositeTelemetry / a telemetry array)You want the Langfuse generation view for the AI team AND OTLP into the org's APM for infra correlation — send the same span batch to both.
- core's customTelemetry(fn)You want the raw SwarmSpan[] to persist or forward yourself, with no Langfuse account and no OpenTelemetry pipeline.
Strengths
- Purpose-built LLM view — model generations are first-class, with model, tokens, and cost surfaced from the gen_ai.* attributes telemetry-core emits.
- Two-line wiring, or one CLI command (owl install telemetry-langfuse) that merges the env vars and inserts the config.
- exportMode 'immediate' by default — serverless-safe (flushes per run); switch to 'batched' for a long-running host.
- Best-effort: the engine exports in a finally and swallows errors, so a throwing or hung Langfuse export never breaks the run.
- @mastra-free (the engine wall) — the built .d.ts has zero @mastra references.
- Composes cleanly — pass an array of exporters or compositeTelemetry to also ship to an OTLP backend.
Limits & trade-offs
- Langfuse v5 only. If you depend on the removed v3 client API, this won't fit.
- Requires a Langfuse deployment (Cloud account or self-hosted) plus public/secret keys — the secret key is server-side only.
- LLM-specialized, not a general APM — you won't see your HTTP/db/infra spans here; use OTel for that (or run both).
- The LangfuseSpanProcessor's default filter is gen_ai.*-centric, so model generations are the first-class citizens; a run/tool span without gen_ai.* attributes is not the focus of the default view.
- Node-only, inherited from telemetry-core's node:crypto trace-id derivation.
How it works
langfuseTelemetry constructs a @langfuse/otel LangfuseSpanProcessor from your publicKey / secretKey / baseUrl / exportMode and hands it to telemetry-core's replayerExporter. When core's engine finishes a run it calls that exporter with the batch; the replayer builds real OTel spans (run + generations + tools) carrying gen_ai.* attributes and pushes them through the processor, which uploads to Langfuse. Because Langfuse's default filter forwards any span carrying gen_ai.* attributes, each model call lands as a Langfuse generation with model, token usage, and cost. exportMode 'immediate' flushes on each export (serverless); 'batched' defers for throughput on a long-running host.
Examples
Wire Langfuse into a swarm
The default exportMode 'immediate' flushes per run — safe under serverless.
import { defineSwarm } from "@nightowlsdev/core";
import { langfuseTelemetry } from "@nightowlsdev/telemetry-langfuse";
const telemetry = langfuseTelemetry({
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_SECRET_KEY!,
});
const swarm = defineSwarm({ agents, telemetry });Batched mode with environment + release tags
On a long-running host, 'batched' trades per-run flushing for throughput; environment/release tag the trace.
import { langfuseTelemetry } from "@nightowlsdev/telemetry-langfuse";
const telemetry = langfuseTelemetry({
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_SECRET_KEY!,
baseUrl: "https://cloud.langfuse.com",
exportMode: "batched",
environment: "prod",
release: "1.4.0",
});Send to Langfuse and an OTLP backend at once
compositeTelemetry isolates each exporter (Promise.allSettled) — one throwing backend never blocks the other or the run.
import { defineSwarm, compositeTelemetry } from "@nightowlsdev/core";
import { langfuseTelemetry } from "@nightowlsdev/telemetry-langfuse";
import { otelTelemetry } from "@nightowlsdev/telemetry-otel";
const swarm = defineSwarm({
agents,
telemetry: compositeTelemetry([
langfuseTelemetry({ publicKey, secretKey }),
otelTelemetry({ url, headers }),
]),
});Related
- telemetry-otel — The other exporter — pick it when you want vendor-neutral OTLP into an APM instead of (or alongside) Langfuse.
- telemetry-core — The shared replay engine this wraps — and where the aisdkTelemetry helper lives for raw, non-swarm calls.
- core — Provides defineSwarm({ telemetry }), compositeTelemetry, and the SwarmSpan batch this exports.