phoenix1331/laravel-ai-headroom
Laravel AI Headroom gives Laravel apps a simple, configurable way to add AI features, manage prompts and providers, and keep token usage within safe limits. Integrates with Laravel's config, facades, and queues for reliable AI workflows.
laravel-ai-headroom v0.1.0 is a first release, so treat this as a greenfield setup. Start by checking config/ai-headroom.php, then run the registry command to confirm the package knows your models before wiring it into an agent.
config/ai-headroom.php for default_model, method, middleware defaults, currency, models, and cache/calibration settings.php artisan headroom:models to inspect context windows, max output, prices, cache prices, long-prompt tiers, overrides, and stale-price warnings.EnsureHeadroom to a laravel/ai agent. Start in report mode or with generous limits if you want observation before enforcement.php artisan headroom:inspect "App\Ai\Agents\SupportAgent" "prompt" to see per-part token breakdown, counting method, context usage, and estimated cost before shipping.Typical first guardrail:
// Register on the agent's middleware stack.
new EnsureHeadroom(
maxPercentOfContext: 75,
maxCost: 0.05,
);
For a first rollout, prefer ->report() or conservative limits, watch HeadroomLow/TokensUsed, then tighten once you understand real traffic.
Use middleware as the primary guardrail. EnsureHeadroom counts every generation step before it is sent. With blocking enabled it fires RequestBlocked and throws TokenLimitExceeded; with ->report() it fires HeadroomLow and lets the request through. Catch TokenLimitExceeded when you need a user-facing fallback response, and use the carried TokenCount plus LimitType to explain which limit was hit.
Preflight requests in application code. Use Headroom::for($agent, $prompt, messages: ..., tools: ...) to count the full request a laravel/ai agent would send without calling the provider. Use the returned TokenCount to check fits, percentUsed, remainingTokens, counting method, confidence, and estimated cost. For plain text, Headroom::count($text, model: ...) is the lighter path. This is useful for queue admission, UI “will this fit?” checks, and cost previews.
Gate CI with the inspect command. php artisan headroom:inspect ... --json exits non-zero when the request does not fit, so it can be used as a pipeline check. headroom:models --json is useful for validating registry overrides and spotting stale shipped prices.
Use fake counts in tests. Headroom::fake() lets you fake totals or breakdowns by agent class, model, or * pattern, with assertCounted(), assertNotCounted(), assertCountedTimes(), and assertNothingCounted(). The middleware, limits, and events still execute against the faked figures; unmatched requests are estimated locally without a provider call. This makes it practical to test blocking, report mode, and event listeners without real API traffic.
Feed real usage back into calibration. Middleware usage automatically reports TokensUsed; for manual preflight flows, pair Headroom::for() with Headroom::recordUsage($count, $response). Calibration learns a per-model correction from estimate-versus-actual pairs and reports calibrated estimates as CountMethod::Calibrated with measured error as confidence. Use php artisan headroom:calibration to see samples, correction factor, measured error, margin, and whether the model is active or still learning.
Build observability on events, not stored prompts. TokensCounted, TokensUsed, HeadroomLow, RequestBlocked, and RequestFailed are public API. They link via TokenCount::requestId() and laravel/ai’s invocation ID, and deliberately do not carry prompt, response, or error text. Listen to them for metrics, tenant chargeback, alerting, and audit trails.
Tag requests with app context. Headroom::resolveContextUsing() can attach scalar context such as tenant_id without making Headroom a storage layer. Failures are logged as warnings and never fail the request, so treat the tag as best-effort.
Extend the registry instead of guessing. Use the models config to override shipped model fields or add your own. Keep fallback_model cautious for unknown models. If you use a provider beyond the shipped Anthropic/OpenAI/Gemini counters, register a counter in the counters map and choose an appropriate method.
auto is an optimization, not a guarantee of native counts. It estimates first and only fetches a native count when the estimate is within escalation_band of a limit. Clear passes and clear failures never call the provider. Use native if you need provider-side counting on every check, or accept the estimate/calibration behavior.CountMethod::Native, calibration status is not improving its accuracy.TokensUsed or recordUsage(), a model stays in “learning.” Check calibration.min_samples, smoothing, max_correction, and safety_margin_floor; use headroom:calibration --reset carefully, especially with --force.->report() only fires HeadroomLow and lets the request proceed. If you rely on it for safety, you need alerting or a separate enforcement path.RequestBlocked/TokenLimitExceeded, which is usually what you want in tests. Unmatched requests are locally estimated, so do not assume every test request hits a provider.Cost includes estimated input cost and worst-case output cost, rounded to 8 decimals. When cache tokens cannot be priced, cost is marked approximate. Configure cache_read_price and cache_write_price for accurate cache-aware estimates.HEADROOM_CURRENCY and HEADROOM_RATE_GBP/exchange_rates if you report in GBP. Stale exchange rates will skew displayed costs.headroom:models warns when shipped prices are more than 60 days old. Add or override entries in models rather than silently accepting old pricing for production chargeback.fallback_model. That is intentionally cautious, but it can produce surprising context limits or costs. Add the model to the registry when it matters.HEADROOM_CACHE, HEADROOM_CACHE_TTL, and HEADROOM_CACHE_STORE; a shared cache store can affect hit rates across environments.resolveContextUsing() is best-effort. Failures log a warning and never fail the request. Do not make critical authorization or billing decisions depend on the context tag always being present.headroom:inspect exits non-zero when the request does not fit. That is useful for CI, but make sure your shell steps handle expected non-zero exits intentionally.How can I help you explore Laravel packages today?