spectra-php/laravel-spectra
Laravel Spectra is the Laravel integration for the Spectra PHP package. It provides config publishing, service-provider registration, and framework-friendly helpers so you can add Spectra to Laravel apps with minimal setup and familiar conventions.
Architecture fit. Spectra is a cross-cutting cost-observability layer that instruments Laravel's HTTP client and the major AI SDK stacks (Prism, Laravel AI SDK, openai-php, Guzzle), persists a row per AI call plus daily aggregates, and exposes budgets, alerts, OpenTelemetry spans and a dashboard. For a TPM this is a governance dependency, not a feature dependency: it is the system of record for "what did AI cost us, per user, per tenant, per feature, per request." It fits best in a Laravel monolith or modular monolith where AI spend is a material line item and where finance/product need attribution and budget guardrails. It fits poorly where AI calls happen outside Laravel (edge workers, Python services), since those calls will be invisible and your cost picture will silently under-report.
Integration feasibility. Standard Laravel package shape: composer install, publish config and migrations, migrate, optionally schedule spectra:prune. v2 adds roughly a dozen columns to the tracked-request table and matching daily-stat totals, plus two new config keys (tracking.trace_per_request, tracking.use_traceparent) and spectra.costs.exchange_rates. The trace model is the notable architectural addition: one trace per HTTP request/job/command, propagation into dispatched jobs, and adoption of an inbound W3C traceparent as the parent span. That is a genuine interoperability win if you already run OpenTelemetry, and it turns Spectra from a cost ledger into a per-request cost timeline.
v2.0.1 is a narrow corrective patch to the v2.0.0 migration set: both migrations now skip tables and columns that already exist, fixing "relation already exists" failures when Spectra uses a dedicated spectra.storage.connection. The root cause is worth registering as an environmental fact rather than a footnote — migrate:fresh only drops tables on the default connection, so with a dedicated storage connection the Spectra tables survive a fresh reset while the migration rows are cleared and the migrations show as pending. That surfaces in CI, ephemeral review environments, and local dev resets, i.e. exactly the places where nobody is watching a cost dashboard. The patch makes the migrations idempotent; it does not change the storage topology or add a drop path for the secondary connection.
Technical risk. The risks are concentrated in data continuity and alerting semantics, not in install mechanics:
Spectra is now a scoped service. This fixes real cross-request leakage (global tags, trackable, tier, metadata bleeding across queued jobs and Octane), but any in-house code that resolved Spectra and held state across requests will change behavior — usually for the better, occasionally confusingly.Key questions.
spectra.storage.connection, and if so what drops those tables today — our reset scripts, or nothing?Stack fit. Requires Laravel 12 or 13 and PHP 8.2–8.5; tests run on Pest 3, 4 or 5. That is a modern, supported baseline and a reasonable place for a package that touches framework internals. Provider coverage is broad and current (Claude 5.x, GPT-6/GPT-5.6, Gemini 3.8/4, Grok 4.5–4.7, Replicate), and retired models are retained and marked — important for historical reporting. Per-catalog currency with exchange rates and regional surcharges make this viable for multi-region billing, which is usually the point at which a homegrown cost table stops being maintainable. Pin to ^2.0.1 rather than 2.0.0; the fix is small but the failure it prevents is a blocked migration on a non-default connection, which is a bad thing to discover during a scheduled change window.
Migration path. Treat this as a schema-and-semantics migration, not a version bump:
spectra.storage.connection. If dedicated, fix the reset path in CI and staging explicitly (drop the Spectra tables in teardown, or accept idempotent re-application), and add a one-time schema assertion so "skipped because it existed" is distinguishable from "skipped because it was already correct."Compatibility. Check for: custom code resolving Spectra and holding it across requests or jobs; dashboards/queries touching the tracked-request table that assume the old column set; morph-map configurations (trackables now resolve by morph alias, which fixes budgets and per-user usage in apps using a morph map — but changes what existing rows resolve to); scheduled prune jobs that need the new --stats-days flag and chunked media deletion; any CI pinned to Pest 2 or Laravel 11; and any pipeline that runs migrate:fresh against a database carrying a dedicated Spectra connection, which prior to 2.0.1 would have left orphaned tables and a failing migration.
Sequencing. Framework upgrade → package upgrade (2.0.1+) + migrations on staging → cost reconciliation → historical-data decision → budget re-baseline and alert routing → trace propagation enablement (trace_per_request, use_traceparent) → dashboard rollout to stakeholders → prune schedule tightened. Trace propagation should come after cost validation, because correlating spans is only useful once the numbers under them are trustworthy.
Maintenance. Shipping twelve maintained pricing catalogs is the package's core ongoing value and its core ongoing dependency: model pricing changes monthly, and a lagging catalog silently misprices spend. Assign explicit ownership of "pricing freshness" — a monthly check that current models resolve and cost correctly — rather than assuming it is free. New pricing dimensions (date-effective since prices, long-context thresholds, audio/image token rates, megapixel/page/request units, aliases, exchange rates) add configurability that someone must own when rates or surcharges change. Add one durable item to the runbook: the reset/teardown procedure for the Spectra storage connection, now that it is a known sharp edge rather than an incident-in-waiting.
Support. Expect two support surfaces. Engineering owns schema, cache, and instrumentation. Finance/ops owns the definition of "cost" — catalog price versus provider-reported cost, currency conversion, and which tier applies. Write that definition down before the dashboard is widely shared; the new request detail page (input uncached/cache-read/cache-write split, output answer/reasoning split, error details) will invite line-item questions from budget holders, and the Trace card will invite questions about why one user action cost what it did. That is a feature, but only if someone can answer them. A third, thinner surface now exists around migration/reset behavior; keep it inside engineering and out of the finance conversation.
Scaling. Row-per-AI-call plus daily aggregates grows with traffic; chunked prune with media deletion and --stats-days is the right control, but the prune schedule must be set deliberately rather than inherited. Trace ids, parent_span_id, conversation_id and laravel_request_id add index surface — validate query plans on the dashboard's aggregate queries at realistic row counts. Budget-alert dedupe adds a cache dependency on the hot path; verify behavior with the production cache driver, not the test driver. A dedicated storage connection isolates Spectra's write volume from the app's primary — useful at scale, but it also means Spectra's tables are outside the blast radius of our standard reset and maintenance tooling, which is a deliberate trade to document rather than a detail to rediscover.
Failure modes.
Ramp-up. Engineering: roughly one sprint for upgrade, migration rehearsal, reconciliation and alert wiring, more if the Laravel 11 → 12/13 jump is bundled. Add a small, bounded increment for the storage-connection path — verifying reset behavior and wiring a schema check — which is cheap now and expensive to retrofit after an incident. Finance/ops: a short briefing plus a written cost-definition note; the step-change in reported spend must be communicated before the first post-upgrade report circulates, or it will read as a regression caused by the upgrade rather than a correction of prior underbilling. Net position: 2.0.1 does not change the shape of the v2 decision — it removes a concrete install-blocker for teams using a dedicated storage connection and, in doing so, shifts a hard failure into a soft one that requires a verification step. The release line remains a substantial correctness upgrade that makes Spectra defensible as a financial system of record, but it is a breaking release on three axes at once (framework floor, schema, billing semantics), so it should be scheduled and communicated as a data-migration event, not a routine dependency bump.
How can I help you explore Laravel packages today?