The spending and turn limits of a user (spec §7.3) and who is told when one is near or reached (spec §7.4).
What is counted
- Money —
PhoenixKitAgent.Spend: the user's rows of the AI request journal whosemetadata.sourceis"agent", so a call that describes an attachment counts as well as a model call of a turn; every row with a cost, a failed call's included. Micro-dollars,$1 = 1_000_000. - Turns — the user's
usermessages with aturn_ref(one per turn), in any of their chats.
The windows are the calendar day and month of the site time zone. The
limits in force are PhoenixKitAgent.UserConfig.effective/1's: the
user's own, else the module's. A limit of 0 allows nothing.
The money limits apply only on an endpoint whose calls get what they cost
in the journal (PhoenixKitAgent.Models.reports_cost?/2: a model call of a
turn, or a picture's description on the vision endpoint, where the provider
reports a cost or phoenix_kit_ai has a price for the model): on another,
nothing would be counted, so only the turn limit applies there — the admin
pages say so for the default model. An endpoint that cannot be found keeps
the money limits.
When
check/3 runs before a turn, while the turn holds its locks
(PhoenixKitAgent.Turns), so the previous turn of the user — and every
journal row it wrote — is over by then. A turn that started under its
limits runs to its end, even past them.
Notices
Each is sent once per limit and period: a row of
phoenix_kit_agent_limit_events (user, kind, period, unique) is inserted
first with ON CONFLICT DO NOTHING, and only an insert that went through
sends anything.
- 80 % of a limit (
check/3) — an in-app notice to the user (PhoenixKit.Notifications.upsert_inapp/3, keyagent:limit:<kind>:<period>). Event kindwarn_80, period keycost:<period>orturns:<period>, so each of the three limits warns on its own. - A limit reached (
announce_reached/2) — the in-app notice to the user (the same key, so an unread warning becomes the new text), and, only when the user'snotify_on_limitis on, a notification of type"agent_limit"to the watchers (create_many/2, which respects a watcher who switched that type off). The two have their own events — the watchers' period key isw:<period>, written only when they are told — sonotify_on_limitswitched on later in the period still tells the watchers of that period, once.
A call refused by the phoenix_kit_ai budget is signalled by
phoenix_kit_ai itself (its limit_reached event, to its watchers and
to those of the task, guard_watchers/1); the turn's notice in the chat
says the budget is used up.
The watchers are agent_limit_watchers (empty: every Owner) and the user's
own notify_user_uuids, without the user, who is told as the user.
Notices name the user by e-mail and the limit with its numbers; nothing of a chat. A notice that cannot be sent is logged as the class of the failure.
Summary
Types
A limit of a user.
A limit as it stands: what was used of it in its period.
What a user used today and this month, and the keys of those periods.
Functions
Tells that the user of settings reached a limit (status from
check/3): once per limit and period — the user in the app, and the
watchers when the user's notify_on_limit is on.
Whether the user of settings (UserConfig.effective/1) may start a turn
on endpoint_uuid now: :ok, or {:error, {:limit_reached, status}} for
the first limit used up — the month's money, the day's money, the day's
turns. On :ok, a limit at 80 % or more is announced to the user (once per
period). Sends nothing about a limit reached: that is
announce_reached/2.
The watchers the module adds to the AI guard's own for its tasks
(watchers of PhoenixKitAgent.ai_tasks/0, called with the user's uuid
or nil): agent_limit_watchers, plus the user's notify_user_uuids when
their notify_on_limit is on, without the user. An empty
agent_limit_watchers adds nobody: the guard tells its own watchers
(every Owner by default) anyway.
Whether money counts for a call (:chat or :vision) on
endpoint_uuid (see the moduledoc): an endpoint whose such calls get a
cost (PhoenixKitAgent.Models.reports_cost?/2), or one that cannot be
found.
What user_uuid used in the day and month of now (see the moduledoc).
Who is told about the user's limits: agent_limit_watchers (empty: every
Owner) and the user's own notify_user_uuids, without the user.
Types
@type kind() :: :daily_cost | :monthly_cost | :daily_turns
A limit of a user.
@type status() :: %{ kind: kind(), used: non_neg_integer(), limit: non_neg_integer(), period_key: String.t() }
A limit as it stands: what was used of it in its period.
@type usage() :: %{ day_micro_usd: non_neg_integer(), month_micro_usd: non_neg_integer(), day_turns: non_neg_integer(), day_key: String.t(), month_key: String.t() }
What a user used today and this month, and the keys of those periods.
Functions
@spec announce_reached(PhoenixKitAgent.UserConfig.effective(), status()) :: :ok
Tells that the user of settings reached a limit (status from
check/3): once per limit and period — the user in the app, and the
watchers when the user's notify_on_limit is on.
@spec check(PhoenixKitAgent.UserConfig.effective(), String.t() | nil, keyword()) :: :ok | {:error, {:limit_reached, status()}}
Whether the user of settings (UserConfig.effective/1) may start a turn
on endpoint_uuid now: :ok, or {:error, {:limit_reached, status}} for
the first limit used up — the month's money, the day's money, the day's
turns. On :ok, a limit at 80 % or more is announced to the user (once per
period). Sends nothing about a limit reached: that is
announce_reached/2.
Options: :use — what the call on the endpoint is (:chat, the default,
or :vision, see money_counted?/2); :now.
The watchers the module adds to the AI guard's own for its tasks
(watchers of PhoenixKitAgent.ai_tasks/0, called with the user's uuid
or nil): agent_limit_watchers, plus the user's notify_user_uuids when
their notify_on_limit is on, without the user. An empty
agent_limit_watchers adds nobody: the guard tells its own watchers
(every Owner by default) anyway.
@spec money_counted?(String.t() | nil, PhoenixKitAgent.Models.call_kind()) :: boolean()
Whether money counts for a call (:chat or :vision) on
endpoint_uuid (see the moduledoc): an endpoint whose such calls get a
cost (PhoenixKitAgent.Models.reports_cost?/2), or one that cannot be
found.
@spec usage(String.t(), DateTime.t()) :: usage()
What user_uuid used in the day and month of now (see the moduledoc).
Who is told about the user's limits: agent_limit_watchers (empty: every
Owner) and the user's own notify_user_uuids, without the user.