Mimir.Pricing (Mimir v0.7.0)

Copy Markdown View Source

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:

  1. the config table (:mimir, :pricing) entry, when it sets that rate;
  2. the vendored LiteLLM pricing DB (loaded once, memoized in :persistent_term), trying the bare model_id, then "<provider>/<model_id>";
  3. zero for input and output, 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

rates()

@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.

usage()

@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

cost_microdollars(model, usage)

@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.