9min.

Symfony AI : simplifier l’analyse de similarités de textes et l’interaction avec vos LLMs favoris

Dernier-né dans la famille Symfony, nous vous présentons Symfony AI 🎉

Symfony AI est un ensemble de composants permettant d’intégrer l’intelligence artificielle dans des applications PHP.

Il se compose de plusieurs parties :

  • Les Composants

    • Platform : interface unifiée vers diverses plateformes d’IA telles que OpenAI, Anthropic, Azure, Gemini, etc. ;
    • Agent : framework pour créer des agents IA capables d’interagir avec les utilisateurs et d’exécuter des tâches ;
    • Store : abstraction de stockage de données avec indexation et recherche pour les applications IA ;
    • MCP SDK : SDK pour le Model Context Protocol permettant la communication entre agents IA et outils.
  • Les Bundles

Décrire tous les composants et bundles serait trop long pour cet article. Nous allons donc nous concentrer sur deux d’entre eux : Platform et Store.

Dans cet article, nous allons créer un petit projet qui indexe des documents et retrouve ceux qui sont similaires à un document donné.

Pour cela, nous devons d’abord vectoriser les documents.

Section intitulée la-vectorisationLa vectorisation

En intelligence artificielle, vectoriser un texte (ou une image, un son, etc.) signifie le transformer en une représentation numérique sous forme d’un tableau de nombres : un vecteur.

  • Chaque vecteur est une liste de valeurs flottantes (ex. [0.12, -0.45, 0.87, ...]) ;
  • Ces nombres sont calculés par un modèle d’embedding, réseau de neurones spécialisé ;
  • L’idée : deux contenus similaires doivent avoir des vecteurs proches dans l’espace numérique.

Pourquoi faire ça ? La vectorisation permet de mesurer la similarité entre deux contenus. Par exemple :

  • « chien » et « chiot » → vecteurs proches ;
  • « chien » et « chat » → vecteurs assez proches ;
  • « chien » et « banane » → vecteurs éloignés.

vectorisation de différent token Vue d’artiste, non réaliste, représentant une vectorisation en 2 dimensions de différent mots

Cela rend possible :

  • La recherche sémantique (retrouver un texte même si les mots exacts ne sont pas utilisés) ;
  • Le clustering (regrouper automatiquement des contenus proches) ;
  • Le RAG (Retrieval-Augmented Generation) : retrouver des informations pertinentes pour un LLM.

Section intitulée comment-mesure-t-on-la-proximiteComment mesure-t-on la proximité ?

On compare les vecteurs avec une mesure :

  • Cosine similarity → compare l’angle entre deux vecteurs (le plus courant) ;
  • Euclidean distance → mesure la distance directe entre deux points.

D’autres mesures existent, mais celles-ci sont les plus utilisées.

Nous appelons le résultat de cette mesure un score. Cette valeur, en fonction de la fonction de calcul peut être :

  • une distance (0 est identique, 1 est complètement différent) ;
  • une similarité (0 est complètement différent, 1 est identique).

Chaque mesure a ses avantages et inconvénients. Par exemple, la cosine similarity est robuste aux variations d’échelle, tandis que la distance euclidienne est sensible à la magnitude des vecteurs.

Une variation d’échelle, c’est quand un vecteur est simplement une version “agrandie” ou “réduite” d’un autre, par exemple [1, 2, 3] vs [2, 4, 6]. La cosine similarity les considère identiques car l’orientation est la même, tandis que la distance euclidienne les voit différents, car la longueur change.

Section intitulée vectoriser-un-texte-avec-code-symfony-ai-platform-codeVectoriser un texte avec symfony/ai-platform

Nous allons utiliser le composant Platform de Symfony AI :

composer require symfony/ai-platform@dev

Ce composant fournit une interface unifiée vers plusieurs plateformes d’IA et permet de créer des clients pour OpenAI, Anthropic, Azure, Gemini, etc.

Exemple avec Gemini :

use Symfony\AI\Platform\Bridge\Gemini\Embeddings as GeminiEmbeddings;
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory as GeminiPlatformFactory;

$platform = GeminiPlatformFactory::create($_SERVER['GEMINI_API_KEY']);

// Choix du modèle d'embedding
$embeddings = new GeminiEmbeddings();
$text = 'Le chat est un animal domestique très apprécié.';

$response = $platform->invoke($embeddings, $text);

$vector = $response->asVectors()[0];

$text est un exemple de texte à vectoriser. Sa longueur doit être inférieure au nombre de tokens maximum autorisé par la plateforme. Pour simplifier, on peut considérer qu’un token ≈ un mot (pour les puristes : token ≈ sous-mot, variable selon le tokenizer; en pratique, il y a environ 0,7 à 1,3 tokens par mot selon la langue). La limite varie souvent entre 512 et 8192 tokens, parfois plus.

$vector est une instance de Symfony\AI\Platform\Vector\Vector contenant :

  • data : tableau de valeurs flottantes représentant le vecteur ;
  • dimension : nombre de valeurs flottantes dans le vecteur.

Chaque plateforme d’IA a ses propres modèles d’embedding. Par exemple, Gemini retourne des vecteurs de 3072 dimensions, OpenAI en propose de 1536. Ce n’est pas la taille qui compte, mais la qualité du modèle.

Note : Ici, nous avons fait le choix d’utiliser symfony/ai-platform sans utiliser symfony/ai-bundle qui permet une intégration au framework full stack. Nous avons fait l’instanciation de la plateforme à la main, alors qu’il aurait été possible d’utiliser la configuration sémantique du bundle pour l’instancier automatiquement.

Section intitulée plateformes-supporteesPlateformes supportées

Au moment de l’écriture de cet article, Symfony AI Platform prend en charge : Albert, Anthropic, Azure, Bedrock, Gemini, HuggingFace, LmStudio, Meta, Mistral, Ollama, OpenAI, OpenRouter, Replicate, TransformersPhp et Voyage.

Section intitulée bien-choisir-sa-plateformeBien choisir sa plateforme

Les critères principaux :

  • Coût : vérifier le prix par token pour la vectorisation ;
  • Précision : certains modèles sont meilleurs pour certains types de contenus ;
  • Performance : rapidité de traitement.

Grâce à Symfony AI, changer de plateforme est simple : il suffit de modifier la création du client et du modèle. Exemple pour passer de Gemini à Mistral :

use Symfony\AI\Platform\Bridge\Mistral\Embeddings as MistralEmbeddings;
use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory as MistralPlatformFactory;

$platform = MistralPlatformFactory::create($_SERVER['MISTRAL_API_KEY']);
$embeddings = new MistralEmbeddings();

// Le reste du code reste inchangé !

Nous avons constaté de grandes différences de performance et de précision selon les plateformes. Il faut vraiment tester pour trouver celle qui convient le mieux à vos besoins à un instant T. Il n’est pas rare que chaque nouvelle génération d’un LLM apporte une meilleure précision ou performance.

Section intitulée heberger-son-propre-modele-d-embeddingHéberger son propre modèle d’embedding

Si vous ne voulez pas dépendre d’un service tiers, vous pouvez héberger votre propre modèle, ce qui peut améliorer la gestion des données sensibles et réduire les coûts.

Pour exécuter un modèle localement, Ollama est une bonne option. Ollama est un daemon exposant une API HTTP, avec un client CLI simple :

$ ollama
Usage:
  ollama [flags]
  ollama [command]

Available Commands:
  serve       Start ollama
  create      Create a model
  show        Show information for a model
  run         Run a model
  stop        Stop a running model
  pull        Pull a model from a registry
  push        Push a model to a registry
  list        List models
  ps          List running models
  cp          Copy a model
  rm          Remove a model
  help        Help about any command

Pour trouver des modèles : Ollama Models.

Puis, pour télécharger un modèle :

$ ollama pull nomic-embed-text

Si la liste est limitée ou la précision insuffisante, vous pouvez chercher sur HuggingFace :

$ ollama pull hf.co/Qwen/Qwen3-Embedding-0.6B-GGUF

Comme pour les plateformes SaaS, chaque modèle a ses caractéristiques :

  • Licence : certaines sont commerciales ;
  • Performance : taille du modèle et vitesse ;
  • Précision : qualité des vecteurs ;
  • Langue : optimisation pour certaines langues.

💡 Souvenez-vous que les vecteurs sont des tableaux de flottants. Et savez vous qui est assez mauvais pour faire des calculs avec des flottants ? Votre CPU ! Et savez vous qui est très bon pour faire des calculs avec des flottants ? Votre GPU ! Nous avons pu observer un facteur 10 entre les deux. Il faut donc privilégier une machine avec un GPU pour héberger votre modèle d’embedding.

Dans le cas d’Ollama, il faut configurer Symfony comme suit:

use Symfony\AI\Platform\Bridge\Ollama\Ollama;
use Symfony\AI\Platform\Bridge\Ollama\PlatformFactory as OllamaPlatformFactory;

$platform = OllamaPlatformFactory::create($_SERVER['OLLAMA_HOST_URL']);
$embeddings = new Ollama('hf.co/Qwen/Qwen3-Embedding-4B-GGUF'),

// Le reste du code reste inchangé !

Maintenant que nous avons des vecteurs, nous pouvons les stocker et les comparer.

Section intitulée stocker-des-vecteurs-avec-code-symfony-ai-store-codeStocker des vecteurs avec symfony/ai-store

Nous allons utiliser le composant Store de Symfony AI pour stocker et comparer les vecteurs :

composer require symfony/ai-store@dev

Ce composant fournit une abstraction de stockage de données avec indexation et recherche pour les applications IA. Au moment de l’écriture de cet article, il prend en charge :

Azure, ChromaDB, ClickHouse, MariaDB, Meilisearch, MongoDB, Neo4j, Pinecone, Postgres, Qdrant, Redis (PR en cours), SurrealDB, Typesense.

Le choix du store est souvent plus simple que pour la vectorisation : utilisez celui que vous connaissez déjà et qui est disponible dans votre application. Cependant, les performances peuvent varier fortement. Par exemple, pour un même dataset, nous avons obtenu :

  • Redis : 4 secondes
  • Postgres : 10 secondes
  • ClickHouse : 30 secondes

Comme Postgres est largement utilisé dans les applications Symfony, nous allons l’utiliser dans cet exemple :

use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Tools\DsnParser;
use Symfony\AI\Store\Bridge\Postgres\Distance;
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
use Symfony\AI\Store\VectorDocument;
use Symfony\AI\Store\Metadata;
use Symfony\Component\Uid\UuidV4;

$sqlConnection = DriverManager::getConnection((new DsnParser())->parse($_SERVER['POSTGRES_URI']));
$sqlStore = PostgresStore::fromDbal(
    connection: $sqlConnection,
    tableName: $tableName,
    distance: Distance::Cosine,
);

// Crée la table si elle n'existe pas, avec le schéma adapté
$sqlStore->initialize();

$document = new VectorDocument(
    new UuidV4($documentId),
    $vector,
    new Metadata([
        'title' => 'Titre du document',
        'datasetId' => $datasetId,
        // D'autres métadonnées peuvent être ajoutées ici
    ]),
);

$sqlStore->add($document);

Section intitulée comparer-des-vecteursComparer des vecteurs

Une fois les documents ajoutés au store, nous pouvons les interroger pour trouver ceux qui sont similaires à un vecteur donné.

Nous utilisons le cosinus comme mesure de distance, mais vous pouvez choisir une autre mesure si votre store le permet. La valeur retournée sera comprise entre 0 et 1, où 0 signifie que les vecteurs sont identiques et 1 qu’ils sont complètement différents.

Ici, nous parcourons tous les documents d’un dataset (datasetA) et cherchons les documents similaires dans un autre dataset (datasetB).

use Symfony\AI\Platform\Vector\Vector;

$rows = $sqlConnection
    ->executeQuery(<<<SQL
        SELECT *
        FROM {$tableName}
        WHERE metadata->>'datasetId' = '{$datasetA}'
    SQL)
    ->fetchAllAssociative()
;

foreach ($rows as $row) {
    // La colonne `embedding` contient le vecteur sous forme de JSON
    $vector = new Vector(json_decode($row['embedding'], true, 512, \JSON_THROW_ON_ERROR));

    $documents = $sqlStore->query(
        $vector,
        [
            // Exclut les documents avec un score > 0.17
            // 0 = correspondance parfaite, 1 = complètement différent
            'maxScore' => 0.17,
            // Filtre : uniquement datasetB et exclut le document courant
            'where' => "metadata->>'datasetId' = :datasetId AND id != :currentId",
            'params' => [
                'datasetId' => $datasetB,
                'currentId' => $row['id'],
            ],
        ],
    );

    if (!$documents) {
        continue;
    }

    $metadata = json_decode($row['metadata'], true, 512, \JSON_THROW_ON_ERROR);

    echo "Current document: {$metadata['title']}\n";
    foreach ($documents as $document) {
        echo "- {$document->metadata['title']} (score: {$document->score})\n";
    }
    echo "\n";
}

Si on utilise ce petit projet pour trouver les pages similaire entre jolicode.com et www.premieroctet.com, nous obtenons :

Current document: https://jolicode.com/blog/jai-teste-tailwind-css
- https://www.premieroctet.com/expertises/tailwind (score: 0.152959644794)

Current document: https://jolicode.com/blog/construire-un-chatbot-specialise-sur-vos-donnees-grace-a-lia-generative-et-php
- https://www.premieroctet.com/blog/comment-fonctionne-un-rag (score: 0.151540160179)

Current document: https://jolicode.com/nos-metiers/technologies/react
- https://www.premieroctet.com/blog/premier-octet-vous-tire-les-cartes (score: 0.16528314352)

Current document: https://jolicode.com/blog/tag/dotjs
- https://www.premieroctet.com/blog/dotjs-2018 (score: 0.151396155357)

Current document: https://jolicode.com/blog/de-la-nostalgie-aux-nouveautes-ce-que-dotjs-2025-nous-a-inspire
- https://www.premieroctet.com/blog/dotjs-2025 (score: 0.0832768678665)
- https://www.premieroctet.com/blog/en/dotjs-2025 (score: 0.162836551666)

Current document: https://jolicode.com/blog/retour-sur-la-dotjs-2018
- https://www.premieroctet.com/blog/dotjs-2018 (score: 0.163798093796)

Current document: https://jolicode.com/blog/paris-web-2018-le-futur-du-web
- https://www.premieroctet.com/blog/paris-web-2018 (score: 0.169616103172)

Pas mal non?

Section intitulée conclusionConclusion

En quelques lignes de code, nous avons pu vectoriser des documents, les stocker et retrouver ceux qui sont similaires à un document donné.

Grâce à ses nombreux bridges, Symfony AI permet de tester facilement différentes plateformes d’IA et de stocker les données dans le store de votre choix.

Vous pouvez ainsi créer des applications IA performantes, flexibles en maîtrisant totalement les données.

Il reste encore beaucoup à explorer dans Symfony AI, notamment les agents et le Model Context Protocol (MCP). Nous vous invitons à consulter la documentation (WIP) pour en savoir plus.

Commentaires et discussions

Nos articles sur le même sujet

Nos formations sur ce sujet

Notre expertise est aussi disponible sous forme de formations professionnelles !

Voir toutes nos formations