Baton.Pricing behaviour (Baton v0.1.0)

Copy Markdown View Source

Behaviour for computing the cost of an LLM call.

Hardcoded prices go stale fast, so Baton does not ship authoritative prices. Instead it defines this behaviour and calls whichever module the host configures:

config :baton, pricing: MyApp.LLMPricing

Your implementation receives a usage map and returns a cost as Decimal.t(), or nil if the model is unknown (cost is then recorded as nil rather than a wrong number).

defmodule MyApp.LLMPricing do
  @behaviour Baton.Pricing

  @impl true
  def cost(%{model: "claude-sonnet-4-" <> _, input_tokens: i, output_tokens: o}) do
    Decimal.add(
      Decimal.mult(Decimal.new(i), Decimal.from_float(3.0 / 1_000_000)),
      Decimal.mult(Decimal.new(o), Decimal.from_float(15.0 / 1_000_000))
    )
  end

  def cost(_usage), do: nil
end

Baton.Pricing.Default is provided as a starting point and for tests, but you should supply your own and keep it current.

Summary

Callbacks

Return the cost in USD for a usage map, or nil if it can't be priced.

Functions

Compute cost via the configured pricing module.

Types

usage()

@type usage() :: %{
  :model => String.t() | nil,
  optional(:input_tokens) => non_neg_integer(),
  optional(:output_tokens) => non_neg_integer(),
  optional(:cache_read_tokens) => non_neg_integer(),
  optional(:cache_write_tokens) => non_neg_integer()
}

Callbacks

cost(usage)

@callback cost(usage()) :: Decimal.t() | nil

Return the cost in USD for a usage map, or nil if it can't be priced.

Functions

cost(usage)

@spec cost(usage()) :: {:ok, Decimal.t()} | {:error, :unpriced}

Compute cost via the configured pricing module.

Returns {:ok, Decimal.t()}, or {:error, :unpriced} when the module returns nil (unknown model, missing data, etc.).