03-comment-je-travaille/tutos/ia/microservice-embeddings-python.md

Tuto — Microservice d'embeddings en Python (FastAPI)

Public visé : développeur PHP/Symfony qui découvre Python. Objectif : exposer un petit service HTTP qui transforme du texte en vecteurs (embeddings), consommé par le cœur RAG côté Symfony.

Contexte : ce service est le composant « Embeddings » de la spec Lot 0 — cœur RAG. Pour comprendre les concepts, voir la fiche Embeddings et recherche sémantique.


Pourquoi un service séparé en Python ?

  • L'Ă©cosystème d'infĂ©rence IA (modèles, sentence-transformers) est mĂ»r en Python.
  • On isole l'infĂ©rence dans un service stateless appelĂ© en HTTP → la frontière avec Symfony est nette, et chaque brique reste dans son langage de prĂ©dilection.
  • Le modèle tourne en local (CPU suffisant pour de l'embedding) : gratuit, on ne paie l'API que pour la gĂ©nĂ©ration.

Prérequis

  • Python 3.10+ (python3 --version).
  • Accès rĂ©seau pour tĂ©lĂ©charger le modèle au premier lancement.
  • Modèle retenu : intfloat/multilingual-e5-base (multilingue, adaptĂ© au français).

1. Environnement isolé

mkdir embeddings-service && cd embeddings-service
python3 -m venv .venv
source .venv/bin/activate        # Windows : .venv\Scripts\activate
pip install --upgrade pip
pip install "fastapi" "uvicorn[standard]" "sentence-transformers"

Bon réflexe : figer les versions ensuite avec pip freeze > requirements.txt.


2. Le service (app.py)

from typing import Literal
from fastapi import FastAPI
from pydantic import BaseModel, Field
from sentence_transformers import SentenceTransformer

MODEL_NAME = "intfloat/multilingual-e5-base"
model = SentenceTransformer(MODEL_NAME)          # chargé une fois au démarrage

app = FastAPI(title="Embeddings service")


class EmbedRequest(BaseModel):
    type: Literal["query", "passage"]        # 422 automatique si autre valeur
    texts: list[str] = Field(min_length=1)   # 422 si la liste est vide


@app.get("/health")
def health():
    return {
        "status": "ok",
        "model": MODEL_NAME,
        "dim": model.get_sentence_embedding_dimension(),
    }


@app.post("/embed")
def embed(req: EmbedRequest):
    # Convention e5 : préfixer selon l'usage (gérée côté service)
    prefix = "query: " if req.type == "query" else "passage: "
    inputs = [prefix + t for t in req.texts]
    # normalize_embeddings=True → vecteurs prêts pour la similarité cosinus
    vectors = model.encode(inputs, normalize_embeddings=True).tolist()
    return {
        "model": MODEL_NAME,
        "dim": model.get_sentence_embedding_dimension(),
        "vectors": vectors,
    }

Points clés :

  • Le modèle est chargĂ© une seule fois (au dĂ©marrage), pas Ă  chaque requĂŞte.
  • Les prĂ©fixes e5 (query: / passage:) sont indispensables Ă  la qualitĂ© — on les ajoute ici, le client Symfony n'a pas Ă  s'en soucier.
  • Validation stricte (Literal + texts non vide) : une entrĂ©e invalide renvoie HTTP 422 automatiquement — pas de fallback silencieux qui produirait un mauvais prĂ©fixe.
  • normalize_embeddings=True simplifie la similaritĂ© cosinus cĂ´tĂ© index.

3. Lancer le service

uvicorn app:app --host 127.0.0.1 --port 8001

On écoute sur 127.0.0.1 (local) : le service ne doit pas être exposé publiquement. En production, on le place derrière le reverse proxy / on le garde sur la boucle locale.


4. Tester

# Santé
curl http://127.0.0.1:8001/health

# Embeddings d'un passage
curl -X POST http://127.0.0.1:8001/embed \
  -H "Content-Type: application/json" \
  -d '{"type":"passage","texts":["Comment réinitialiser mon mot de passe ?"]}'

La réponse contient dim (768 pour e5-base) et vectors (un vecteur par texte). Le contrat correspond à la spec ia-coeur.md §4.1.


5. Appel depuis Symfony (côté cœur)

// Implémentation de EmbeddingClientInterface (extrait)
$response = $this->httpClient->request('POST', $this->serviceUrl . '/embed', [
    'json' => ['type' => $type, 'texts' => $texts],
    'timeout' => 30,
]);

$data = $response->toArray();
return $data['vectors']; // float[][]

L'URL du service (rag.embedding.service_url) est injectée par configuration.


6. Déploiement (note)

  • Sur le VPS (CPU only), le service tourne en tâche de fond — par exemple via un service systemd dĂ©diĂ©, dans son venv.
  • Surveiller la mĂ©moire : un seul modèle chargĂ© ; Ă©viter d'en charger plusieurs.
  • Exposer /health pour la supervision (la commande app:rag:stats cĂ´tĂ© Symfony l'interroge).

Bonnes pratiques retenues

  • Stateless : aucune donnĂ©e persistĂ©e par le service ; il calcule et rĂ©pond.
  • Local par dĂ©faut : pas d'exposition publique, pas de donnĂ©es sensibles (cf. cadre IA).
  • Contrat stable : /health et /embed documentĂ©s, alignĂ©s sur la spec du cĹ“ur.
  • Versions figĂ©es (requirements.txt) pour la reproductibilitĂ©.

Pour aller plus loin

  • Spec du socle : specs/ia-coeur.md
  • Concepts : Embeddings et recherche sĂ©mantique
  • DĂ©ploiement local vs API : fiche 2.5

Assistant documentaire

Posez une question sur la documentation. Les réponses citent leurs sources — un clic ouvre le document à gauche.

Loading…
Loading the web debug toolbar…
Attempt #