Skip to main content
Laravel, thinking fast.

Bind the manager as a singleton and put a facade in front of it, so calling code reads like the rest of Laravel:

final class AiServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(AiManager::class);
    }
}
$content = AiFacade::getSiteContent(
    model: config('services.ai.model'),
    prompt: $prompt,
);

A caller now cannot see which provider answered, cannot construct an unconfigured client, and cannot accidentally skip the retry policy. There is exactly one road, and it is paved.

What This Bought

Take stock, because it is easy to look at three small files and conclude you have written a wrapper for the sake of it.

You now have one place where the provider's API shape lives โ€” one file to edit when they rename a field. One place where credentials are validated, with an error that says where to look. One place where timeouts and retries are decided, applied to every AI call in the application including the ones you have not written yet. One interface for tests to fake, at a boundary that does not move.

And you have somewhere to put the next lesson. That is the part that compounds. Everything the rest of this book teaches โ€” cost ceilings, structured output validation, cancellation, observability โ€” lands cleanly because there is a boundary to land on. Without it, each of those becomes a change to every call site.

The wrapper is not the point. The place to put decisions is the point.

What This Does Not Solve Yet

Be clear about what is still missing, because at this stage the endpoint is honest but naive.

It has no idea what a call costs, and no ceiling on how many a tenant can make. It trusts that the response has the structure the feature needs, which is not yet true. It runs inside the HTTP request, which will not survive a long turn. And its prompt is still a string that somebody assembled somewhere, which is the subject of the next two chapters.

Every one of those is a chapter, and every one of those is a change inside this boundary rather than a change across your application. That is the return on the three files.

Key Principles

  • Put the provider behind a contract that describes your needs, not theirs. If the interface uses the provider's vocabulary, it is a rename, not an abstraction.
  • The driver is a translation layer, and nothing else. It receives a configured client; it does not build one.
  • Guarantee transport, validate meaning elsewhere. The driver promises decodable JSON. Whether that JSON is right is a question for code that knows the caller's context.
  • Fail where things are configured, not where they are used. A missing credential must produce your error message, not the provider's misleading one.
  • Resolve credentials per tenant from day one. Retrofitting attribution and per-customer caps is far more expensive than starting with them.
  • Retry transient failures only. Connection errors, 5xx and 429. Never 4xx, never your own exceptions.
  • One road in. A singleton behind a facade means no caller can construct an unconfigured, untimed, unretried client by accident.