Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mutable Content Ai Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wraps AI provider interactions in Eloquent-backed models and services, so the fastest way in is to get the schema and config in place, then record a single run and inspect what it produced.

  1. Install and publish:
    composer require <vendor>/<package>
    php artisan vendor:publish --tag="ai-config"
    php artisan vendor:publish --tag="ai-migrations"
    
  2. Run the migrations, including the new extend_ai_tables_with_balance migration, then run the seeders — LovsSeeder and FieldsSeeder must be executed after the migration, otherwise the ai_balance_entry_kind LOV and field definitions will be missing:
    php artisan migrate
    php artisan db:seed --class="Vendor\\Package\\Database\\Seeders\\LovsSeeder"
    php artisan db:seed --class="Vendor\\Package\\Database\\Seeders\\FieldsSeeder"
    
  3. Open config/ai.php first. As of v0.5.0 this is where prices lives — per-model input/output prices and separate cache read/write prices, expressed in the provider's currency.
  4. First use case: record one run and look at the money.
    $run = Runs::record($response); // persists tokens, cache tokens, model, provider
    
    $run->cost();                                  // float, in the provider's currency
    $run->getAttribute(Run::FIELD_CACHE_READ_TOKENS);
    $run->getAttribute(Run::FIELD_CACHE_WRITE_TOKENS);
    
  5. Then zoom out with Services\Spending:
    app(Spending::class)->total($user);   // sums paid runs by currency
    app(Spending::class)->daily($user);   // per-day breakdown
    

If cost() returns null or 0.0 on a run you know was billed, the model name on the run almost certainly doesn't match a key in prices — check that before anything else.

Implementation Patterns

Wrap run recording in one place. Runs::record() is the single write path for token and cache-token fields. Put it behind your own action/job so every AI call in the app funnels through it; that guarantees cache read/write tokens are captured and cost() stays computable. Do not hand-write Run rows.

Treat cache tokens as a first-class cost lever. With FIELD_CACHE_READ_TOKENS / FIELD_CACHE_WRITE_TOKENS now persisted, you can quantify prompt caching rather than guess at it. A useful pattern is a small reporting query grouping runs by model and comparing cache-read cost against non-cache input cost from the same prices entry — that's the number that justifies (or kills) a caching strategy.

Spending dashboards, not raw sums. Spending::runs(), total() and daily() already aggregate by currency and by model. Build your admin/user billing views on top of these instead of re-deriving sum(cost) in queries — you get the currency split for free, which matters the moment a user hits two providers.

Reconcile balances instead of trusting the estimate. Balances::remaining() is defined as latest actual balance + top-ups − costs after it. The workflow that keeps this honest:

  • Record topUp() whenever you credit the provider account.
  • Periodically call setActual() with the real figure from the provider dashboard.
  • Let remaining() do the arithmetic from that anchor point forward.

Anything before the most recent setActual() is ignored by design, so a scheduled setActual() (daily cron hitting the provider's balance endpoint, or a manual admin action) is the pattern that keeps the number from drifting.

Use BalanceEntry for an audit trail. entries(), find() and delete() on Services\Balances make it easy to expose a read-only ledger in an admin panel. Prefer delete() over raw model deletes so any package-level invariants hold.

Multi-currency discipline. prices is per-provider-currency. Never sum cost() values across providers into a single scalar — aggregate per currency (which Spending already does) and convert at the presentation layer if you need one number.

Testing. Because prices is config-driven, tests can set a tiny fixture price table and assert exact costs. Stub the recording call with a fixed token payload rather than calling a real provider.

Gotchas and Tips

  • Migrate, then seed, in that order. extend_ai_tables_with_balance creates the ai_balance_entries table, but the ai_balance_entry_kind LOV and field definitions arrive via LovsSeeder / FieldsSeeder. Deployments that run migrate but skip seeding will fail when writing balance entries.
  • Costs are historical, not live. cost() recalculates from the current prices config. Change a price and every past run's cost changes too. If you need immutable billing figures, snapshot the computed cost onto your own column at record time.
  • Model name matching is exact. A slightly different model string (version suffix, provider prefix, alias) silently misses prices and yields a zero cost. Log a warning when cost() can't resolve a model.
  • Cache prices are separate keys. Don't assume the input price applies to cache reads — cache reads are usually far cheaper and cache writes often more expensive. Configure all four values per model or your cost numbers will be wrong in both directions.
  • remaining() is an estimate by construction. It's the last actual plus top-ups minus costs after it. If a provider bills you for usage the package never recorded as a Run (other tools, console usage, embedding calls), the estimate will read high. Regular setActual() calls are the antidote — treat it as reconciliation, not a source of truth.
  • daily() and timezones. Day bucketing follows your app timezone; if finance reports on UTC and your app runs on a local zone, daily totals will disagree at month boundaries. Standardise before comparing.
  • Currency is on the price entry, not the user. Spending::total() returns a per-currency breakdown rather than one figure — render it as multiple amounts or convert deliberately; don't cast it to a single float.
  • Extension point: the prices config is the right place to hang per-tenant or per-plan overrides (resolve them at runtime before calling cost()), rather than forking the Costs service. Similarly, wrap Balances::setActual() in a job if you want automated provider reconciliation, keeping Services\Balances untouched.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log