Pour qui ? Ma méthode Compétences Réalisations Avis Contact

Laravel, pgvector et les agents IA

Plus besoin de Qdrant?

Photo principale: Laravel, pgvector et les agents IA

Laravel, pgvector et les agents IA :
votre prochain réceptionniste n'aura pas de badge

Avec whereVectorSimilarTo, le Laravel AI SDK et pgvector, construire un agent autonome capable de lire votre base, vos CGV et de finaliser un booking tient désormais dans une application Laravel standard. Plus besoin de Python, plus besoin de stack à quinze briques.

Il fut un temps où réserver une chambre d'hôtel relevait d'un geste simple : choisir une date, cliquer sur « réserver », puis oublier l'existence même de cette réservation jusqu'à la veille du départ.

Aujourd'hui, l'expérience ressemble davantage à une partie d'échecs jouée contre un poulpe statistique. L'utilisateur hésite, compare, annule, revient, réserve depuis son canapé puis annule depuis les toilettes d'un TGV.

Et au milieu de ce ballet numérique, une nouvelle créature commence à apparaître sur les plateformes : l'agent autonome. Pas un chatbot scripté qui répond à des FAQ avec la grâce d'une borne automatique de 2003 - un véritable agent capable de comprendre une demande en langage naturel, interroger votre base de données, consulter vos CGV, proposer une réduction adaptée à la période et finaliser le booking. Le tout, dans la même conversation.

Pendant deux ans, ce genre d'architecture était réservé aux équipes qui aimaient Python, LangChain, et les Kubernetes qui consomment plus d'électricité qu'une boulangerie industrielle. En 2026, ce n'est plus le cas. La pile Laravel - un framework PHP qu'on annonce mort tous les six mois depuis 2010 - a tranquillement absorbé tout ce qu'il fallait pour construire ce type d'application sans quitter PHP.

Petite visite guidée, à travers un fil rouge concret : un agent de réservation hôtelière baptisé SleepyFox.

La pile de 2026 - trois briques, zéro Python obligatoire

Avant de plonger dans le code, posons les briques. Elles sont au nombre de trois.

PostgreSQL avec pgvector

L'extension qui transforme votre base relationnelle préférée en moteur de recherche sémantique. Stocker des vecteurs, calculer des distances, indexer en HNSW pour des latences sub-100ms - tout cela vit désormais dans la même base que vos utilisateurs et vos commandes. Le benchmark de Timescale sur 50 millions de vecteurs montre que pgvector tient la comparaison face à des bases vectorielles dédiées, à 99 % de recall et sub-100ms aux centiles p50, p95 et p99.

Le Laravel AI SDK (laravel/ai)

Package officiel first-party introduit avec Laravel 12 et consolidé dans Laravel 13. Il fournit une API unifiée pour 14 providers : OpenAI, Anthropic, Gemini, Groq, Mistral, DeepSeek, xAI, Ollama, Azure OpenAI, Cohere, OpenRouter, Jina, VoyageAI, ElevenLabs. Vous changez de modèle dans config/ai.php, votre code applicatif ne bouge pas. Le SDK gère le tool calling, le structured output, le streaming, la mémoire conversationnelle, les embeddings et la recherche vectorielle dans un seul package cohérent.

whereVectorSimilarTo()

Méthode native du query builder ajoutée à Laravel 13. Vous passez une chaîne, Laravel génère l'embedding via le SDK, exécute la requête pgvector, vous récupère les résultats triés par similarité. Pas de package tiers, pas de pipeline manuel, pas de service séparé. C'est devenu du SQL ordinaire.

🦊

C'est exactement ce dont a besoin un agent autonome.

whereVectorSimilarTo - la recherche sémantique devient du SQL ordinaire

Commençons par la brique qui change probablement le plus de choses au quotidien. Avant Laravel 13, ajouter de la recherche sémantique à une application Eloquent impliquait : un service d'embedding maison, une colonne vector créée manuellement via DB::statement, des opérateurs pgvector écrits à la main, et au moins un package tiers (pgvector/pgvector-php, excellent par ailleurs).

Depuis Laravel 13, voici ce que ça donne :

database/migrations/create_hotel_terms_table.php
Schema::ensureVectorExtensionExists();

Schema::create('hotel_terms', function (Blueprint $table) {
    $table->id();
    $table->string('section');
    $table->text('content');
    // 768 pour nomic-embed-text (Ollama), 1536 pour text-embedding-3-small (OpenAI),
    // 1024 pour mxbai-embed-large ou voyage-3. À aligner avec votre provider.
    $table->vector('embedding', dimensions: 768)->index();
    $table->timestamps();
});

L'appel ->index() crée automatiquement un index HNSW avec distance cosine. C'est documenté noir sur blanc dans la doc officielle. Plus de migration DB::statement à écrire à la main.

Côté modèle :

app/Models/HotelTerm.php
use Laravel\Ai\Concerns\HasEmbeddings;

class HotelTerm extends Model
{
    use HasEmbeddings;

    protected $casts = [
        'embedding' => 'array',
    ];

    protected $embeddable = ['content'];
}

Et la requête :

Eloquent query
$relevantTerms = HotelTerm::query()
    ->whereVectorSimilarTo('embedding', 'politique d\'annulation last-minute')
    ->limit(5)
    ->get();

Vous passez une chaîne. Laravel s'occupe du reste. Le développeur Sadique Ali a publié un guide détaillé qui montre concrètement comment passer d'une recherche LIKE à une recherche sémantique en moins de cinquante lignes. Et l'équipe de Tighten signale un détail important pour le tuning : le seuil par défaut de minSimilarity (0.6) est souvent trop strict pour des requêtes courtes - descendre à 0.3 améliore drastiquement le rappel sur des produits ou termes brefs.

Ce qui était une fonctionnalité « IA » devient, en pratique, un where parmi d'autres dans un query builder Eloquent. C'est exactement ce niveau de banalisation qui rend la suite possible.

Le Laravel AI SDK - un seul code, n'importe quel LLM

Le deuxième pilier, c'est le Laravel AI SDK. Il faut comprendre l'enjeu : il y a deux ans, écrire une application qui parle à GPT-4 vous attachait à l'écosystème OpenAI. Vouloir basculer sur Claude impliquait de réécrire tout le wrapper. Tester un modèle local en développement signifiait monter une infrastructure parallèle.

Le SDK casse ce modèle. La configuration vit dans config/ai.php :

config/ai.php
return [
    'default' => env('AI_DEFAULT_PROVIDER', 'ollama'),

    'providers' => [
        'ollama' => [
            'driver' => 'ollama',
            'base_url' => env('OLLAMA_BASE_URL', 'http://localhost:11434'),
            'default_model' => 'llama3.2',
            'default_embedding_model' => 'nomic-embed-text',
        ],
        'anthropic' => [
            'driver' => 'anthropic',
            'api_key' => env('ANTHROPIC_API_KEY'),
            'default_model' => 'claude-sonnet-4',
        ],
        'openai' => [
            'driver' => 'openai',
            'api_key' => env('OPENAI_API_KEY'),
            'default_model' => 'gpt-4o',
        ],
    ],
];

En développement, vous tournez sur Ollama : zéro coût, vos données restent chez vous, aucun rate limit. En production, vous basculez sur Anthropic ou OpenAI en changeant une variable d'environnement. Le code applicatif ne change pas d'une ligne.

Mieux : le SDK supporte nativement le failover entre providers, comme l'a documenté Champspoint. Si Anthropic est lent ou hit un rate limit, Laravel bascule automatiquement vers OpenAI sans que l'utilisateur final ne s'en rende compte :

Failover multi-provider
$response = (new BookingAgent)->prompt(
    $message, 
    provider: ['anthropic', 'openai', 'ollama']
);

Cette flexibilité change la dynamique opérationnelle d'un projet IA.

Les démos tournent sur du local, les tests automatisés utilisent le système de fakes intégré (documenté par Delaney Industries), et la production peut mixer plusieurs providers selon le contexte.

SleepyFox - anatomie d'un agent autonome

On arrive au fil rouge. SleepyFox est un agent de réservation qui converse naturellement avec un visiteur, sait lire la base de données, peut consulter les CGV, propose des réductions adaptées et finalise un booking. Pas un chatbot. Un véritable agent au sens ReAct du terme - un LLM qui raisonne, appelle des outils, lit les résultats, et recommence jusqu'à avoir une réponse complète.

⚠️

Note pratique sur le choix du modèle. Faire tourner un agent ReAct sur un petit modèle local (Llama 3.2 3B via Ollama) reste laborieux dès qu'il faut enchaîner plusieurs outils - perte du format JSON, hallucinations sur les paramètres, oubli d'étapes. Llama 3.2 suffit pour comprendre les bases en dev, mais passez sur un modèle plus robuste (Llama 3.3 70B en local si vous avez le GPU, ou Claude Sonnet / GPT-4o en API) dès que l'agent doit enchaîner plus de deux outils complexes. Le grand intérêt du SDK Laravel reste là : vous bougez la variable d'environnement, le code applicatif ne change pas.

Le squelette de l'agent

Selon la documentation officielle, un agent est une classe PHP qui implémente Agent et déclare ses outils. Voici SleepyFox dans sa version minimale :

app/Ai/Agents/SleepyFoxAgent.php
namespace App\Ai\Agents;

use Laravel\Ai\Concerns\Promptable;
use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\{Agent, Conversational, HasTools};

class SleepyFoxAgent implements Agent, Conversational, HasTools
{
    use Promptable, RemembersConversations;

    public function instructions(): string
    {
        return <<<PROMPT
        Tu es SleepyFox, l'assistant de réservation d'un hôtel boutique à Lisbonne.
        Tu peux : rechercher des chambres disponibles, consulter les CGV pour répondre 
        aux questions sur les politiques, proposer des réductions selon la période, 
        et finaliser une réservation après confirmation explicite du client.
        Sois concis, courtois, et ne confirme jamais une réservation sans avoir 
        validé les dates, le tarif final, et la politique d'annulation.
        PROMPT;
    }

    public function tools(): iterable
    {
        return [
            new \App\Ai\Tools\SearchAvailableRooms(),
            new \App\Ai\Tools\SuggestDiscount(),
            \Laravel\Ai\Tools\SimilaritySearch::usingModel(
                model: \App\Models\HotelTerm::class, 
                column: 'embedding'
            )->withDescription(
                'Recherche dans les CGV et politiques de l\'hôtel. Utilise cet outil 
                 quand le client pose une question sur l\'annulation, le paiement, 
                 les enfants, les animaux, ou toute autre règle.'
            ),
            new \App\Ai\Tools\CreateBooking(),
        ];
    }
}

Le trait RemembersConversations gère la persistance du dialogue dans deux tables auto-générées par la migration du package : agent_conversations et agent_conversation_messages. Pas d'implémentation manuelle de mémoire à coder, comme le détaille le guide RichDynamix.

Tool 1 - chercher des chambres disponibles dans la base

Un outil Laravel AI SDK est une classe qui implémente Tool. Quatre méthodes : un nom, une description (que le LLM lit pour décider quand appeler l'outil), un schéma JSON des paramètres, et le code qui s'exécute.

app/Ai/Tools/SearchAvailableRooms.php
namespace App\Ai\Tools;

use App\Models\Room;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Illuminate\Contracts\JsonSchema\JsonSchema;

class SearchAvailableRooms implements Tool
{
    public function name(): string
    {
        return 'search_available_rooms';
    }

    public function description(): string
    {
        return 'Recherche les chambres disponibles entre deux dates. Retourne 
                la liste des chambres avec leur catégorie, prix nuit, et capacité.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'check_in'  => $schema->string()->format('date')->description('YYYY-MM-DD'),
            'check_out' => $schema->string()->format('date')->description('YYYY-MM-DD'),
            'guests'    => $schema->integer()->minimum(1)->maximum(6),
        ];
    }

    public function handle(Request $request): string
    {
        $rooms = Room::query()
            ->where('capacity', '>=', $request['guests'])
            ->whereDoesntHave('bookings', function ($q) use ($request) {
                $q->where('check_in', '<', $request['check_out'])
                  ->where('check_out', '>', $request['check_in']);
            })
            ->get(['id', 'category', 'price_per_night', 'capacity']);

        return $rooms->toJson();
    }
}

Le LLM voit la description, comprend qu'il a besoin de dates et d'un nombre de personnes, extrait ces informations de la conversation, appelle l'outil, et reçoit la liste sous forme structurée. Toute la magie tient dans la qualité de la description - Hafiz le rappelle bien : si l'agent appelle le mauvais outil ou refuse de l'appeler, le problème est presque toujours dans la description, pas dans le LLM.

Tool 2 - proposer une réduction selon la période

app/Ai/Tools/SuggestDiscount.php (méthode handle)
public function handle(Request $request): string
{
    $checkIn = Carbon::parse($request['check_in']);
    $daysUntilArrival = now()->diffInDays($checkIn);
    $month = $checkIn->month;

    $discount = match (true) {
        $daysUntilArrival <= 3 => ['rate' => 0.15, 'reason' => 'Last-minute'],
        in_array($month, [1, 2, 11]) => ['rate' => 0.20, 'reason' => 'Basse saison'],
        $daysUntilArrival >= 60 => ['rate' => 0.10, 'reason' => 'Early booking'],
        default => ['rate' => 0, 'reason' => 'Aucune promotion applicable'],
    };

    return json_encode($discount);
}

Logique métier triviale, mais c'est exactement le genre de règles qu'un agent peut combiner avec la recherche de chambres pour faire une proposition cohérente du type : « j'ai trouvé une junior suite à 180€/nuit pour vos dates ; comme vous réservez à 48h de l'arrivée, je peux vous appliquer une réduction de 15% ».

Tool 3 - le RAG sur les CGV

C'est là que la vectorisation prend tout son sens. Les CGV d'un hôtel, c'est un PDF de quinze pages que personne ne lit, contenant tout : politique d'annulation, frais de nettoyage, animaux acceptés, conditions du petit-déjeuner. Plutôt que de la donner brute à l'agent (qui exploserait votre fenêtre de contexte à chaque échange), on découpe le document en chunks, on les embedde, et on stocke tout dans hotel_terms.

Commande Artisan exécutée une fois pour seeder les CGV
collect($chunks)->each(function ($chunk) {
    HotelTerm::create([
        'section'   => $chunk['section'],
        'content'   => $chunk['content'],
        'embedding' => Str::of($chunk['content'])->toEmbeddings(),
    ]);
});

Str::of($text)->toEmbeddings() retourne un float[] en utilisant le provider d'embedding configuré par défaut - Ollama en dev, OpenAI ou Voyage en prod, comme le détaille le guide RichDynamix.

Ensuite, le tool SimilaritySearch::usingModel() est déjà fourni par le SDK. Quand le client demande « vos chambres acceptent les chiens ? », l'agent appelle automatiquement cet outil avec la requête appropriée, reçoit les 3-5 chunks de CGV les plus pertinents, et répond en se fondant dessus plutôt qu'en inventant. C'est du RAG textbook, intégré en deux lignes.

Tool 4 - finaliser le booking

app/Ai/Tools/CreateBooking.php (méthode handle)
public function handle(Request $request): string
{
    $booking = DB::transaction(function () use ($request) {
        return Booking::create([
            'room_id'   => $request['room_id'],
            'guest_email' => $request['guest_email'],
            'check_in'  => $request['check_in'],
            'check_out' => $request['check_out'],
            'total'     => $request['total_amount'],
            'status'    => 'pending_payment',
        ]);
    });

    PredictCancellationRisk::dispatch($booking);

    return json_encode([
        'booking_id' => $booking->id,
        'payment_link' => route('booking.pay', $booking),
    ]);
}

Notez le PredictCancellationRisk::dispatch($booking) à la dernière ligne. C'est la jonction avec la dernière brique de notre stack - celle qui alimente le dashboard.

Le dashboard - XGBoost en arrière-plan

Pendant que l'agent fait son travail conversationnel, une autre logique tourne en silence. Chaque nouvelle réservation déclenche un job en queue qui appelle un modèle de prédiction d'annulation. Et là, contre-intuitivement, on ne va pas utiliser un LLM. On va utiliser XGBoost.

Pourquoi ? Parce que la littérature scientifique sur les données tabulaires est sans appel.

Tabular Data: Deep Learning is Not All You Need (Shwartz-Ziv & Armon, 2021) compare rigoureusement XGBoost à plusieurs architectures deep learning sur 11 datasets et conclut : XGBoost gagne sur la majorité, demande beaucoup moins de tuning, et coûte une fraction du temps de calcul. McElfresh et al. (2023) confirment sur 176 datasets que les neural networks sont rarement compétitifs avec les gradient boosted trees sur des features hétérogènes.

Spécifiquement pour la prédiction d'annulation hôtelière, Antonio et al. (2019) ont publié un système de production basé sur XGBoost dès 2019. Plus récemment, une étude publiée chez Atlantis Press obtient un ROC-AUC de 0.9808, et identifie trois features clés : le lead time, le canal de réservation, et le nombre de special requests.

L'intégration - Laravel ↔ FastAPI

XGBoost est une bibliothèque Python. Laravel est en PHP. Plutôt que de tordre les bras de PHP, on adopte le pattern recommandé par LabroDev : un microservice FastAPI exposant l'inférence.

predictor.py
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
import pandas as pd

app = FastAPI()
model = joblib.load('cancellation_model.pkl')

class BookingFeatures(BaseModel):
    lead_time: int
    deposit_type: str
    total_special_requests: int
    market_segment: str
    previous_cancellations: int
    adr: float

@app.post('/predict')
def predict(features: BookingFeatures):
    # On passe un DataFrame nommé : XGBoost retrouve les colonnes par leur nom
    # tel qu'il a été entraîné, peu importe l'ordre du payload Pydantic.
    df = pd.DataFrame([features.dict()])
    proba = model.predict_proba(df)[0][1]
    return {'cancellation_probability': float(proba)}

Côté Laravel, le job dispatché par notre tool CreateBooking appelle l'endpoint :

app/Jobs/PredictCancellationRisk.php
class PredictCancellationRisk implements ShouldQueue
{
    public function handle(): void
    {
        $features = $this->booking->toMlFeatures();

        $proba = Http::post(config('services.ml.url').'/predict', $features)
            ->json('cancellation_probability');

        $this->booking->update(['cancellation_risk' => $proba]);

        if ($proba > 0.7) {
            event(new HighRiskBookingDetected($this->booking));
        }
    }
}

Le dashboard affiche pour chaque réservation un indicateur de risque. Au-delà de 70 %, l'équipe revenue management reçoit une notification - l'occasion de demander un acompte plus élevé, d'activer un surbooking intelligent, ou de déclencher une campagne de rétention.

🔓

XGBoost est open-source (Apache 2.0), FastAPI est sous licence MIT, scikit-learn est BSD. Tout tourne sur votre propre infrastructure, sans facture mensuelle qui suit votre trafic.

Pourquoi cette stack tient debout

Récapitulons. SleepyFox, l'agent de réservation autonome, c'est :

Laravel 13

Une application standard, avec PHP 8.3+.

PostgreSQL

Avec pgvector et un index HNSW.

laravel/ai

Le package pour l'orchestration agentique.

Ollama

En local pour le dev, basculable vers Claude ou GPT en prod sans toucher au code.

FastAPI + XGBoost

Microservice pour la prédiction d'annulation.

Aucun service tiers obligatoire. Aucune dépendance payante structurelle. Aucune base spécialisée à maintenir en plus. La logique conversationnelle, le RAG, la mémoire, le routing entre providers et la recherche vectorielle vivent dans une application Laravel. Le seul morceau Python du projet est isolé derrière un endpoint HTTP, parfaitement remplaçable, et concerne uniquement le calcul ML offline.

C'est cette consolidation qui change tout. Pendant deux ans, beaucoup d'équipes ont construit des architectures comme des enfants dans un magasin LEGO après trois cafés : une base SQL, une vector DB, un Redis, un Elastic, un orchestrateur, un moteur de streaming. Et probablement une blockchain « au cas où ». Puis la réalité opérationnelle est arrivée : coûts, backups, migrations, incidents, latences réseau, compétences nécessaires pour maintenir tout ça.

⚖️

Le retour de balancier est en cours. Comme le résume Victoria Mycolaivna après une semaine à benchmarker tout ce qui existe : commencer par pgvector, ne penser à migrer que quand on a des preuves mesurées que la base est devenue le goulot - pas un benchmark de vendeur, pas une intuition.

Le vrai message pour les CTO

Les agents IA autonomes ne sont plus une démo de conférence. C'est devenu un pattern documenté, outillé, et accessible à n'importe quelle équipe Laravel sans changer d'écosystème. Le coût d'entrée pour construire un SleepyFox a chuté d'un ordre de grandeur en dix-huit mois.

Et le plus intéressant n'est pas la performance brute. C'est que tout cela tient dans une stack que vos développeurs connaissent déjà. Pas de Python à apprendre. Pas de LangChain à maintenir. Pas de vector database à backuper séparément. Pas de vendor lock-in sur un seul LLM.

Laravel + PostgreSQL + pgvector + Laravel AI SDK + XGBoost n'a rien d'une architecture sexy pour keynote futuriste. Mais c'est précisément ce qui la rend redoutablement crédible : chaque brique est mature, documentée, et maintenable par une équipe de taille raisonnable. Quand l'IA entre dans sa phase de normalisation, ce sont ces stacks-là qui gagnent - pas celles qui font joli sur une slide.

Au bout du compte, le client veut juste une chambre propre avec vue sur mer.
Et le CTO veut juste dormir la nuit.

SleepyFox s'en charge.

Sources et lectures complémentaires

Partager cet article