Dsxir.Cost (dsxir v0.4.0)

Copy Markdown

Provider-independent record of token usage and cost for LM work.

Carries a full breakdown — input/output/cache-read/cache-write/reasoning token counts and their per-bucket costs, a total_cost, and a currency — plus a calls counter (1 for a single LM call, N after aggregation via merge/2 or sum/1). Token and cost fields are nil when the provider did not report them; nil is distinct from 0. Mapping from a specific provider's usage struct lives in the corresponding Dsxir.LM implementation, not here.

Summary

Functions

Field-wise sum of two costs. nil is the additive identity per field; a field that is nil in both stays nil. calls always adds. The first non-nil currency is kept.

Folds a list of costs into one, seeded with zero/0.

Flattens a cost into the numeric measurement map emitted on [:dsxir, :predictor, :stop]. Keeps the legacy keys :tokens_in, :tokens_out, and :cost; adds the cache/reasoning token breakdown. Values stay nil when unreported.

Runs fun and returns {result, %Dsxir.Cost{}} where the cost is the sum of every predictor call ([:dsxir, :predictor, :stop]) and embedding call ([:dsxir, :lm, :embed, :stop]) emitted within the block — including from fan-out workers that replay the Dsxir.Settings snapshot.

Additive identity: all token/cost fields nil, calls: 0.

Types

t()

@type t() :: %Dsxir.Cost{
  cache_read_cost: float() | nil,
  cache_read_tokens: non_neg_integer() | nil,
  cache_write_cost: float() | nil,
  cache_write_tokens: non_neg_integer() | nil,
  calls: non_neg_integer(),
  currency: String.t() | nil,
  input_cost: float() | nil,
  input_tokens: non_neg_integer() | nil,
  output_cost: float() | nil,
  output_tokens: non_neg_integer() | nil,
  reasoning_cost: float() | nil,
  reasoning_tokens: non_neg_integer() | nil,
  total_cost: float() | nil
}

Functions

merge(a, b)

@spec merge(t(), t()) :: t()

Field-wise sum of two costs. nil is the additive identity per field; a field that is nil in both stays nil. calls always adds. The first non-nil currency is kept.

sum(costs)

@spec sum([t()]) :: t()

Folds a list of costs into one, seeded with zero/0.

to_measurements(cost)

@spec to_measurements(t()) :: map()

Flattens a cost into the numeric measurement map emitted on [:dsxir, :predictor, :stop]. Keeps the legacy keys :tokens_in, :tokens_out, and :cost; adds the cache/reasoning token breakdown. Values stay nil when unreported.

track(fun)

@spec track((-> result)) :: {result, t()} when result: var

Runs fun and returns {result, %Dsxir.Cost{}} where the cost is the sum of every predictor call ([:dsxir, :predictor, :stop]) and embedding call ([:dsxir, :lm, :embed, :stop]) emitted within the block — including from fan-out workers that replay the Dsxir.Settings snapshot.

Pushes a unique scope id onto the :_cost_scope stack in Dsxir.Settings (propagated to workers via Settings.run/2) and attaches a telemetry handler that accumulates matching events in an ephemeral ETS table, folded on exit. No long-lived process. Handler and table are always torn down, including when fun raises.

zero()

@spec zero() :: t()

Additive identity: all token/cost fields nil, calls: 0.