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 Spectra Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Spectra is zero-instrumentation AI usage tracking for Laravel: it hooks the HTTP stack your AI calls already go through (Laravel HTTP client, Prism, Laravel AI SDK, openai-php, Guzzle), prices each call from a shipped catalog, stores a row per request, and gives you budgets plus a dashboard.

Before you install v2 (currently 2.0.1):

  1. Check the platform floor first. v2 requires Laravel 12 or 13 and PHP 8.2–8.5. Laravel 11 support is dropped. If you are on 11, upgrade the framework before touching Spectra — do not try to run both upgrades at once.
  2. Read UPGRADE.md before composer update. Several fixes change stored values (token counts, cost, daily-stat totals) rather than just code paths, so you want to know what your history will look like afterwards.
  3. Publish and run migrations. v2 adds columns to the requests table and matching totals to daily stats: cached_tokens, cache_write_tokens, page_count, currency, error_type, error_message, parent_span_id, conversation_id, conversation_turn, request_source, laravel_request_id, ip_address, user_agent. Re-publish the config too and diff it against yours. If you point spectra.storage.connection at a dedicated connection, read the migrate:fresh gotcha below before you run it — 2.0.1 makes the migrations idempotent, which fixes the failure but also silences a signal you may want.

Where to look first:

  • config/spectra.php — specifically tracking.trace_per_request, tracking.use_traceparent, prompt storage, budgets, storage.connection, and the new spectra.costs.exchange_rates block.
  • The pricing catalogs (12 of them, refreshed to October 2026). Skim the catalog for the providers you actually use and confirm your model ids resolve.
  • The dashboard — the new request detail page is the fastest way to understand what v2 records, including the input (uncached / cache read / cache write) and output (answer / reasoning) token breakdown.

First use case: install, migrate, then make one real streaming call through your existing client — no code changes. Open the dashboard, click into the request, and confirm three things: the model resolved (not "unknown"), the token breakdown is non-zero on the streamed call, and the cost is non-zero when you know the call was billable. If those three hold, the integration is working.

For local development, vendor/bin/testbench workbench:build seeds demo data so you can work on the dashboard without burning real API credits. That is a workbench-only command — never wire it into an app.

Implementation Patterns

Automatic tracking is the default path — resist wrapping it. v2 tracks streams as the app consumes them, including SSE, newline-delimited JSON, streamed JSON arrays, streams the app stops reading early, and error events sent mid-stream. If you wrote any "buffer the stream so Spectra sees the body" workaround in 1.x, delete it; that pattern is exactly the bug v2 fixes.

Trace per request is the new organising concept. All AI calls inside one HTTP request, queued job, or Artisan command share a trace id, jobs dispatched during a request inherit it, and an inbound W3C traceparent is joined with its span as the OpenTelemetry parent. Day-to-day usage:

  • Do nothing for the common case — you get grouping for free.
  • Use Spectra::currentTraceId() when you want to attach the id to your own logs or error reports, so a Sentry event and a dashboard trace line up.
  • Use Spectra::newTrace() in long-running workers, queue consumers, or anywhere the "request" boundary is blurry, when you want a fresh logical unit rather than inheriting whatever came before.
  • Set tracking.trace_per_request and tracking.use_traceparent deliberately. If your app sits behind an existing observability pipeline that already emits traceparent, use_traceparent lets you join traces rather than run two parallel id spaces.

Scope your tracking context explicitly — this is the pattern v2 forces on you. Spectra is now a scoped service. Global tags, trackable, pricing tier, and metadata no longer leak across queued jobs and Octane requests, which is a real correctness win, but it means you can no longer set them once in a service provider and assume they persist. The correct shape is:

  • Set the trackable (user, team, tenant) at the start of each job's handle() or each controller action, right before the AI call.
  • Set global tags and metadata at the same point, not in AppServiceProvider::boot().
  • For queued jobs that dispatch further jobs, set context again in the child; the trace id carries over, the rest does not.

Per-user and per-tenant budgets. Budgets resolve through the trackable, so the trackable must be set before the call and must match what you query later. With a morph map in play, v2 stores trackables consistently — confirm your budgets actually resolve after upgrading (see Gotchas).

Queued and async work. Polled async jobs (video, background responses, predictions, queued jobs) now add only the delta per repeat write instead of re-counting the whole job on every poll. If you have your own polling loop that re-reads a prediction every few seconds, keep it — it is now cheap in stats and budget terms.

Pricing configuration as a first-class concern. v2 adds cache writes, long-context pricing (aboveInputTokens), date-effective prices (since), audio and image token rates, fast/ultrafast tiers, megapixel/page/request units, aliases, retired models, per-catalog currency with exchange rates, provider-reported costs, and regional surcharges. Practical consequences:

  • Populate spectra.costs.exchange_rates if you have catalogs in different currencies — otherwise your aggregate spend numbers are mixing units.
  • Rely on aliases, dated snapshots, and routing suffixes rather than hardcoding exact model ids; the resolver is designed to handle them and the model the provider reports as served is priced first.
  • If you maintain an in-house model or an unpriced provider, add a catalog entry rather than letting it fall through — an unpriced call is a silent $0 in your cost reporting.

Storage topology is a config decision, not an afterthought. spectra.storage.connection lets you keep Spectra's tables somewhere other than your default connection. That is a reasonable choice for a high-write analytics workload, but it splits your migration story in two: your normal php artisan migrate flow covers both, while migrate:fresh, migrate:refresh, and any DB-reset tooling generally do not. Treat the Spectra connection as a first-class part of your deploy and reset scripts, not an extra.

Pruning and retention. Schedule spectra:prune in the console kernel with --stats-days to keep daily stats longer than raw requests. Prune now deletes stored media and works in chunks, so it is safe on large tables but takes time — run it off-peak. Confirm the configured disk permissions allow media deletion, especially if media lives off the default disk.

Dashboard as the debugging surface. The new request detail page shows token breakdown, error details, request origin, and a Trace card with every AI call of the same request as a timeline with total cost, tokens, and time. When a cost looks wrong, start there before reading rows.

Gotchas and Tips

Your historical numbers are now wrong in specific, known ways. This is the biggest thing to communicate to stakeholders:

  • Streaming was under-counted badly in 1.x. Streamed calls often stored a zero-token row with an empty body. Those rows cannot be retroactively repaired — the token counts were never captured. Expect your pre-upgrade token and cost history to understate streaming-heavy workloads.
  • Anthropic prompt caching was billed far too low. Cache reads were subtracted from input_tokens (which already excludes them) and cache writes were never billed at all. If you run heavy Anthropic caching, expect a visible step up in reported cost that is a correction, not a regression.
  • Gemini thinking tokens, Gemini tool-use prompt tokens, and xAI reasoning tokens are now billed. Same story — costs rise because they were previously missed.
  • Polled async jobs were counted once per poll in daily stats and budgets. Historical daily stats for video/prediction workloads may be inflated; if a budget was blown by polling, it may have been an artefact.
  • Audio uploads with the file first lost their model and priced at $0. Historical multipart audio costs are wrong.

When you roll v2 out, annotate the dashboard or your internal reports with the upgrade date so nobody reads the discontinuity as a real spend spike.

The scoped-service change is the most likely source of silent breakage. If any of your code (or a package) does Spectra::tags([...]), sets a trackable, or sets a pricing tier in a service provider, a middleware that only runs once, or a singleton boot path, it will now not apply where you expect — especially under Octane or in queue workers. Audit for those calls and move them next to the AI invocation.

Dedicated spectra.storage.connection + migrate:fresh leaves ghost tables. This is what 2.0.1 fixes, and it is worth understanding rather than just consuming the fix. migrate:fresh only drops tables on the default connection, so when Spectra stores its tables elsewhere they survive the wipe while their migration rows are cleared. The next migrate then hit "relation already exists". 2.0.1 makes both migrations skip tables and columns that already exist, so the run now succeeds — but the trade-off is that a stale Spectra schema is no longer a loud error:

  • After a migrate:fresh on an app with a dedicated Spectra connection, the Spectra tables are still the old ones with old data. If you expected a clean slate, drop them explicitly (migrate:fresh --database=<spectra-connection> if that connection is separately configured, or drop manually) before migrating.
  • Because the migrations are now skip-if-exists, a half-applied schema also stops being loud. If a previous migration died partway through, missing columns will not be re-added. After upgrading to 2.0.1, verify the v2 columns actually landed on the Spectra tables — do not assume migrate exited 0 means the schema is complete.
  • Local resets, CI database setup, and any RefreshDatabase-style tooling in the host app all need to account for the second connection. Pin this in your test bootstrap now rather than debugging "why do my tests see last week's requests".

Morph maps need a data check. In 1.x, trackables were stored with the full class name but looked up by morph alias, so budgets and per-user usage found nothing in apps with a morph map. v2 fixes lookups, but existing rows still hold class names. If you use a morph map and had budgets that "didn't work", inspect the stored trackable values and decide whether to backfill or accept a clean break at the upgrade date.

Budget alerts are now once-per-period via the cache. This fixes both the PHP-FPM "fires on every request" and the long-running-worker "never fires again" problems, but it moves the semantics onto your cache:

  • Use a shared cache store (Redis, database, memcached) in multi-server setups. The array driver gives you per-process dedupe, i.e. effectively none.
  • In tests, flush the cache between alert assertions or the second test will see a suppressed alert.
  • If you previously disabled or throttled alerts yourself because they were noisy, remove that workaround.

Failures no longer blow up the app — which means they now need watching. Saving a tracked request that fails (e.g. a duplicate daily-stat row under load) is reported rather than thrown. That is correct for production, but: update any tests that assert an exception, and make sure your exception reporter surfaces Spectra's reported failures. Otherwise you get silent gaps in tracking. Related: provider error responses are now stored with the provider's message, and connection failures are recorded and still thrown — so your existing try/catch around provider calls is still required.

Prompt storage off does not mean nothing was stored. In 1.x the raw request body was saved even with prompt storage disabled. Check your prompts-side storage for legacy bodies and scrub or prune them if that matters for privacy or GDPR. Verify the setting actually behaves as you expect on a fresh call before assuming the old data is representative.

Trace data only exists from v2 onward. Older rows have no parent_span_id, conversation_id, or trace lineage, so the Trace card and conversation views will be empty for them. Plan dashboard work and support runbooks around "traces start at the upgrade date".

Treat use_traceparent as an input-validation surface. Joining a client-supplied W3C header means a caller can influence how your traces and spans group. Enable it when you control the upstream (an internal gateway, your own OTel collector); be more cautious on public endpoints, where forged trace ids could let a caller blend their requests into someone else's trace view.

Provider-reported tier now wins over your requested tier. If you deliberately downgrade a request or rely on a project-default tier, pricing follows what the provider says actually served it — which is more correct but means you can't hand-tune cost by setting a tier. Price expectations should come from the provider's report, not your intent.

Pricing lookups: check aliases and since dates when a new model ships. Retired models are kept and marked rather than removed, so historical rows still price. But a model released after your catalog snapshot will not resolve — the symptom is a request row with an unknown model or a $0 cost. The fix is a catalog update, not a workaround.

Clean up 1.x workarounds now that the causes are fixed. Remove: stream-buffering shims, try/catch around Replicate calls guarding the ISO-timestamp subtraction bug (a finished prediction threw into your own Http::post()), any "force a nonzero token count" hacks, and any manual dedupe around async polling.

Smaller things worth knowing:

  • OpenAI Responses latency is now whole-second provider timestamps, so latency charts are coarser there — don't chase sub-second precision in that provider's data.
  • spectra:prune chunked deletes are slower; schedule with headroom and confirm the configured disk permissions allow media deletion.
  • The dashboard's colour-blind-safe palette follows light/dark theme — if you embed or screenshot charts into reports, capture both themes before publishing.
  • The test suite runs on Pest 3, 4, or 5; if your app pins Pest, you're not forced to move.
  • Spectra::currentTraceId() and Spectra::newTrace() are the two extension points worth adopting immediately — surfacing the trace id in your own logs is the highest-value, lowest-effort integration in this release.
  • 2.0.1 is a drop-in patch on top of 2.0.0 (no new config keys, no schema changes of its own) — but its migration edits mean anyone who hit the "relation already exists" failure should composer update and simply re-run migrate, not rebuild their database.
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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk