PhoenixKitAI.Migrations (PhoenixKitAI v0.26.1)

Copy Markdown View Source

Module-owned versioned migrations for phoenix_kit_ai — the protocol core's mix phoenix_kit.update discovers through PhoenixKitAI.migration_module/0 (current_version/0, migrated_version_runtime/1, up/1, down/1). Same shape as PhoenixKitAgent.Migrations: two version readers, up/1 re-reading the version before it changes anything, the SQL kept as data (up_statements/2, down_statements/2).

This chain holds only objects core does not create. The tables phoenix_kit_ai_endpoints, phoenix_kit_ai_prompts and phoenix_kit_ai_requests stay in core's chain and are never touched here.

Tables (V1)

  • phoenix_kit_ai_model_capabilities — the snapshot of what the catalogue says each model an endpoint uses can do (PhoenixKitAI.Capabilities), one row per resolved base URL and model; the flags are yes, no or unknown.

V2: the task guard's settings and events

  • phoenix_kit_ai_task_settings — what an admin set for an AI task (one row per task key): its limits, the warn percent, and the guard mode and classifier fields later parts of the guard read.
  • phoenix_kit_ai_task_user_limits — a user's personal limits in a task (one row per task key and user).
  • phoenix_kit_ai_guard_events — what the guard noticed (kind, checked against the known kinds; a new kind is a new version). dedupe_key is unique where it is set, so a signal sent once per window is an insert that either happens or does not.
  • Two more flags on phoenix_kit_ai_model_capabilities: file_input and image_output (yes, no or unknown, default unknown), for what a later version of the capability cache reads.
  • An index on the journal (core's phoenix_kit_ai_requests): ((metadata->>'task'), inserted_at), with no condition on the status, for the task limits and the models that worked in a task. Core's schema check reports extra columns of its tables, not extra indexes, so it is not drift there; the index is dropped with V2.

V3: blocks

  • phoenix_kit_ai_guard_blocks — a person blocked from a group of AI tasks (block_group; a task's own key unless it names a group) until the block is lifted. The row keeps where it came from (the task and the flag), whether its reason is private, who set it and when, and who lifted it, when and why. One active block per group and person: a unique index on (block_group, user_uuid) where lifted_at is NULL; lifted blocks stay as the history. The guard event kinds of V2 already include off_purpose, off_purpose_signal, anomaly, blocked and unblocked.

V4: money in dollars

Core's V223 turned the journal's cost_cents (millionths of a dollar) into cost, dollars. V4 does the same for what this module stores, so every amount is dollars (PhoenixKitAI.Money). It is data, not shape, and it converts by shape, so a re-run converts nothing twice:

  • the spend caps ai_daily_budget, ai_daily_budget_per_endpoint and ai_daily_budget_per_user (core's phoenix_kit_settings, digits = millionths) are copied to ai_daily_cap, ai_daily_cap_per_endpoint and ai_daily_cap_per_user as dollar strings ("5.00"), only where the new key does not exist yet; the old keys are left as they were;
  • a money cell of a task's limits (phoenix_kit_ai_task_settings.limits, phoenix_kit_ai_task_user_limits.limits) that is a JSON number or a digits-only string becomes a dollar string;
  • an endpoint's manual price (phoenix_kit_ai_endpoints.provider_settings -> 'pricing') whose halves are numbers becomes dollar strings per 1M.

It needs core's V223 first (the cost column) and stops otherwise. Down to 3 turns the task limits and prices back into integer millionths — losing what is finer than a millionth ("0.0000004" becomes 0, and a money limit of 0 allows nothing) — and deletes the new cap keys, also ones set after V4 (the old keys still hold the released values).

Version marker

COMMENT ON TABLE phoenix_kit_ai_model_capabilities IS 'ai_schema:<N>'. No table, or one without an ai_schema: comment, reads as version 0.

Idempotency and rollback

Every statement guards itself (IF NOT EXISTS / IF EXISTS), so the chain can be re-run. down/1 to version 0 drops the snapshot table; it is rebuilt from the providers' catalogues on the next refresh.

Summary

Functions

The version this code expects the schema to be at.

Rolls back to opts[:version] (default 0). Migration context only. To 0 it drops every table of this chain — see the moduledoc.

The SQL down/1 runs, as data: each later version undone, newest first, then the marker re-stamped (to 0 there is no table left to stamp).

The installed version, read inside a migration through Ecto.Migration's repo(). No rescue: a version that cannot be read aborts the migration.

The installed version, read outside a migration (what mix phoenix_kit.update calls) through PhoenixKit's configured repo. An invalid prefix raises; a database that cannot be reached reads as 0, which only costs a redundant migration file because up/1 re-reads the version before it changes anything.

Applies the chain up to opts[:version] (default current_version/0). Migration context only. Re-reads the installed version first: a database already at (or past) the target is left alone.

The SQL up/1 runs, as data. target is how far to go (default current_version/0); 0 is nothing.

The table carrying the ai_schema:<N> marker.

Functions

current_version()

@spec current_version() :: pos_integer()

The version this code expects the schema to be at.

down(opts \\ [])

@spec down(keyword() | map()) :: :ok

Rolls back to opts[:version] (default 0). Migration context only. To 0 it drops every table of this chain — see the moduledoc.

down_statements(prefix \\ "public", target \\ 0)

@spec down_statements(String.t(), non_neg_integer()) :: [String.t()]

The SQL down/1 runs, as data: each later version undone, newest first, then the marker re-stamped (to 0 there is no table left to stamp).

migrated_version(opts \\ [])

@spec migrated_version(keyword() | map()) :: non_neg_integer()

The installed version, read inside a migration through Ecto.Migration's repo(). No rescue: a version that cannot be read aborts the migration.

migrated_version_runtime(opts \\ [])

@spec migrated_version_runtime(keyword() | map()) :: non_neg_integer()

The installed version, read outside a migration (what mix phoenix_kit.update calls) through PhoenixKit's configured repo. An invalid prefix raises; a database that cannot be reached reads as 0, which only costs a redundant migration file because up/1 re-reads the version before it changes anything.

up(opts \\ [])

@spec up(keyword() | map()) :: :ok

Applies the chain up to opts[:version] (default current_version/0). Migration context only. Re-reads the installed version first: a database already at (or past) the target is left alone.

up_statements(prefix \\ "public", target \\ 4)

@spec up_statements(String.t(), non_neg_integer()) :: [String.t()]

The SQL up/1 runs, as data. target is how far to go (default current_version/0); 0 is nothing.

version_table()

@spec version_table() :: String.t()

The table carrying the ai_schema:<N> marker.