Back to the stack guide
Observability

Langfuse

Two runtimes, one trace shape — TypeScript and Python emit identical traces, captured offline-first as JSONL and shipped idempotently.

Two runtimes, one trace shape

LLM work happens in two places here — the Next.js/Workers app (TypeScript) and the pipeline lanes (Python). Both trace to Langfuse, and the Python uploader's REST payload is parity-pinned to the TypeScript client's shape: same trace structure from either runtime, so a dashboard doesn't care which side of the stack produced a span.

Offline-first: capture is not shipping

Pipelines don't take a hard dependency on an observability SaaS being up. Every lane writes trace events as JSONL locally, always — even with no Langfuse credentials configured. Tracing can never block or break a run. A separate uploader ships batches to Langfuse's ingestion API later, and it's idempotent: a per-directory ledger tracks what shipped, so re-running the uploader never duplicates events.

That decoupling buys durability (traces survive network failures), replay (reprocess old runs into a fresh project), and honest structured logging — append-only, schema'd, machine-readable — as a side effect.

What a trace contains

Per-call: model, tokens, latency, cost. Per-lane: spans for each stage — retrieve → rerank → synthesize → judge — so a quality regression is attributable to a stage, not to "the pipeline". Judge scores land next to the traces they evaluate, which turns "which prompt version regressed faithfulness?" into a query instead of an investigation — the closing of the loop the Evaluation & Feedback guide builds toward.

The wider net

The case-study system layers LangSmith over OpenTelemetry on a single trace across Next.js, Python, and Rust — with eval failures routed to annotation queues, so failures become labeled data rather than log lines. Cost telemetry feeds per-workflow daily budgets with a global kill switch: observability closing the loop into control, not just visibility.

The invariants

  • Two runtimes, one parity-pinned trace shape — a dashboard never cares which side of the stack produced a span.
  • Capture is JSONL-locally-always, even with no credentials configured — tracing can never block or break a run.
  • The uploader is idempotent by ledger: re-running it duplicates zero events.
  • Every lane run is spanned per stage (retrieve → rerank → synthesize → judge), so regressions attribute to a stage.