Token usage -> integer microdollar cost.
Rates are integer µ$ per million tokens: input:, output:, and optional
cache_read: and cache_write:. Each rate resolves on its own for a
"provider:model_id" key:
- the config table (
:mimir, :pricing) entry, when it sets that rate; - the vendored LiteLLM pricing DB (loaded once, memoized in
:persistent_term), trying the baremodel_id, then"<provider>/<model_id>"; - zero for
inputandoutput, so an unpriced model never crashes metering.
A config entry that sets only input: and output: (a negotiated rate, say) still
takes the vendored DB's list cache rates unless it sets its own.
An explicit 0 in a config entry is honored as the operator's rate: that field
prices free, same as any other rate the config table sets. This differs from a
zero in the vendored DB, which counts as missing (below) — the DB's zero marks an
untracked cost LiteLLM hasn't filled in, not a negotiated zero-cost rate.
A misconfigured entry is loud: a key outside input:/output:/cache_read:/
cache_write:, or a rate that isn't a non-negative integer, raises
Mimir.Pricing.InvalidConfigError naming the model and the offending key/value,
whenever that model's rate is resolved — nothing is memoized, so every call
re-validates. Mimir.Guard rescues this specific exception and halts instead of
raising mid-run; calling this module directly does not.
Cache tokens (:cache_read_input_tokens, :cache_creation_input_tokens) price at the
cache rates. With no cache rate from either source they price at the input rate rather
than at zero by default — an unpriced model's input rate is itself 0, though, so its
cache tokens still cost 0, same as before — and [:mimir, :pricing, :no_cache_rate]
fires, only when such tokens are present. Its measurements are the token counts priced
that way; its metadata is %{model: model}. A zero cache cost in the vendored DB counts
as no rate.
The vendored DB is converted from LiteLLM's USD/token floats at load time:
round(cost * 1.0e12) → integer µ$/M tokens, so the hot path is integer math only.
Refresh it with mix mimir.pricing.refresh; :mimir, :pricing_db_path overrides its
path (useful in tests).
Summary
Types
Integer µ$ per million tokens: a config-table entry, or a vendored DB entry.
Token counts, atom-keyed; a missing key counts as zero. The two cache keys
also tolerate an explicit nil (as zero), since a caller passing a decoded
wire map straight through may hand one over for an absent cache count.
Functions
Cost of usage against model's rates, in integer microdollars. Resolves
the rates as documented above; an unpriced model costs 0.
Types
@type rates() :: %{ :input => non_neg_integer(), :output => non_neg_integer(), optional(:cache_read) => non_neg_integer(), optional(:cache_write) => non_neg_integer() }
Integer µ$ per million tokens: a config-table entry, or a vendored DB entry.
@type usage() :: %{ optional(:input_tokens) => non_neg_integer(), optional(:output_tokens) => non_neg_integer(), optional(:cache_read_input_tokens) => non_neg_integer() | nil, optional(:cache_creation_input_tokens) => non_neg_integer() | nil }
Token counts, atom-keyed; a missing key counts as zero. The two cache keys
also tolerate an explicit nil (as zero), since a caller passing a decoded
wire map straight through may hand one over for an absent cache count.
Functions
@spec cost_microdollars(String.t(), usage()) :: non_neg_integer()
Cost of usage against model's rates, in integer microdollars. Resolves
the rates as documented above; an unpriced model costs 0.