Skip to main content
A span is a unit of work inside a trace that is not an LLM call or a tool call — retrieval, reranking, planning, validation, parsing, a database query, or any app operation you want to see and time. Spans are the catch-all. Use a generation for LLM calls and a tool call for tools; use a span for everything else.

Record a span

Create spans inside the trace root callback so they nest under the trace.
import { startActiveObservation } from "@langfuse/tracing";

const ranked = await startActiveObservation(
  "rerank-results",
  async (span) => {
    span.update({ input: { candidates: candidates.length } });
    const result = await rerank(candidates);
    span.update({ output: { kept: result.length } });
    return result;
  },
  { asType: "span" },
);
{ asType: "span" } is the default, so you can omit it.

Nesting spans

Spans can contain other spans, generations, and tool calls. Anything created inside a span’s callback becomes its child, which is how you model a multi-step sub-task:
support-agent              ← trace root
└─ answer-question         ← span
   ├─ retrieve-context     ← span
   │  └─ search_docs       ← tool call
   └─ draft-reply          ← generation

What to record

FieldUse
inputInputs to the operation (keep it a small summary if large)
outputResult or a summary of the result
level + statusMessageMark errors (level: "ERROR")
metadataOperation-specific detail for debugging
Spans are for app logic that the model and tools do not cover. Start with generations and tool calls; add spans where you need to time or debug a specific step.

Next steps

Threads & context

Group conversations and attach users, sessions, and metadata.

Trace contract

The exact shape Lemma reads.