amarenkov/laravel-mutable-content-ai
A Laravel package for working with mutable AI-generated content, letting you store, edit, and refresh model output while keeping it versioned and easy to manage in your application.
Architecture fit
Run::FIELD_CACHE_READ_TOKENS / FIELD_CACHE_WRITE_TOKENS extend the existing journal rather than introducing a parallel store, so the data model remains a single append-oriented source of truth for usage.Services\Costs → Services\Spending → Services\Balances layering is a sensible separation of concerns: per-run pricing, per-actor aggregation, and derived account position. That maps cleanly onto the three product questions we typically need answered — what did this call cost, who spent it, and how much runway is left.Models\BalanceEntry + the ai.balance_entries table (migration extend_ai_tables_with_balance, LOV ai_balance_entry_kind) is an event-sourced ledger pattern. Good for auditability; it does mean the "remaining balance" figure is a derived estimate, not an authoritative provider value, which is the single most important framing for stakeholder communication.prices config key keeps pricing out of the database and in version-controlled deployment artifacts — defensible, but it makes pricing a deploy-time concern rather than a runtime one.Integration feasibility
Run LovsSeeder and FieldsSeeder after the migration) are the main integration hazard — easy to miss in CI/CD pipelines that run migrate without the seed workflow.Services\Spending aggregation assumes a paid-by-user dimension and per-currency, per-model grouping. That fits multi-tenant SaaS; single-tenant or B2C-without-accounts deployments will get little value from the Spending surface.Technical risk
prices config. Provider price changes, new model aliases, or regional price variants will silently produce wrong numbers. Silent-zero or null cost for an unpriced model is the failure mode to design against.remaining() = latest actual + top-ups − costs after it. This is only as good as setActual() discipline; it will diverge from the provider console and generate support tickets if surfaced to finance without a disclaimer.ai_balance_entry_kind would surface as runtime exceptions on the balance path.Spending groups by currency, so any consolidated total is our responsibility, not the package's.Key questions
Run::cost() persisted at Runs::record() time, or computed on read? This determines query cost, historical mutability, and whether a price correction retroactively rewrites the past.prices entry — exception, null, or zero? A fail-loud default is strongly preferred for finance-adjacent data.balance_entries append-only, and is delete() soft or hard? Hard deletes on a ledger undermine audit claims.Spending::daily() require a timezone, and does runs() paginate or stream?topUp()/setActual() writes?Stack fit
ai.* table prefix and LOV/enum-table conventions suggest an established schema idiom; align our internal analytics and BI tooling with those conventions rather than building a parallel warehouse table.Services\Spending is the natural integration point for admin dashboards, tenant billing exports, and internal margin reporting. Services\Balances is the natural integration point for ops alerting.Migration path
prices config block for every model currently referenced in our codebase in the same PR — do not ship the config key empty.extend_ai_tables_with_balance in the normal migration step.LovsSeeder then FieldsSeeder as a distinct, ordered step. Add this to the deploy runbook and to any environment bootstrap script; it must not depend on developer memory.setActual() from the provider console at cutover so remaining() starts from a known anchor.Compatibility
Sequencing
prices config. Verify token and cost fields are actually populating on live traffic.Spending::runs() / daily(). Two weeks of data before anyone sees a number.Balances::remaining() into ops alerting with a threshold and an explicit "estimate" caveat.prices upkeep with a reviewed, scheduled update process before the table is load-bearing for money.Maintenance
prices config upkeep. This is a recurring human task with a hard external dependency (provider price pages) and a silent failure mode. Assign a named owner and a review cadence — monthly at minimum, plus on every new model adoption.ai_balance_entry_kind) mean seeders become part of the schema lifecycle. Any future LOV additions carry the same "did you run the seeder?" risk.Support
remaining() is an estimate built from manually recorded top-ups and actuals.Scaling
Spending::daily() and total() are aggregation queries over the runs table. At volume, index on the paid-by-user column, currency, model, and the timestamp used for daily bucketing. Verify query plans early rather than after the dashboard times out.Failure modes
setActual() → remaining() drifts, alerting becomes noise and gets muted.Ramp-up
How can I help you explore Laravel packages today?