Skip to content

feat: add BudgetGuard middleware for cost control - #36

Open
oziriemeka wants to merge 1 commit into
promptphp:mainfrom
oziriemeka:feat/budget-guard
Open

oziriemeka wants to merge 1 commit into
promptphp:mainfrom
oziriemeka:feat/budget-guard

Conversation

@oziriemeka

@oziriemeka oziriemeka commented Oct 13, 2026 •

Copy link
Copy Markdown

Motivation and Context

Production AI applications need reliable safeguards against unexpected costs caused by excessive token consumption, model changes, or misuse.

Previously, the Intercept middleware stack did not provide a mechanism for enforcing spending limits at the request, session, or user level.

This PR introduces promptphp/intercept-budget-guard, a middleware package that estimates generation costs before invoking the AI provider and enforces configurable spending budgets.

Key Features

  • Pre-request cost estimation: Estimates the cost of a generation step before making an AI provider call.

  • Configurable spending limits: Supports per-request, per-session, and per-user-per-day budgets.

  • Flexible enforcement actions:

    • block — Rejects requests that exceed the configured budget.

    • log — Logs budget violations while allowing requests to proceed.

    • truncate — Reduces the latest user message to fit within the available budget, falling back to block when necessary.

  • Unknown model handling: Configurable behaviour when pricing information is unavailable.

  • Custom callbacks: Allows consumers to override the default enforcement action.

  • Persistent budget tracking: Tracks accumulated spending across requests using Laravel's cache.

Implementation Decisions

The original proposal in #12 referred to the middleware as CostGuard and suggested using RateLimitExceededException.

The implementation uses BudgetGuard and BudgetGuardException instead, aligning with the package name reserved in ROADMAP.md and the existing <Package>Exception naming convention across the middleware collection.

Additionally, rather than using Laravel's RateLimiter, spending is tracked directly through the cache.

Laravel's RateLimiter is designed around counting attempts within a time window, whereas budget enforcement requires tracking monetary values.

To support this, costs are represented as **integer micro-dollars**, avoiding floating-point inaccuracies and enabling atomic increments on supported cache drivers. Budget windows follow the same timer-key approach used internally by Laravel's `RateLimiter`.

Closes #12.

Dependencies

No new root-level Composer dependencies are introduced.

The new package depends on:

  • promptphp/intercept-support — existing sibling package within the monorepo.

  • laravel/ai: ^1.0 — consistent with the other middleware packages.

Install dependencies locally using:

composer install

Testing

Automated Tests

The implementation adds 22 tests, bringing the full suite to 264 passing tests.

Command | Purpose | Result -- | -- | -- composer test | Full test suite | 264 passing vendor/bin/pest src/BudgetGuard/tests | BudgetGuard tests | 22 passing composer test:lint | Laravel Pint | Passing composer test:types | PHPStan Level 5 | Passing composer packages:validate | Monorepo package validation | Passing

Test Coverage

The new tests cover:

  • Allowing requests that remain within their configured budgets.

  • Blocking requests whose projected costs exceed the per-request budget, including costs accumulated during earlier generation steps.

  • Logging budget violations without interrupting execution.

  • Truncating the latest user message to fit the remaining budget.

  • Falling back to block when truncation cannot produce a sufficiently inexpensive request.

  • Handling models with unavailable pricing according to unknownModelAction.

  • Overriding the default enforcement action through a custom callback.

  • Tracking per-session and per-user-per-day spending across separate agent runs.

  • Skipping session or user budget enforcement when the corresponding key resolver does not resolve a value.

  • Rejecting invalid enforcement actions and non-positive numeric configuration values during construction.

Manual Verification

The middleware can also be verified end-to-end using a Laravel application with laravel/ai.

  1. Create a temporary Laravel application and install laravel/ai.

  2. Configure a Composer path repository pointing to this monorepo's src/* packages.

  3. Install the new package:

    composer require promptphp/intercept-budget-guard:@dev
  4. Generate an agent using php artisan make:agent, add the HasMiddleware trait, and configure:

    public function middleware(): array
    {
        return [
            new BudgetGuard(
                maxCostPerRequest: 0.0000001,
                action: 'block',
            ),
        ];
    }
  5. Invoke the agent:

    $agent->prompt('Say hello.');

    Expected: A BudgetGuardException is thrown before any provider API call. No provider API key is required to verify this behaviour.

  6. Change the action to log, retaining the same budget.

    Expected: The violation is recorded in storage/logs/laravel.log, and execution proceeds to the provider call.

  7. Change the action to truncate and provide a sufficiently long prompt with a budget that permits a shortened request.

    Expected: The middleware shortens the prompt and allows execution to continue. If the available budget remains insufficient after truncation, it falls back to block.

Summary

This PR introduces configurable, middleware-level cost controls for AI generation requests, providing applications with a way to manage spending before provider calls are made.

The implementation follows the existing Intercept package conventions, introduces no new root-level dependencies, and includes automated coverage for budget enforcement, accumulated spending, truncation, and configuration validation.

@oziriemeka
oziriemeka requested a review from veeqtoh as a code owner October 13, 2026 00:07
@veeqtoh

veeqtoh commented Oct 14, 2026

Copy link
Copy Markdown
Member

Hi @oziriemeka, can't thank you enough for this contribution, I'm taking some time to go through the PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cost Guard Middleware

2 participants