Per-model token prices for turning a %Nous.Usage{} into a dollar figure.
Prices are quoted per 1M tokens in USD, the way every vendor publishes
them. Each entry carries an :input and :output rate plus optional
:cache_read (a cache hit) and :cache_write (creating a cache entry)
rates, because cached input is billed at a different rate than fresh input —
which is the whole reason %Nous.Usage{} tracks cache_read_input_tokens
and cache_creation_input_tokens separately.
Lookup
lookup/2 is keyed by {provider, model} where provider is one of
Nous.Model.provider/0 and model is the API model id string.
Resolution order:
Exact match on
{provider, model}.Longest-prefix match — model ids carry dated or variant suffixes (
"gpt-4o-2026-05-13","claude-opus-4-5-20251101"), so the entry whose model string is the longest prefix of the requested id wins. The prefix must end on a-boundary. Longest wins is what keeps"gemini-2.5-flash-lite-preview-09-2025"on the Flash-Lite price instead of falling into the pricier"gemini-2.5-flash"entry.The trade-off: an unlisted sibling variant inherits its family's price rather than reporting
:unknown(ask for"o1-mini"and you get the"o1"rate). Add the sibling through the override config below when that matters.Local providers (
:ollama,:lmstudio,:llamacpp,:vllm,:sglang) resolve to all-zero rates: you run the weights on your own hardware, so there is no per-token invoice. Zero is the honest answer here, not:unknown.Otherwise
:unknown. A wrong price is worse than no price, so an unrecognised model never gets a guessed rate and never raises.
Operator overrides
Fine-tuned, self-hosted, brokered and enterprise-contract models cannot be priced from a public table. Supply your own rates, which are merged over the built-in table (so they also win on exact keys):
config :nous, :model_prices, %{
{:openai, "ft:gpt-4.1-2026-01-01:acme:support:abc123"} =>
%{input: 3.0, output: 12.0, cache_read: 0.75},
{:groq, "llama-3.3-70b-versatile"} => %{input: 0.59, output: 0.79}
}Omitted optional keys default conservatively: a missing :cache_read bills
cache hits at the full :input rate (no published discount to apply), and a
missing :cache_write bills cache creation at zero (no published surcharge,
which is how OpenAI prompt caching works).
Price snapshot
Prices below were recorded on 2026-08-14 from the vendors' official pricing pages:
- https://developers.openai.com/api/docs/pricing
- https://platform.claude.com/docs/en/about-claude/pricing
- https://ai.google.dev/gemini-api/docs/pricing
This is a snapshot and it will go stale — vendors reprice, and this
table is only refreshed when someone remembers to. Treat the numbers as an
estimate, and when they are wrong for you, correct them with
config :nous, :model_prices (above) rather than waiting for a release.
Only models whose published rates were confirmed at that date are listed. Deliberate omissions:
- Audio, image, realtime and embedding models — those are billed per
modality (and sometimes per character or per second), which
%Nous.Usage{}cannot distinguish from text tokens. :vertex_ai— Gemini on Vertex is invoiced by Google Cloud on its own rate card, which is not the Developer API rate card.:groq,:openrouter,:together,:mistral,:custom— broker and per-account pricing that varies by model and contract.
Anything not listed resolves to :unknown, which is the honest answer.
Summary
Types
A price table entry as written in the table or in operator config.
A resolved price, per 1M tokens in USD. All four rates are always present.
Functions
Look up the price for provider and model.
The effective price table: the built-in snapshot with
config :nous, :model_prices merged over it.
Types
Functions
@spec lookup(Nous.Model.provider(), String.t()) :: price() | :unknown
Look up the price for provider and model.
Returns a price/0 with all four rates filled in, or :unknown when no
entry matches. See the moduledoc for the exact resolution order (exact
match, then longest prefix, then zero for local providers).
Examples
iex> price = Pricing.lookup(:ollama, "llama3.3:70b")
iex> {price.input, price.output}
{0.0, 0.0}
iex> Pricing.lookup(:openai, "no-such-model")
:unknown
@spec table() :: %{required({Nous.Model.provider(), String.t()}) => entry()}
The effective price table: the built-in snapshot with
config :nous, :model_prices merged over it.
Entries are returned as written (optional cache rates may be absent);
lookup/2 is what fills in the defaults.