Skip to main content

Spans

A span represents a single operation within a trace - like an LLM call, tool execution, or retrieval step.

Span Structure

Every span contains:
PropertyDescription
spanIdUnique identifier
traceIdParent trace identifier
parentSpanIdParent span (for nesting)
spanKindType of operation
nameOperation name (e.g., model name, tool name)
startTimeWhen operation began
endTimeWhen operation completed
durationMsDuration in milliseconds
inputInput data
outputOutput data
status’completed’ or ‘error’
tokensToken usage (for LLM spans)
errorError message (if failed)

Span Types

Foil supports several span types to categorize different operations:

LLM Span

For language model API calls:
const span = await ctx.startSpan(SpanKind.LLM, 'gpt-4o', {
  input: messages
});

const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages
});

await span.end({
  output: response.choices[0].message.content,
  tokens: {
    prompt: response.usage.prompt_tokens,
    completion: response.usage.completion_tokens,
    total: response.usage.total_tokens
  }
});
Captured data: Model, messages, response, tokens, latency, TTFT

Tool Span

For function/tool executions:
await ctx.tool('web-search', async () => {
  return await searchAPI(query);
}, {
  input: { query }
});
Captured data: Tool name, arguments, result, duration, errors

Agent Span

For high-level agent orchestration:
const agentSpan = await ctx.startSpan(SpanKind.AGENT, 'research-agent', {
  input: { task: 'Research topic X' }
});

// Agent does multiple sub-operations...

await agentSpan.end({
  output: { findings: [...] }
});
Captured data: Agent name, task, sub-spans, final output

Retriever Span

For RAG retrieval operations:
await ctx.retriever('vector-db', async () => {
  return await vectorStore.similaritySearch(query, 5);
}, {
  input: { query }
});
Captured data: Query, retrieved documents, scores, latency

Embedding Span

For embedding generation:
await ctx.embedding('text-embedding-3-small', async () => {
  return await createEmbeddings(texts);
}, {
  input: { textCount: texts.length }
});
Captured data: Model, input count, vector dimensions

Chain Span

For pipeline/chain steps:
const chainSpan = await ctx.startSpan(SpanKind.CHAIN, 'rag-pipeline');

// Multiple operations in sequence...

await chainSpan.end({ output: finalResult });

Custom Span

For any other operation:
await ctx.wrapInSpan(SpanKind.CUSTOM, 'post-processing', async () => {
  return await customOperation(data);
});

Span Nesting

Spans automatically form a hierarchy:
await tracer.trace(async (ctx) => {
  // Root span (depth: 0)
  const agentSpan = await ctx.startSpan(SpanKind.AGENT, 'orchestrator');

  // Child spans (depth: 1)
  const planSpan = await ctx.startSpan(SpanKind.LLM, 'gpt-4o');
  await planSpan.end({ output: plan });

  // Another child
  const toolSpan = await ctx.startSpan(SpanKind.TOOL, 'search');

  // Grandchild span (depth: 2)
  const apiSpan = await ctx.startSpan(SpanKind.CUSTOM, 'api-call');
  await apiSpan.end({ output: results });

  await toolSpan.end({ output: results });
  await agentSpan.end({ output: 'done' });
});
Resulting hierarchy:
agent: orchestrator (depth: 0)
├── llm: gpt-4o (depth: 1)
└── tool: search (depth: 1)
    └── custom: api-call (depth: 2)

Recording Data

Input

Record what goes into the operation:
await ctx.startSpan(SpanKind.LLM, 'gpt-4o', {
  input: {
    messages: [...],
    temperature: 0.7,
    maxTokens: 1000
  }
});

Output

Record what comes out:
await span.end({
  output: {
    content: response.text,
    finishReason: 'stop'
  }
});

Tokens

For LLM spans, always record token usage:
await span.end({
  tokens: {
    prompt: 150,
    completion: 50,
    total: 200
  }
});

Timing

Record timing metrics:
await span.end({
  timing: {
    totalDuration: 1200,  // ms
    ttft: 300             // time to first token
  }
});

Errors

Record failures:
try {
  // operation
} catch (error) {
  await span.end({
    error: error.message,
    status: 'error'
  });
}

Custom Properties

Add metadata for filtering and analysis:
await ctx.startSpan(SpanKind.LLM, 'gpt-4o', {
  properties: {
    userId: 'user-123',
    feature: 'chat',
    promptVersion: '2.0',
    experimentGroup: 'treatment'
  }
});

Best Practices

Choose the span type that best represents the operation. This enables better filtering and analytics.
Token counts are essential for cost tracking and optimization.
Record enough context to debug issues, but be mindful of data size.
Always call span.end() even on errors. Use try/finally or wrapInSpan for automatic handling.
Use consistent naming (model names, tool names) to enable aggregation.

Next Steps