Money as the module keeps it: an amount of US dollars as a Decimal, never a
float and never a scaled integer. A call's cost (Request.cost), the spend caps
(PhoenixKitAI.Budget), the task limits (PhoenixKitAI.TaskLimits) and the
prices per 1M tokens (PhoenixKitAI.ManualPrice) are all plain dollars.
to_decimal/1reads a number from anywhere (a provider's JSON float, a stored string, aDecimal); a float goes through its shortest decimal form, so3.0e-5is exactly0.00003, neverDecimal.from_float/1's binary tail.parse_dollars/1reads what a person typed.amount/1is the strict reader for an amount set in code or stored as a setting: aDecimalor a dollar string. An integer is refused there, because the released versions stored those amounts as integer millionths of a dollar (5_000_000meant $5), and reading one as dollars would loosen a cap a million times over.0is the one integer accepted (no amount).round_cost/1is the only rounding: a call's total cost, to the 18 decimal places of thecostcolumn (an attodollar — far below the cheapest token).to_json/1is how an amount is written into a JSON map: a string, so it reads back as the same decimal and never as a float.
Summary
Functions
An amount set in code or stored as a setting, as dollars: a Decimal or a
dollar string, from 0 to $1 000 000 000; the integer 0 is no amount.
{:error, :ambiguous_amount} for any other integer or a float (see the
moduledoc), :error for anything that is not an amount at all.
Whether a stored amount is in the shape the released versions wrote: an
integer, or a string of digits only ("5000000"), which meant millionths of a
dollar. A dollar string always has a decimal point or is "0".
Dollars typed by a person: finite, from 0 to $1 000 000 000. :error for
anything else — not a number, NaN, an infinity, a negative amount.
Whether the database keeps costs in dollars: core's V223 has made
phoenix_kit_ai_requests.cost and no legacy cost column (cost_cents,
cost_nanodollars) is left beside it. Checked once per node and remembered
once true; until then every call is refused
({:error, :money_migration_required}), because a sum of a column that is
not there — or of a new empty one beside the old costs — reads as no spend.
A call's total cost rounded to the 18 decimal places of the cost column.
An amount as it is written into a JSON map or a setting: a decimal string with
at least two places ("5.00", "0.10", "0.0000004"), so it reads back as the
same decimal, never as a float, and never looks like the released versions'
digits-only millionths (legacy_amount?/1).
Functions
An amount set in code or stored as a setting, as dollars: a Decimal or a
dollar string, from 0 to $1 000 000 000; the integer 0 is no amount.
{:error, :ambiguous_amount} for any other integer or a float (see the
moduledoc), :error for anything that is not an amount at all.
Whether a stored amount is in the shape the released versions wrote: an
integer, or a string of digits only ("5000000"), which meant millionths of a
dollar. A dollar string always has a decimal point or is "0".
Dollars typed by a person: finite, from 0 to $1 000 000 000. :error for
anything else — not a number, NaN, an infinity, a negative amount.
@spec ready?() :: boolean()
Whether the database keeps costs in dollars: core's V223 has made
phoenix_kit_ai_requests.cost and no legacy cost column (cost_cents,
cost_nanodollars) is left beside it. Checked once per node and remembered
once true; until then every call is refused
({:error, :money_migration_required}), because a sum of a column that is
not there — or of a new empty one beside the old costs — reads as no spend.
A call's total cost rounded to the 18 decimal places of the cost column.
A sum read back from the database (a Decimal, or nil for no rows) as dollars.
A number as a Decimal: a Decimal, an integer, a finite float (through its
shortest decimal form) or a decimal string (trimmed). :error for anything
else, NaN and infinities included.
An amount as it is written into a JSON map or a setting: a decimal string with
at least two places ("5.00", "0.10", "0.0000004"), so it reads back as the
same decimal, never as a float, and never looks like the released versions'
digits-only millionths (legacy_amount?/1).