Skip to content

ADR 0003 — Embeddings BGE Multilingual Gemma2 via fournisseur externe

Date : initial Statut : Accepté

Contexte

Pour la recherche vectorielle, il faut produire un embedding stable et de qualité pour :

  1. Chaque voyage du catalogue (calculé au reindex sur la concaténation name + subtitle + description + servicesIncluded + highLights).
  2. Chaque requête utilisateur arrivant via GET /travels?search=….

Contraintes :

  • Français natif : le contenu et les requêtes sont en français à 95%. Le modèle doit comprendre la sémantique française au moins aussi bien que l'anglais.
  • Pas d'infra ML à opérer : pas de GPU, pas de service d'inférence à maintenir côté équipe.
  • Coût modéré : quelques centaines de voyages × reindex/jour + ~quelques milliers de requêtes/jour.
  • Latence acceptable : < 500ms pour la requête utilisateur (l'embedding est sur le path critique).

Décision

Utiliser BGE Multilingual Gemma2 (3584 dimensions) servi par l'API embeddings d'Infomaniak (compatibilité OpenAI).

  • Modèle : bge_multilingual_gemma2.
  • Hébergement : Infomaniak (fournisseur cloud suisse, datacentre Suisse — important pour la conformité données Buchard).
  • Authentification : Bearer token (INFOMANIAK_TOKEN) + product ID (INFOMANIAK_PID). Détails du provisioning hors documentation publique — voir coffre-fort équipe.
  • Wrapper Python : src/infomaniak.py (~40 lignes, deux fonctions get_embedding(text) et get_embeddings(texts)).

Conséquences

Positives

  • Qualité francophone solide. BGE Multilingual a été entraîné massivement sur des données multilingues, dont français. Tests qualitatifs en interne confirment de bons matches sémantiques.
  • Dimensions élevées (3584) → capacité de représentation suffisante pour distinguer des nuances dans un petit catalogue voyages.
  • Pas de stack ML interne. Pas de Triton, Ollama, TGI à opérer.
  • Données chez Infomaniak (Suisse) — aligné avec les contraintes Buchard.
  • API OpenAI-compatible. Le wrapper Python est trivial. Si on bascule sur OpenAI ou autre, c'est un changement minime.

Négatives

  • Couplage à un fournisseur externe. Une panne Infomaniak casse le path ?search=… (la branche filtre-seul continue). Pas de fallback offline.
  • 3584 dimensions = stockage non trivial. 3584 × 4 octets = 14 Ko par embedding. Pour 1000 voyages, 14 Mo de vecteurs. Pas dramatique mais à connaître si le catalogue grossit (10k records ≈ 140 Mo).
  • Pas d'index ANN (hnsw) : la dimensionnalité élevée pénalise les index approximatifs. Pour l'instant, seq scan suffit ; à reconsidérer si la table dépasse ~10k lignes.
  • Changement de modèle = reindex complet. Les embeddings d'un modèle A ne sont pas comparables à ceux d'un modèle B. Migrer = (a) migration SQL ALTER COLUMN embedding TYPE vector(new_dim), (b) vider et re-ingérer.
  • Latence ~100-300ms par appel. Pas négligeable sur un endpoint qui doit répondre rapidement. Mitigeable par batch côté reindex (10 textes par appel).

Alternatives écartées

  • OpenAI text-embedding-3-large (3072D) : qualité similaire, datacentre US — bloquant pour les contraintes data Buchard.
  • Sentence-transformers self-hosted (paraphrase-multilingual-mpnet) : auto-hébergeable, 768D, qualité correcte. Mais nécessite un service d'inférence à opérer. Refusé pour le coût opérationnel.
  • Mistral Embed (1024D) : alternative française. Pas encore mature au moment du choix initial.
  • Cohere Embed Multilingual (1024D) : qualité comparable. Hosting hors UE.
  • Encodage TF-IDF / SVD côté Python : zero-cost, mais qualité sémantique très inférieure — c'est ce que FTS fait déjà via tsvector, autant ne pas le doubler.

Coût / Quotas

À surveiller (pas exposé en métriques aujourd'hui) :

  • Nombre d'appels Infomaniak / jour. Coût = (reindex_par_jour × pages × 10 records) + (recherches_par_jour × 1).
  • Avec REINDEX_FREQUENCY=900 (15 min) et un catalogue de ~300 voyages : 96 reindex × 30 pages = 2880 appels/jour côté reindex.
  • Côté requêtes : un appel par recherche avec ?search=…. Les recherches filtre-only n'embedderont rien.

Si le coût devient problématique :

  1. Ajouter un hash content-based dans _ingest_source pour skipper l'embedding des records inchangés (cf. architecture/reindex-lifecycle.md § « Ce qui n'est PAS fait »).
  2. Augmenter REINDEX_FREQUENCY (45 min ou 1h sont raisonnables si le catalogue ne bouge pas vite).
  3. Cacher les embeddings des requêtes fréquentes (memcached / redis devant infomaniak.get_embedding).

Note de sécurité

Le token Infomaniak ne doit jamais apparaître dans les logs ni dans les réponses HTTP. Aucun log applicatif n'inclut le header Authorization. Le secret est monté via Docker secrets en prod (cf. architecture/deployment.md).

Contributors

No contributors

Changelog

No recent changes