What to choose for an AI task, from live data only — there is no list of "best models" in the module. What a model can do comes first, its price second.
ranked/2— the models of the catalogues the endpoints use (PhoenixKitAI.Capabilities.catalogue/1) in groups:verified(the catalogue, or the endpoint's declaration, says the model has every need),possible(it lacks none, but at least one nobody verified — each row names those inunverified) andexperimental(models of either kind that look experimental: an id withpreview,betaorexp, a:freevariant or a free price, an alias, or no price in a catalogue that prices its models), each ranked by the estimated price of one call.- The needs are the task's, with its images / documents / image
generation needs replaced by the
filters(filterable/0; the task's own by default).refilter/2regroups for other filters. cheapest/2— the models fit for all of the task's hard needs, ranked by price, whatever they look like.worked/2— the models that already worked in this task, from the usage journal over the last 30 days.for_task/2— all of it, for the recommendations block.
Ranking
The price of one call is input_tokens × prompt_price + output_tokens × completion_price, with the catalogue's dollars-per-token prices and the
task's typical call size: typical_tokens: {in, out} from its
declaration (estimate: :declared); else the median input and output
tokens of its successful calls in the journal over 30 days
(estimate: :journal); else nothing is known (estimate: :rough) and the
rows are ranked by the input price alone, with no price per call.
Inside each group the rows are ranked so. A model is in verified when
PhoenixKitAI.Tasks.assess/2 says :fit, in possible when it says
{:unknown, _}, nowhere when it says {:unfit, _}; it must answer in
text unless image generation is among the needs. Retired models are left
out; deprecated ones are marked. The /models of
Anthropic, DeepSeek and Mistral list no prices, so their models are
unpriced (they are never mapped to an OpenRouter model by name), and no
provider but OpenRouter reports what a call cost; they come after the
priced ones, with one place kept for them among the rows.
A direct operator's plain list of ids (OpenAI, xAI, Z.ai — a :plain
source, Capabilities.source_kind/2) says nothing about any model: its
models are possible at best, never verified, marked
:capabilities_not_reported, and come after every model something is
known about. A capability filter does not hide them: «?» is not «no».
A model the catalogue gives no price is priced by the manual price an
endpoint there carries for it (PhoenixKitAI.ManualPrice), marked
:manual_price; so is a model that worked with costs counted from one.
Journal
worked/2 leaves out the rows the guard refused before any HTTP
(metadata.refused), so they count neither as calls nor as errors.
Summary
Functions
Whether the model id is an alias that can move to another model.
The cheapest fit models, limit (5) rows at most, each
%{model, name, provider, base_url, integration_uuid, context_length, capabilities, source_endpoint_uuid, price_per_call, price_known?, input_price_per_million, deprecated?, notes}. price_per_call is dollars, nil without a price
or an estimate of the call; input_price_per_million is dollars.
The capabilities the recommendations can be filtered by: images, documents, image generation.
Everything for a task (a key or a %TaskSpec{}): the groups of
ranked/2 with its filters, estimate and tokens, and worked
(worked/2). It also keeps spec for refilter/2 — but not the
catalogue: a panel keeps the result in its state.
for_task/2 for several tasks at once, as a map by task key: the
catalogues are read once and the journal is asked twice in all (the
median call sizes and the models that worked, each grouped by task), not
twice per task — for a page that lists every task.
The models for the task in groups, limit (5) rows each:
%{verified, possible, experimental, filters, estimate, tokens}. A row is
as in cheapest/2, plus unverified — the needs nobody verified ([]
in verified).
The recommendations of for_task/2 regrouped for other filters, from
the catalogues as the cache holds them now (or :catalogue, as in
cheapest/2); the journal part and the estimate are kept, nothing is
asked of the journal.
The catalogues the endpoints use, as the sources cheapest/2 ranks: one
per base URL with a catalogue or a plain list (kind: :plain),
%{base_url, kind, provider, endpoint_uuid, integration_uuid, models} —
provider the endpoints' provider key, which names a plain list's
operator. Read once and passed as :catalogue when several tasks are
listed together. The integration is the one of an endpoint there (enabled
ones first), which the create button hands to the endpoint form.
The models that worked in the task over the last 30 days (:days), most
calls first, ten at most, one row per model and operator: %{model, provider, calls, avg_cost, error_share, avg_latency_ms, cost_reported?, notes}. provider is the endpoint's provider key, nil when the
endpoint is gone. avg_cost is US dollars, a Decimal
(Request.format_cost/1 shows it) and nil when no call reported a
cost; error_share is 0.0–1.0. Refusals and cache hits are not calls.
Types
Functions
Whether the model id is an alias that can move to another model.
@spec cheapest(String.t() | PhoenixKitAI.TaskSpec.t(), keyword()) :: %{ rows: [map()], estimate: estimate(), tokens: {non_neg_integer(), non_neg_integer()} | nil }
The cheapest fit models, limit (5) rows at most, each
%{model, name, provider, base_url, integration_uuid, context_length, capabilities, source_endpoint_uuid, price_per_call, price_known?, input_price_per_million, deprecated?, notes}. price_per_call is dollars, nil without a price
or an estimate of the call; input_price_per_million is dollars.
Options: :limit, :days, and :catalogue — the sources to rank instead
of the cache's (%{base_url, kind, integration_uuid, models: [{id, caps}]}).
@spec filterable() :: [PhoenixKitAI.TaskSpec.need()]
The capabilities the recommendations can be filtered by: images, documents, image generation.
@spec for_task(String.t() | PhoenixKitAI.TaskSpec.t(), keyword()) :: map()
Everything for a task (a key or a %TaskSpec{}): the groups of
ranked/2 with its filters, estimate and tokens, and worked
(worked/2). It also keeps spec for refilter/2 — but not the
catalogue: a panel keeps the result in its state.
@spec for_tasks([String.t() | PhoenixKitAI.TaskSpec.t()], keyword()) :: %{ required(String.t()) => map() }
for_task/2 for several tasks at once, as a map by task key: the
catalogues are read once and the journal is asked twice in all (the
median call sizes and the models that worked, each grouped by task), not
twice per task — for a page that lists every task.
@spec ranked(String.t() | PhoenixKitAI.TaskSpec.t(), keyword()) :: map()
The models for the task in groups, limit (5) rows each:
%{verified, possible, experimental, filters, estimate, tokens}. A row is
as in cheapest/2, plus unverified — the needs nobody verified ([]
in verified).
Options: :filters (a subset of filterable/0; the task's own needs
among them by default), :limit, :days, :catalogue (as in
cheapest/2).
The recommendations of for_task/2 regrouped for other filters, from
the catalogues as the cache holds them now (or :catalogue, as in
cheapest/2); the journal part and the estimate are kept, nothing is
asked of the journal.
@spec sources() :: [map()]
The catalogues the endpoints use, as the sources cheapest/2 ranks: one
per base URL with a catalogue or a plain list (kind: :plain),
%{base_url, kind, provider, endpoint_uuid, integration_uuid, models} —
provider the endpoints' provider key, which names a plain list's
operator. Read once and passed as :catalogue when several tasks are
listed together. The integration is the one of an endpoint there (enabled
ones first), which the create button hands to the endpoint form.
@spec worked(String.t() | PhoenixKitAI.TaskSpec.t(), keyword()) :: [map()]
The models that worked in the task over the last 30 days (:days), most
calls first, ten at most, one row per model and operator: %{model, provider, calls, avg_cost, error_share, avg_latency_ms, cost_reported?, notes}. provider is the endpoint's provider key, nil when the
endpoint is gone. avg_cost is US dollars, a Decimal
(Request.format_cost/1 shows it) and nil when no call reported a
cost; error_share is 0.0–1.0. Refusals and cache hits are not calls.