Current section
Files
Jump to
Current section
Files
lib/asm/cost.ex
defmodule ASM.Cost do
@moduledoc """
Cost helpers for token usage projection and accumulation.
"""
alias ASM.Control
alias ASM.Cost.Models
@type usage :: map()
@type totals :: %{
required(:input_tokens) => non_neg_integer(),
required(:output_tokens) => non_neg_integer(),
required(:cost_usd) => float()
}
@spec from_usage(atom(), String.t() | nil, usage()) :: Control.CostUpdate.t()
def from_usage(provider, model, usage) when is_atom(provider) and is_map(usage) do
rates = Models.lookup(provider, model)
input_tokens = int_value(usage, :input_tokens)
output_tokens = int_value(usage, :output_tokens)
%Control.CostUpdate{
input_tokens: input_tokens,
output_tokens: output_tokens,
cost_usd: input_tokens * rates.input_rate + output_tokens * rates.output_rate
}
end
@spec accumulate(totals(), Control.CostUpdate.t()) :: totals()
def accumulate(current, %Control.CostUpdate{} = update) when is_map(current) do
%{
input_tokens: int_value(current, :input_tokens) + update.input_tokens,
output_tokens: int_value(current, :output_tokens) + update.output_tokens,
cost_usd: float_value(current, :cost_usd) + update.cost_usd
}
end
defp int_value(map, key) when is_map(map) do
map
|> Map.get(key, Map.get(map, Atom.to_string(key), 0))
|> case do
value when is_integer(value) and value >= 0 -> value
value when is_float(value) and value >= 0 -> trunc(value)
_ -> 0
end
end
defp float_value(map, key) when is_map(map) do
map
|> Map.get(key, Map.get(map, Atom.to_string(key), 0.0))
|> case do
value when is_float(value) -> value
value when is_integer(value) -> value * 1.0
_ -> 0.0
end
end
end