Nested traces

Model the full tree of a request — retrievers, tools, and chains — by nesting spans and generations inside a trace.

Real LLM apps aren't one call — they're retrievers, tool calls, and chains of model steps. Currai nests spans and generations inside a trace so you get the full tree of a request and can pinpoint exactly which step was slow or wrong.

Spans

A span is any unit of work that isn't itself a model call — a vector search, a tool invocation, a function. Create one with span() and end() it when the work finishes:

const trace = currai.trace({ name: "rag-answer" });

const retrieval = trace.span({
  name: "retrieve-docs",
  input: { query: question },
});
const docs = await vectorStore.search(question, { k: 4 });
retrieval.end({ output: { docIds: docs.map((d) => d.id) } });

const generation = trace.generation({
  name: "answer",
  model: "gpt-4o",
  input: prompt(docs),
});
generation.end({ output: answer });
trace = currai.trace(name="rag-answer")

retrieval = trace.span(name="retrieve-docs", input={"query": question})
docs = vector_store.search(question, k=4)
retrieval.end(output={"doc_ids": [d.id for d in docs]})

generation = trace.generation(name="answer", model="gpt-4o", input=prompt(docs))
generation.end(output=answer)

Arbitrary nesting

Spans and generations can be created on a span as well as on a trace, so the tree goes as deep as your app does — a tool span containing a generation containing another span:

const tool = trace.span({ name: "search-tool" });
const sub = tool.generation({ name: "rerank", model: "gpt-4o-mini", input });
sub.end({ output });
tool.end({ output: results });

Each node carries its own input, output, timing, and metadata, and is linked to its parent automatically. This works for agent loops, RAG pipelines, and multi-model chains alike.

Next: token cost, tracked for you.