KI-Softwareentwicklung

Laravel 13: Service Container mit dem AI SDK verbinden

Der Service Container und das Laravel AI SDK gehören zusammen: Ein KI-Agent ist ein normaler Container-Service. Sie binden ihn, tauschen den Provider per Attribut und ersetzen ihn im Test durch eine Attrappe. Beispiele laufen gegen Azure OpenAI.

Fortgeschritten70 Min.Autor: Martin TomczakAktualisiert: 09.07.2026
Isometrisches Diagramm: der Service Container verdrahtet einen Controller mit einem KI-Agenten und dessen Provider

Was Sie in diesem Tutorial bauen

Sie verbinden in diesem Tutorial den Laravel Service Container mit dem Laravel AI SDK und bauen eine App, in der ein KI-Agent sauber aufgelöst, konfiguriert und getestet wird. Der Service Container ist Laravels Motor für Dependency Injection: Er erzeugt Objekte und liefert deren Abhängigkeiten automatisch. Das AI SDK (Composer-Paket laravel/ai) bringt KI-Agenten nach genau diesem Muster in die App. Beide gehören zusammen, weil ein Agent im SDK ein ganz normaler Container-Service ist. Sie binden ihn, tauschen seinen Provider und ersetzen ihn im Test durch eine Attrappe, ohne eine Zeile der Fachlogik anzufassen.

Am Ende haben Sie zwei Agenten, die über den Container laufen: einen Support-Assistenten gegen Azure OpenAI und einen Triage-Agenten, der eingehende Nachrichten klassifiziert. Stand dieses Tutorials: Laravel 13.19, laravel/ai 0.9.0, PHP 8.5. Alle Code-Beispiele sind real ausgeführt, die Ausgaben stammen aus echten Aufrufen gegen eine Azure-OpenAI-Ressource mit dem Deployment gpt-5.5-1.

Ein Hinweis vorweg: laravel/ai steht auf Version 0.9.0, also vor dem ersten stabilen Release. Die Grundmuster sitzen, einzelne Methodensignaturen können sich bis 1.0 noch verschieben. Pinnen Sie die Version in Ihrer composer.json und prüfen Sie nach einem Update Ihre Aufrufe gegen die installierte Fassung.

Schritt 1: Laravel 13 und das AI SDK installieren

Sie brauchen PHP 8.3 oder neuer. Laravel 13 setzt PHP 8.3 als Minimum voraus, ich habe hier PHP 8.5.3 verwendet. Frisches Projekt anlegen und das SDK dazuholen:

composer create-project "laravel/laravel:^13.0" support-desk
cd support-desk
composer require laravel/ai

Der zweite Befehl zog laravel/ai in Version 0.9.0. Als Nächstes veröffentlichen Sie die Konfiguration, damit Sie die Provider anpassen können:

php artisan vendor:publish --tag=ai-config

Das legt config/ai.php an. Interessant ist der providers-Block: laravel/ai bringt einen eigenen azure-Treiber mit, Sie müssen also für Azure OpenAI nichts selbst basteln. Der Treiber spricht die v1-kompatible Azure-API an. Wichtig dabei ist die Form der URL: Sie hinterlegen die nackte Ressourcen-Basis, den Pfad /openai/v1 hängt der Treiber selbst an. Tragen Sie die Zugangsdaten in die .env ein:

AI_DEFAULT_PROVIDER=azure

AZURE_OPENAI_API_KEY=ihr-schluessel
AZURE_OPENAI_URL=https://ihre-ressource.services.ai.azure.com
AZURE_OPENAI_API_VERSION=2026-04-24
AZURE_OPENAI_DEPLOYMENT=gpt-5.5-1

Ein Detail, das mich zuerst gekostet hat: Die veröffentlichte config/ai.php setzt 'default' => 'openai' als festen Wert, nicht über eine Umgebungsvariable. Damit AI_DEFAULT_PROVIDER greift, ändern Sie eine Zeile:

// config/ai.php
'default' => env('AI_DEFAULT_PROVIDER', 'openai'),

Schritt 2: Was macht der Service Container genau?

Der Service Container erzeugt Objekte und löst ihre Abhängigkeiten automatisch auf, statt dass Sie überall new schreiben. Zwei Bindungsarten reichen für den Anfang. Ein bind liefert bei jeder Auflösung ein frisches Objekt, ein singleton immer dasselbe. Das lässt sich in einem Satz beweisen:

app()->singleton('demo.counter', fn () => new stdClass);
app()->bind('demo.fresh', fn () => new stdClass);

app('demo.counter') === app('demo.counter'); // true, gleiche Instanz
app('demo.fresh')   === app('demo.fresh');   // false, neue Instanz

Neben singleton gibt es scoped. Es verhält sich unter klassischem PHP-FPM wie ein Singleton pro Anfrage, wird aber zwischen zwei Anfragen zurückgesetzt. Unter Laravel Octane ist dieser Unterschied entscheidend, weil derselbe Prozess mehrere Anfragen bedient. Für zustandsbehaftete KI-Dienste kommen wir in Schritt 5 darauf zurück.

Der eigentliche Gewinn ist die automatische Constructor-Injection. Wenn eine Klasse im Konstruktor eine andere Klasse per Type-Hint verlangt, baut der Container sie und reicht sie hinein. Genau dieses Muster nutzt gleich der KI-Agent.

Schritt 3: Den ersten KI-Agenten über den Container auflösen

Ein Agent im AI SDK ist eine Klasse, die das Interface Laravel\Ai\Contracts\Agent erfüllt und das Trait Promptable einbindet. Die Methode instructions() liefert den System-Prompt. Über die Attribute #[Provider] und #[Model] legen Sie am Klassenkopf fest, welcher Provider und welches Modell diesen Agenten bedienen:

<?php

namespace App\Agents;

use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

#[Provider(Lab::Azure)]
#[Model('gpt-5.5-1')]
class SupportAgent implements Agent
{
    use Promptable;

    public function instructions(): string
    {
        return 'Du bist der Support-Assistent eines DACH-B2B-Softwarehauses. '
            .'Antworte praezise auf Deutsch, hoechstens zwei Saetze.';
    }
}

#[Model('gpt-5.5-1')] referenziert bei Azure den Namen des Deployments, nicht eine generische Modellkennung wie gpt-4o. Nun lösen Sie den Agenten über den Container auf und stellen eine Frage. Der Aufruf app(SupportAgent::class) baut die Klasse, prompt() schickt die Anfrage an Azure:

$antwort = app(App\Agents\SupportAgent::class)
    ->prompt('Was ist ein Service Container in einem Satz?');

echo $antwort->text;

Das lieferte bei mir eine echte Antwort aus Azure OpenAI:

Ein Service Container ist ein zentrales Objekt in einer Anwendung, das
Services und ihre Abhaengigkeiten verwaltet, erstellt und bei Bedarf
bereitstellt.

Sie haben den Agenten nirgends von Hand instanziiert. Der Container hat ihn gebaut, die Attribute haben ihn zu Azure geroutet. Damit ist der Agent ein Service wie jeder andere.

Datenfluss vom Controller über den Service Container zum KI-Agenten und weiter zum Azure-OpenAI-Provider bis zur Antwort
Datenfluss vom Controller über den Service Container zum KI-Agenten und weiter zum Azure-OpenAI-Provider bis zur Antwort

Schritt 4: Wie geben Sie zwei Agenten zwei verschiedene Provider?

Über Contextual Binding. Sie definieren ein Interface für die Fähigkeit, hinterlegen eine Standard-Implementierung und weisen einzelnen Verbrauchern gezielt eine andere zu. Das ist der Punkt, an dem Container und AI SDK verschmelzen: Der Container entscheidet, welcher Agent mit welchem Modell in welchen Teil der App fließt.

Zuerst ein schmales Interface, das beide Agenten erfüllen:

namespace App\Contracts;

interface SupportAssistant
{
    public function answer(string $question): string;
}

Der SupportAgent bekommt die Methode answer() und implementiert das Interface zusätzlich zum Agent-Vertrag. Daneben stellen Sie einen TriageAgent, der Nachrichten in ein Wort einsortiert:

#[Provider(Lab::Azure)]
#[Model('gpt-5.5-1')]
class TriageAgent implements Agent, SupportAssistant
{
    use Promptable;

    public function instructions(): string
    {
        return 'Klassifiziere die Nachricht in genau ein Wort: '
            .'Rechnung, Technik oder Sonstiges. Nur das Wort.';
    }

    public function answer(string $question): string
    {
        return trim($this->prompt($question)->text);
    }
}

Jetzt die Bindung im AppServiceProvider. Standardmäßig löst das Interface zum SupportAgent auf, der TriageDesk bekommt per Contextual Binding den TriageAgent:

public function register(): void
{
    $this->app->bind(SupportAssistant::class, SupportAgent::class);

    $this->app->when(TriageDesk::class)
        ->needs(SupportAssistant::class)
        ->give(TriageAgent::class);
}

Zwei Verbraucher, Inbox und TriageDesk, verlangen im Konstruktor jeweils einen SupportAssistant. Die Auflösung zeigt, dass der Container jedem den richtigen Agenten gibt:

Inbox->assistant:  App\Agents\SupportAgent
Triage->assistant: App\Agents\TriageAgent

Und der Triage-Agent arbeitet auch wirklich. Zwei echte Aufrufe gegen Azure:

Triage("Meine Rechnung stimmt nicht")      -> Rechnung
Triage("Der Login-Button reagiert nicht")  -> Technik

Beide Agenten laufen hier auf demselben Deployment gpt-5.5-1. Wollen Sie die Triage auf ein günstigeres Modell legen, ändern Sie eine einzige Attribut-Zeile am TriageAgent und hinterlegen ein zweites Azure-Deployment. Die Container-Verdrahtung bleibt gleich. Genau das ist der Wert der Trennung: Das Routing lebt in einer Provider-Klasse, nicht verstreut in Ihren Controllern.

Contextual Binding: die Inbox erhält den SupportAgent, der TriageDesk erhält den TriageAgent aus demselben Container
Contextual Binding: die Inbox erhält den SupportAgent, der TriageDesk erhält den TriageAgent aus demselben Container

Schritt 5: Warum gehört die KI hinter den Container?

Wegen der Testbarkeit. Ein hart verdrahteter Facade-Aufruf mitten in der Fachlogik lässt sich im Test nur schwer ersetzen. Ein Container-Service dagegen ist ein Handgriff. Das AI SDK bringt dafür sogar einen eigenen Fake mit. SupportAgent::fake() hinterlegt eine feste Antwort, assertPrompted() prüft, dass der Agent den erwarteten Prompt bekommen hat, ganz ohne Netzwerkaufruf:

public function test_agent_can_be_faked_via_the_sdk(): void
{
    SupportAgent::fake(['Der Container verwaltet Abhaengigkeiten.']);

    $answer = app(SupportAgent::class)->answer('Was ist Dependency Injection?');

    $this->assertSame('Der Container verwaltet Abhaengigkeiten.', $answer);
    SupportAgent::assertPrompted('Was ist Dependency Injection?');
}

Die zweite Variante braucht das SDK nicht einmal. Sie tauschen die Interface-Bindung im Container gegen eine Attrappe und prüfen, dass Ihr Code die Antwort korrekt verarbeitet:

public function test_assistant_can_be_swapped_in_the_container(): void
{
    $this->app->bind(SupportAssistant::class, fn () => new class implements SupportAssistant {
        public function answer(string $question): string
        {
            return 'GEFAKED';
        }
    });

    $inbox = app(Inbox::class);

    $this->assertSame('GEFAKED', $inbox->assistant->answer('egal'));
}

Beide Tests laufen bei mir grün, in zusammen 60 Millisekunden, weil kein echter Azure-Aufruf passiert. Das ist der praktische Grund, jeden KI-Zugriff hinter ein Interface im Container zu legen: Ihre Testsuite bleibt schnell, deterministisch und unabhängig vom Anbieter. Der Kostenpunkt zählt mit, denn ein Testlauf, der bei jedem Durchlauf echte Tokens verbrennt, wird niemand oft ausführen.

Beim Binding-Typ lohnt eine bewusste Wahl. Ein Agent ohne veränderlichen Zustand kann ein singleton sein. Hält er dagegen Gesprächsverlauf im Objekt, nehmen Sie scoped, damit unter Laravel Octane der Kontext einer Anfrage nicht in die nächste leckt.

Der Service Container tauscht den echten KI-Agenten im Test gegen eine Attrappe aus
Der Service Container tauscht den echten KI-Agenten im Test gegen eine Attrappe aus

Schritt 6: Drei Stolperfallen aus der Praxis

Beim Aufbau bin ich in drei Fallen getappt, die Ihnen Zeit sparen.

Erstens die Azure-URL. Der azure-Treiber baut die Basis als {URL}/openai/v1 zusammen. In AZURE_OPENAI_URL gehört deshalb nur die Ressourcen-Basis wie https://ihre-ressource.services.ai.azure.com, nicht der vollständige Deployment-Pfad. Kleben Sie die klassische /openai/deployments/...-URL hinein, antwortet die API mit einem Fehler.

Zweitens der Modellname. #[Model('gpt-5.5-1')] muss exakt den Namen Ihres Azure-Deployments treffen, nicht eine generische Kennung. Bei OpenAI direkt wäre es der Modellname, bei Azure ist es der Deployment-Name. Das verwechselt man leicht, wenn man aus einem OpenAI-Beispiel kopiert.

Drittens die Provider-Auswahl. Das #[Provider]-Attribut schlägt den Standard aus config/ai.php. Ich habe das geprüft, indem ich den Default bewusst auf openai gesetzt habe, für das gar kein Schlüssel hinterlegt war. Der SupportAgent mit #[Provider(Lab::Azure)] lief trotzdem gegen Azure durch. Das Attribut ist also die verlässliche, explizite Route pro Agent. Verlassen Sie sich nicht allein auf den globalen Default.

Ein Wort zur Transparenzpflicht: Sobald ein solcher Agent für Endnutzer sichtbar antwortet, greift Artikel 50 des EU AI Act. Ab dem 2. August 2026 müssen Sie kenntlich machen, dass die Antwort von einer KI stammt. Ein Satz im Chatfenster genügt, aber er muss da sein.

Was Sie jetzt haben

Sie haben eine Laravel-13-App, in der KI-Agenten echte Container-Services sind. Ein SupportAgent gegen Azure OpenAI, ein TriageAgent für die Klassifikation, beide über Interface und Contextual Binding sauber getrennt, beide im Test in Millisekunden ersetzbar. Der rote Faden war durchgehend derselbe: Was der Container für gewöhnliche Objekte tut, tut er genauso für KI. Er baut sie, verdrahtet ihre Provider und lässt sie sich austauschen.

Von hier aus bieten sich zwei Richtungen an. Sie können den Agenten Tools geben, damit sie Daten aus Ihrer App abrufen, oder Sie legen strukturierte Ausgaben per Schema fest. Wenn Sie KI in ein bestehendes Laravel-Produkt bringen wollen, ist das der Bauplan, den ich in Kundenprojekten nutze. Bei der Umsetzung in Ihrer Codebasis unterstütze ich Sie über die KI-Softwareentwicklung. Wie sich KI-Funktionen in gewachsene Systeme einfügen, vertieft der Beitrag zur KI-Integration in bestehende Systeme. Und wenn Ihr Stack ohnehin auf Laravel läuft, lohnt ein Blick auf das Angular-und-Laravel-Themengebiet. Wer lieber mit TypeScript und Azure OpenAI startet, findet im Tutorial zum RAG-System mit Azure OpenAI und Qdrant den passenden Einstieg.

FAQ

Häufige Fragen

Ist das Laravel AI SDK dasselbe wie Prism?

Nein. Das Laravel AI SDK ist das offizielle First-Party-Paket laravel/ai von Laravel. Prism (prism-php/prism) ist ein beliebtes Drittanbieter-Paket mit anderer API, das dem offiziellen SDK vorausging. Auch das Vercel AI SDK ist etwas anderes, es ist für JavaScript, nicht für PHP.

Kann ich statt Azure OpenAI einen anderen Anbieter nutzen?

Ja. laravel/ai bringt First-Class-Treiber unter anderem für OpenAI, Anthropic, Gemini und Bedrock mit, wählbar über das Lab-Enum. Sie ändern nur das #[Provider]-Attribut am Agenten und hinterlegen den passenden Schlüssel in config/ai.php. Der restliche Code bleibt gleich.

Warum sollte ich einen Agenten hinter ein Interface legen?

Damit Sie ihn im Container austauschen können, ohne Ihre Fachlogik anzufassen. Im Test binden Sie eine Attrappe, in Produktion die echte Implementierung. So bleiben Ihre Tests schnell und kosten keine Tokens, und Sie können den Anbieter später wechseln, ohne die aufrufenden Klassen zu ändern.

Ist laravel/ai schon produktionsreif?

Zum Stand dieses Tutorials steht das Paket auf Version 0.9.0, also vor dem ersten stabilen Release. Die Architektur ist tragfähig, aber einzelne Methodensignaturen können sich bis 1.0 noch ändern. Pinnen Sie die Version fest und prüfen Sie nach jedem Update Ihre Aufrufe gegen die installierte Fassung.

Nächster Schritt

KI in Ihre Laravel-App bringen?

Sie wollen KI-Funktionen in Ihre Laravel-Anwendung bringen, ohne den Code unwartbar zu machen? Ich unterstütze bei Architektur und Umsetzung.

Erstgespräch anfragen