Skip to content

Bug: exported trace usage mixes gross input tokens with cached-token breakdowns #1359

Description

@Diluka

Version

@voltagent/core: 2.8.0

Correction / clarification

This issue was originally framed as only totalTokens double-counting cached/reasoning token breakdowns. A more precise description is that the exported trace summary appears to mix two different input-token semantics.

The exported summary exposes promptTokens as gross input tokens, including cached input tokens, but totalTokens appears to treat promptTokens as if it were uncached input and then adds cachedTokens again.

Trace evidence

In an exported VoltAgent trace, the top-level trace.usage object reported:

{
  "promptTokens": 389461,
  "completionTokens": 1990,
  "totalTokens": 726377,
  "cachedTokens": 333440,
  "reasoningTokens": 1486
}

The root/agent span in the same trace had these attributes:

usage.prompt_tokens = 389461
usage.completion_tokens = 1990
usage.cached_tokens = 333440
usage.reasoning_tokens = 1486
usage.total_tokens = 391451

The root/agent span is internally consistent if usage.prompt_tokens is gross input, including cached input:

391451 = 389461 + 1990

The uncached input portion would be:

389461 - 333440 = 56021

But the exported top-level summary reports:

726377 = 389461 + 1990 + 333440 + 1486

So the top-level summary appears to combine these fields as:

promptTokens + completionTokens + cachedTokens + reasoningTokens

Problem

The exported summary currently appears to expose:

promptTokens = gross input tokens, including cached input
cachedTokens = cached input token breakdown

But the exported totalTokens appears to calculate as if:

promptTokens = uncached input tokens
cachedTokens = additional cached input tokens

Those two interpretations conflict.

If cachedTokens is exposed separately and intended to be added to input for accounting, then promptTokens would need to represent uncached input. In this trace that would be 56021, not 389461.

If promptTokens represents gross input, then cachedTokens is a breakdown field and should not be treated as additional input in the exported summary total.

The same ambiguity applies to reasoningTokens if it is already included in completionTokens.

Impact

This affects downstream token accounting that reads the exported summary fields, for example:

{
  inputTokens: usage.promptTokens,
  outputTokens: usage.completionTokens,
  totalTokens: usage.totalTokens,
  reasoningTokens: usage.reasoningTokens,
  cachedInputTokens: usage.cachedTokens,
}

With the current exported summary, downstream consumers cannot tell whether promptTokens is gross input or uncached input. This can lead to cached input being counted twice or input totals being interpreted incorrectly.

Related

Related but separate from #1360, which is about provider/model-specific usage semantics in the onEnd hook and root span usage.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions