La recherche textuelle traditionnelle fait correspondre des mots. Vous cherchez « chaise » et trouvez des documents contenant le mot « chaise ». Mais vous manquez « siège », « tabouret », « fauteuil » et « assise » -- des mots qui signifient la même chose mais utilisent des lettres différentes. La recherche par mots-clés est rapide et prévisible, mais elle ne comprend pas le sens.
La recherche sémantique fait correspondre le sens. Vous cherchez « assise confortable pour bureau » et trouvez des produits décrits comme « chaise de bureau ergonomique », « tabouret pivotant réglable » et « siège bureau avec support lombaire » -- même si aucune de ces descriptions ne contient les mots « confortable » ou « assise ». La recherche comprend les concepts, pas seulement les caractères.
FLIN intègre la recherche sémantique dans le système de types. Un champ déclaré comme semantic text est automatiquement transformé en vecteur, indexé pour une recherche par similarité rapide et interrogeable avec le mot-clé search. Pas de Pinecone. Pas d'Elasticsearch. Pas de base de données vectorielle à configurer et maintenir.
Le type semantic text
Le modificateur semantic sur un champ texte indique à FLIN de générer des embeddings automatiquement :
flinentity Product {
name: text // Texte normal -- correspondance exacte
description: semantic text // Sémantique -- recherche basée sur le sens
sku: text // Texte normal -- identifiants
}Lorsqu'un champ semantic text est sauvegardé, FLIN :
1. Stocke le texte original dans FlinDB.
2. Génère un embedding vectoriel en utilisant le modèle IA configuré.
3. Indexe l'embedding dans un index HNSW (Hierarchical Navigable Small World).
4. Associe l'embedding à l'instance d'entité.
Les quatre étapes se produisent de manière atomique lors du save. Le développeur écrit save product et l'embedding est généré. Il n'y a pas d'étape d'indexation séparée, pas de job batch, pas de processus de synchronisation.
Le mot-clé search
Le mot-clé search effectue une recherche par similarité sémantique :
flinresults = search "comfortable seating for office"
in Product
by description
limit 10La syntaxe est : search "requête" in Entité by champ [limit N]
La chaîne de requête est transformée en embedding en utilisant le même modèle que le champ, puis comparée à tous les embeddings stockés en utilisant la similarité cosinus. Les résultats sont classés par score de similarité et les N premiers sont retournés comme entités FLIN typées.
flinentity Article {
title: text
content: semantic text
summary: semantic text
}
// Rechercher par différents champs sémantiques
by_content = search "machine learning tutorials" in Article by content
by_summary = search "AI beginner guide" in Article by summaryUne entité peut avoir plusieurs champs sémantiques, chacun avec son propre index. Chercher par content compare avec le texte complet de l'article. Chercher par summary compare avec les résumés plus courts. Le choix dépend du cas d'utilisation -- recherche par contenu complet pour la précision, recherche par résumé pour la vitesse.
Comment fonctionnent les embeddings
Un embedding est un vecteur de nombres à virgule flottante (typiquement 384 à 1536 dimensions) qui représente le sens d'un texte. Des textes similaires produisent des vecteurs similaires. La distance entre les vecteurs est corrélée à la distance sémantique.
"comfortable office chair" -> [0.12, -0.45, 0.78, 0.33, ...]
"ergonomic desk seating" -> [0.11, -0.42, 0.76, 0.35, ...]
"kitchen table with drawers" -> [-0.55, 0.23, 0.09, -0.41, ...]Les deux premiers vecteurs sont proches (sens similaire). Le troisième est éloigné (concept différent). La similarité cosinus mesure cette distance :
similarity("comfortable office chair", "ergonomic desk seating") = 0.92
similarity("comfortable office chair", "kitchen table with drawers") = 0.23L'index HNSW
La recherche par similarité en force brute compare le vecteur de requête à chaque vecteur stocké. C'est O(n) et devient inutilisable à grande échelle. FLIN utilise HNSW (Hierarchical Navigable Small World) pour la recherche approximative de plus proches voisins :
rustpub struct HnswIndex {
layers: Vec<Vec<Node>>,
entry_point: usize,
max_connections: usize,
ef_construction: usize,
}
impl HnswIndex {
pub fn search(
&self,
query: &[f32],
k: usize,
ef_search: usize,
) -> Vec<(usize, f32)> {
// Commencer de la couche supérieure, descendre à travers les couches
let mut current = self.entry_point;
for layer in (1..self.layers.len()).rev() {
current = self.greedy_search(query, current, layer);
}
// Recherche exhaustive dans la couche inférieure avec largeur de faisceau ef_search
self.beam_search(query, current, 0, ef_search, k)
}
}Propriétés HNSW : - Temps de requête : O(log n) en moyenne - Mémoire : O(n * d) où d est la dimension de l'embedding - Précision : >95% de rappel à des vitesses de recherche de millions par seconde - Temps d'insertion : O(log n) en moyenne
Pour une base de données de 100 000 produits, une requête de recherche sémantique se complète en moins de 5 millisecondes.
Génération d'embeddings
Les embeddings sont générés en utilisant le modèle IA configuré. FLIN prend en charge plusieurs fournisseurs d'embeddings :
flin// flin.config
{
"ai": {
"provider": "anthropic",
"model": "claude-3-haiku",
"embedding_model": "text-embedding-3-small"
}
}Pour les cas d'utilisation hors ligne ou à faible latence, FLIN prend en charge la génération locale d'embeddings via FastEmbed (couvert dans l'article 119).
Embedding automatique lors de la sauvegarde
Lorsqu'une entité avec un champ semantic text est sauvegardée, l'embedding est généré automatiquement :
flinproduct = Product {
name: "Ergonomic Office Chair",
description: "A comfortable chair designed for long work sessions with adjustable lumbar support, breathable mesh back, and 360-degree swivel base.",
sku: "CHAIR-ERG-001"
}
save product // Embedding généré ici pour le champ descriptionSi la description est mise à jour, l'embedding est regénéré :
flinproduct = Product.find(42)
product.description = "Updated description with new features..."
save product // Embedding regénéréL'ancien embedding est remplacé de manière atomique. Il n'y a pas de problème d'index obsolète.
Champs sémantiques multiples
Les entités peuvent avoir plusieurs champs sémantiques, chacun interrogeable indépendamment :
flinentity Job {
title: text
description: semantic text
requirements: semantic text
benefits: semantic text
}
// Rechercher différents aspects de la même entité
by_desc = search "remote engineering position" in Job by description
by_reqs = search "python and kubernetes experience" in Job by requirements
by_perks = search "health insurance and remote work" in Job by benefitsChaque champ sémantique a son propre index HNSW. Les indices sont indépendants -- mettre à jour la description n'affecte pas l'index des exigences.
Recherche dans les templates
La recherche sémantique s'intègre naturellement avec les templates de vue de FLIN :
flin// app/products.flin
query = ""
<input placeholder="Rechercher des produits..."
value={query}
input={results = search query in Product by description limit 20}>
{if query.len > 3}
{for product in results}
<div class="product-card">
<h3>{product.name}</h3>
<p>{product.description}</p>
<span class="price">${product.price}</span>
</div>
{/for}
{/if}Au fur et à mesure que l'utilisateur tape, la recherche se déclenche à chaque événement d'entrée. Les résultats se mettent à jour de manière réactive. L'ensemble de l'expérience de recherche -- de la frappe aux résultats affichés -- est construit avec le système de réactivité standard de FLIN.
Caractéristiques de performance
| Opération | Temps | Notes |
|---|---|---|
| Génération d'embedding (API) | 100-300 ms | Dépend de la longueur du texte et du fournisseur |
| Génération d'embedding (local) | 10-50 ms | Avec FastEmbed |
| Insertion HNSW | < 1 ms | Par document |
| Recherche HNSW (10K docs) | < 2 ms | Top 10 résultats |
| Recherche HNSW (100K docs) | < 5 ms | Top 10 résultats |
| Recherche HNSW (1M docs) | < 15 ms | Top 10 résultats |
Le goulot d'étranglement est la génération d'embeddings, pas la recherche. Pour les cas d'utilisation interactifs (comme l'exemple de recherche en temps réel ci-dessus), la génération locale d'embeddings avec FastEmbed est recommandée pour maintenir la latence sous 50 ms.
Quand utiliser semantic vs texte normal
| Utiliser sémantique | Utiliser normal |
|---|---|
| Longues descriptions | Noms et labels courts |
| Contenu généré par l'utilisateur | Identifiants structurés (SKU, ID) |
| Champs à forte recherche | Champs de correspondance exacte |
| Contenu en langage naturel | Codes, énumérations, valeurs de statut |
| Formulations multiples possibles | Valeurs canoniques |
Le modificateur semantic ajoute un surcoût de stockage (vecteur d'embedding par enregistrement) et une latence d'écriture (génération d'embedding lors de la sauvegarde). Utilisez-le pour les champs où la recherche basée sur le sens apporte de la valeur, pas pour chaque champ texte.
Considérations de confidentialité
La génération d'embeddings nécessite l'envoi de texte à un fournisseur IA (sauf en utilisant des embeddings locaux). FLIN traite cela :
- Seuls les champs sémantiques sont envoyés pour l'embedding. Les champs texte normaux, les mots de passe et autres données d'entités restent locaux.
- Texte, pas contexte. La valeur du champ est envoyée pour l'embedding, pas la structure de l'entité ou les données connexes.
- Option locale. FastEmbed génère des embeddings localement sans aucun appel réseau.
Pour les applications avec des exigences strictes de résidence des données, l'embedding local avec FastEmbed élimine toute transmission de données externe.
La recherche sémantique transforme la façon dont les utilisateurs interagissent avec les données. Au lieu de leur demander de connaître les mots-clés exacts, les catégories ou les tags, ils décrivent ce qu'ils veulent en langage naturel et FLIN trouve les correspondances les plus proches. Dans le prochain article, nous explorons la passerelle IA -- comment FLIN se connecte à huit fournisseurs IA différents à travers une seule API unifiée.
Ceci est la partie 117 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont conçu et construit un langage de programmation à partir de zéro.
Navigation de la série : - [116] Le moteur d'intentions : requêtes base de données en langage naturel - [117] Recherche sémantique et stockage vectoriel (vous êtes ici) - [118] Passerelle IA : 8 fournisseurs, une seule API - [119] Intégration FastEmbed pour les embeddings