Traceway
Tracing

Tracing Structure

Span kinds, the span tree, metadata fields, and cost tracking.

Span kinds

Every span has a kind that describes what type of work it represents. The kind is a discriminated union on the type field.

llm_call

The most common kind. Represents a call to a language model.

{
  "type": "llm_call",
  "model": "gpt-4o",
  "provider": "openai",
  "input_tokens": 150,
  "output_tokens": 42,
  "cost": 0.00123
}
FieldTypeDescription
modelstringThe model identifier (gpt-4o, claude-3-opus, llama-3.1-70b, etc.)
providerstringOptional. The provider name (openai, anthropic, ollama, etc.)
input_tokensnumberOptional. Number of input/prompt tokens
output_tokensnumberOptional. Number of output/completion tokens
costnumberOptional. Estimated cost in USD

Token counts and cost can be set at span creation time (if you know them up front) or at completion time (more common, since you get token counts from the API response). If you pass kind in both the create and complete requests, the complete request's values take precedence.

custom

For any non-LLM step: database queries, vector searches, API calls, parsing, formatting, tool invocations.

{
  "type": "custom",
  "kind": "vector_search",
  "attributes": {
    "collection": "documents",
    "top_k": 10,
    "similarity": "cosine"
  }
}
FieldTypeDescription
kindstringA label for the kind of work (vector_search, http_request, parse, etc.)
attributesobjectArbitrary key-value pairs for additional context

fs_read / fs_write

File system operations, primarily used by the proxy when it intercepts file operations.

{
  "type": "fs_read",
  "path": "/data/config.json",
  "bytes_read": 1024
}
KindFields
fs_readpath (string), bytes_read (number)
fs_writepath (string), bytes_written (number)

The span tree

Spans within a trace form a tree through parent_id. When you create a span with a parent_id, it becomes a child of that span. The dashboard renders this tree visually, letting you expand and collapse branches.

summarize-document (trace)
  ├── chunk-document (custom: parser)
  │     ├── chunk-1 (custom: parser)
  │     └── chunk-2 (custom: parser)
  ├── summarize-chunk-1 (llm_call: gpt-4o-mini)
  ├── summarize-chunk-2 (llm_call: gpt-4o-mini)
  └── combine-summaries (llm_call: gpt-4o)

Depth is unlimited. In practice, most traces are 2-3 levels deep.

Automatic tree construction with the SDK

When you use tw.trace() and ctx.span(), the SDK manages parent-child relationships for you:

await tw.trace('pipeline', async (ctx) => {
  // This span is a root span (no parent)
  await ctx.span('step-1', async (span) => {
    // Nested spans could be created with the low-level API
    // using span.id as parent_id
  });

  // This is also a root span (sibling of step-1)
  await ctx.span('step-2', async (span) => {
    // ...
  });
});

If you need deeper nesting with the low-level API:

const trace = await tw.createTrace('deep-pipeline');

const parent = await tw.startSpan({
  traceId: trace.id,
  name: 'outer',
  kind: { type: 'custom', kind: 'orchestrator', attributes: {} },
});

const child = await tw.startSpan({
  traceId: trace.id,
  parentId: parent.id,  // nested under "outer"
  name: 'inner',
  kind: { type: 'llm_call', model: 'gpt-4o', provider: 'openai' },
  input: messages,
});

await tw.completeSpan(child.id, { text: 'response' });
await tw.completeSpan(parent.id, { result: 'done' });

Metadata and computed fields

Traceway computes additional fields after a span is completed:

FieldTypeDescription
duration_msnumberended_at - started_at in milliseconds
total_tokensnumberinput_tokens + output_tokens (for llm_call spans)
estimated_costnumberComputed from the model pricing table if not provided

Cost estimation

Traceway maintains a pricing table with 50+ models across OpenAI, Anthropic, and other providers. When a span is completed with a model and token counts, Traceway looks up the per-token price and calculates the cost.

Supported providers and example models:

ProviderModels
OpenAIgpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo, o1, o1-mini, o1-pro
Anthropicclaude-3-opus, claude-3-sonnet, claude-3-haiku, claude-3.5-sonnet, claude-3.5-haiku
Meta (via various providers)llama-3.1-8b, llama-3.1-70b, llama-3.1-405b
Googlegemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash
Mistralmistral-large, mistral-small, mixtral-8x22b

If a model isn't in the pricing table, the cost field is left empty. You can always provide your own cost value in the span's kind field.

The pricing table is updated with each Traceway release. Costs are per-token estimates and may differ from your actual invoice due to caching, batching, or pricing changes.

Trace-level aggregates

The dashboard rolls up span-level metrics to the trace level:

  • Total duration — from the earliest span start to the latest span end
  • Total cost — sum of all span costs
  • Total tokens — sum of all span token counts
  • Span count — number of spans in the trace
  • Status — if any span failed, the trace shows as failed

These aggregates are computed on-the-fly in the dashboard and API responses. They are not stored separately.

Input and output formats

The input and output fields on a span accept any JSON value. There is no enforced schema. Common patterns:

LLM calls — input is typically an array of messages, output is the response text or structured output:

{
  "input": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "What is the capital of France?" }
  ],
  "output": {
    "text": "The capital of France is Paris.",
    "finish_reason": "stop"
  }
}

Tool calls — input is the arguments, output is the return value:

{
  "input": { "city": "San Francisco" },
  "output": { "temperature": 62, "condition": "foggy", "unit": "fahrenheit" }
}

Custom steps — input and output are whatever makes sense for your step:

{
  "input": { "query": "machine learning", "top_k": 5 },
  "output": [
    { "id": "doc_1", "score": 0.95, "title": "Introduction to ML" },
    { "id": "doc_2", "score": 0.87, "title": "Neural Networks" }
  ]
}

The dashboard renders these as formatted JSON. Large values are collapsed by default and expandable on click.

On this page