En bref : Les vector embeddings transforment le code en coordonnées numériques dans un espace multidimensionnel, où le code sémantiquement similaire se regroupe naturellement. Cela permet une recherche sémantique par IA qui comprend ce que fait votre code, pas seulement ce qu'il dit. Les modèles d'embeddings modernes comme Voyage-3 et les modèles ONNX locaux alimentent des outils comme Semantiq pour trouver des fonctions liées, détecter les duplications et comprendre les codebases à grande échelle—tout en traitant le code comme du sens, pas du texte.
Que sont les vector embeddings ?
Imaginez que vous voulez expliquer où se trouve un restaurant. Vous pourriez dire "près du parc, à deux rues de la rue principale", mais des coordonnées GPS comme (40.7589, -73.9851) sont plus précises. Les vector embeddings fonctionnent de la même manière pour le sens.
Au lieu de décrire le code avec des mots, les embeddings le représentent comme un point dans un espace multidimensionnel—typiquement 768, 1024 ou même 1536 dimensions. Une fonction qui trie un tableau pourrait être aux coordonnées [0.23, -0.45, 0.67, ...], tandis qu'une autre fonction de tri (même dans un langage différent) se trouve à proximité car elles partagent une signification sémantique.
L'idée centrale : la distance dans l'espace d'embedding correspond à la similarité sémantique. Les fonctions qui font des choses similaires se regroupent, indépendamment de leur syntaxe, leurs noms de variables ou leur langage de programmation.
1Représentation textuelle:2"fonction qui trie un tableau par ordre croissant"34Représentation vectorielle:5[0.234, -0.456, 0.678, 0.123, -0.890, 0.345, ...] (1024 dimensions)6 ↓7 Coordonnées numériques dans l'espace sémantiqueCette transformation des symboles vers la sémantique est ce qui permet à l'IA de "comprendre" le code d'une manière que la recherche par mots-clés ne pourrait jamais faire.
Du texte au sens : comment fonctionnent les modèles d'embeddings
La conversion du code source en vector embeddings implique plusieurs étapes, alimentées par des réseaux de neurones basés sur l'architecture transformer.
Étape 1 : Tokenisation
D'abord, le code est découpé en tokens—pas seulement des mots, mais des unités significatives incluant opérateurs, mots-clés et caractères spéciaux. Un tokenizer pourrait découper getUserById(42) en ['get', 'User', 'By', 'Id', '(', '42', ')'], préservant la structure sémantique.
Les tokenizers de code modernes comprennent :
- Les conventions CamelCase et snake_case
- Les mots-clés des langages de programmation (
async,const,class) - Les opérateurs et la syntaxe (
=>,::,?.) - Les patterns courants comme les signatures de fonction
Étape 2 : Endévelopement par transformer
Les tokens passent à travers un modèle transformer—la même architecture derrière GPT et BERT, mais entraîné spécifiquement sur du code. Les transformers utilisent des mécanismes d'auto-attention pour comprendre les relations entre tokens :
// Le modèle apprend que ces tokens sont liés :
async function fetchUser(id: string): Promise<User> {
// ↑ lié à ↑ ↑ lié au type de retourChaque couche de transformer construit des représentations de plus en plus abstraites, de la syntaxe à la sémantique. Les couches précoces capturent des patterns comme "c'est une déclaration de fonction", tandis que les couches plus profondes comprennent "ceci récupère des données de manière asynchrone".
Étape 3 : Pooling vers des dimensions fixes
Les sorties du transformer sont de longueur variable (un vecteur par token), mais nous avons besoin d'un seul vecteur de taille fixe pour l'ensemble du snippet de code. Les stratégies de pooling incluent :
- Mean pooling : Moyenne de tous les vecteurs de tokens
- CLS token pooling : Utilisation d'un token de classification spécial
- Max pooling : Valeurs maximales à travers les dimensions
Le résultat est un vecteur dense—un seul point dans l'espace multidimensionnel qui représente le sens du code.
Pourquoi le code nécessite des modèles spécialisés
Les embeddings de texte généralistes peinent avec le code car :
- La syntaxe porte du sens :
user.getName()etuser?.getName()sont sémantiquement différents à cause d'un seul caractère - Hiérarchies de contexte : La signification d'une variable dépend de sa portée (locale, classe, module)
- Patterns cross-langage :
Promise<T>en TypeScript etFuture[T]en Scala représentent le même concept - La structure compte : L'indentation, les accolades et les espaces sont sémantiques, pas décoratifs
Les modèles d'embeddings de code sont entraînés sur des milliards de lignes de code réel provenant de GitHub, Stack Overflow et de documentation pour apprendre ces patterns.
Embeddings de code vs embeddings de texte
Voyons pourquoi les embeddings de code spécialisés surpassent les modèles de texte généralistes avec des exemples concrets.
Exemple 1 : Sémantiquement identique, textuellement différent
Ces fonctions sont sémantiquement identiques mais ne partagent presque aucun mot-clé :
def total_price(items):
return sum(item['price'] for item in items)const calculateSum = (products) =>
products.reduce((acc, p) => acc + p.cost, 0);Similarité d'embeddings textuels : ~0.35 (faible) Similarité d'embeddings de code : ~0.89 (forte)
Les embeddings de code reconnaissent que les deux implémentent "somme des prix d'une collection", tandis que les embeddings textuels voient des langages, noms de variables et mots-clés différents.
Exemple 2 : Signatures de fonction vs implémentations
1// Déclaration2interface UserRepository {3 findById(id: string): Promise<User | null>;4}56// Implémentation7class PostgresUserRepo implements UserRepository {8 async findById(id: string): Promise<User | null> {9 const result = await this.db.query(10 'SELECT * FROM users WHERE id = $1', [id]11 );12 return result.rows[0] || null;13 }14}Les embeddings de code comprennent que :
- L'interface définit un contrat
- L'implémentation remplit ce contrat
- Les deux sont liés mais servent des buts différents
- La requête SQL fait partie de la stratégie d'implémentation
Les embeddings textuels manqueraient ces relations architecturales.
Exemple 3 : Relations cross-langage
1// Go2type Result[T any] struct {3 Value T4 Err error5}1// Rust2enum Result<T, E> {3 Ok(T),4 Err(E),5}Les embeddings de code reconnaissent que les deux implémentent le pattern "Result monad" malgré une syntaxe et des mots-clés complètement différents. Cela permet de trouver des patterns équivalents à travers les langages.
Le pipeline d'embeddings
Voici comment fonctionne un système d'embeddings de code en production, étape par étape :
Étape 1 : Parser la structure du code
Utiliser un parser comme Tree-sitter pour extraire la structure syntaxique :
1import Parser from 'tree-sitter';2import TypeScript from 'tree-sitter-typescript';34const parser = new Parser();5parser.setLanguage(TypeScript);67const sourceCode = `8function calculateDiscount(price: number, rate: number): number {9 return price * (1 - rate);10}11`;1213const tree = parser.parse(sourceCode);14// Tree contient l'AST avec les limites des fonctions, paramètres, typesÉtape 2 : Découpage intelligent
Diviser le code en unités significatives—pas des limites arbitraires de caractères, mais des frontières sémantiques :
1// Bon découpage (par fonction)2chunk1 = "function calculateDiscount(price: number, rate: number): number { ... }"3chunk2 = "function applyTax(amount: number, taxRate: number): number { ... }"45// Mauvais découpage (par nombre de caractères)6chunk1 = "function calculateDiscount(price: nu"7chunk2 = "mber, rate: number): number { return"Respecter :
- Les limites de fonction/méthode
- Les définitions de classe
- Les limites de module
- Les blocs de commentaires (docstrings)
Étape 3 : Générer les embeddings
Passer les chunks à travers un modèle d'embeddings (exemple ONNX local) :
1import { InferenceSession, Tensor } from 'onnxruntime-node';23async function embedCode(code: string): Promise<number[]> {4 // Charger le modèle5 const session = await InferenceSession.create('code-embedding-model.onnx');67 // Tokeniser8 const tokens = tokenize(code); // [101, 2339, 2129, ...]9 const inputTensor = new Tensor('int64', tokens, [1, tokens.length]);1011 // Exécuter l'inférence12 const outputs = await session.run({ input_ids: inputTensor });13 const embedding = outputs.last_hidden_state.data;1415 // Mean pooling16 return meanPool(embedding); // [0.234, -0.456, ...] (768 dims)17}Étape 4 : Stocker dans une base de données vectorielle
Indexer les embeddings pour une recherche de similarité rapide :
1-- Utiliser SQLite avec extension vectorielle (FTS5 + similarité custom)2CREATE VIRTUAL TABLE code_embeddings USING vec0(3 chunk_id TEXT PRIMARY KEY,4 file_path TEXT,5 code TEXT,6 embedding FLOAT[768]7);89INSERT INTO code_embeddings VALUES (10 'func_123',11 'src/utils/math.ts',12 'function calculateDiscount(...)',13 vec_f32('[0.234, -0.456, ...]')14);Étape 5 : Requête avec similarité cosinus
Rechercher du code sémantiquement similaire :
1async function searchSimilarCode(query: string, topK = 10) {2 // Embedder la requête3 const queryEmbedding = await embedCode(query);45 // Trouver les voisins les plus proches par similarité cosinus6 const results = await db.query(`7 SELECT8 chunk_id,9 file_path,10 code,11 vec_cosine_distance(embedding, ?) as similarity12 FROM code_embeddings13 ORDER BY similarity DESC14 LIMIT ?15 `, [queryEmbedding, topK]);1617 return results.filter(r => r.similarity > 0.7); // Seuil18}Similarité cosinus : trouver du code lié
La similarité cosinus mesure l'angle entre deux vecteurs, allant de -1 (opposé) à 1 (identique). Pour les embeddings de code, c'est la métrique de référence.
Les mathématiques (simplifiées)
Étant donné deux vecteurs d'embeddings A et B :
1cosine_similarity(A, B) = (A · B) / (||A|| × ||B||)23Où :4- A · B = produit scalaire (somme de la multiplication élément par élément)5- ||A|| = magnitude de A (racine carrée de la somme des carrés)Analogie visuelle
Imaginez deux flèches dans l'espace 3D :
- Même direction (flèches parallèles) : similarité = 1.0
- Perpendiculaires (angle de 90°) : similarité = 0.0
- Direction opposée (angle de 180°) : similarité = -1.0
Dans un espace à 768 dimensions, le même principe s'applique. Le code similaire "pointe" dans des directions sémantiques similaires.
Seuils pratiques
D'après des systèmes réels de recherche de code :
| Similarité | Interprétation | Cas d'usage |
|---|---|---|
| 0.95-1.0 | Quasi-duplicatas | Détecter le code copié-collé |
| 0.85-0.95 | Hautement lié | Trouver des implémentations alternatives |
| 0.75-0.85 | Lié | Découvrir des patterns similaires |
| 0.65-0.75 | Vaguement lié | Explorer des concepts connexes |
| < 0.65 | Non lié | Filtrer le bruit |
Pourquoi c'est meilleur que la recherche par mots-clés
1// Requête : "valider adresse email"23// Correspondance par mots-clés : FAIBLE (mots différents)4function checkEmailFormat(str: string): boolean {5 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);6}78// Similarité cosinus : 0.87 (FORTE)9// Le modèle comprend : validation regex + pattern email = validation emailLes embeddings capturent l'intention et le sens, pas seulement le chevauchement de tokens.
Principaux modèles d'embeddings de code en 2026
Les modèles d'embeddings de code se sont beaucoup améliorés ces deux dernières années. Voici une comparaison des principales options :
| Modèle | Dimensions | Longueur contexte | Déploiement | Meilleur pour |
|---|---|---|---|---|
| Voyage-3-large | 2048 (flexible: 256-2048) | 32K tokens | API (Cloud) | Précision maximale, grands contextes |
| OpenAI text-embedding-3-large | 3072 (flexible: 256-3072) | 8K tokens | API (Cloud) | Code + docs généraux, haute qualité |
| EmbeddingGemma (308M) | 768 (flexible: 128-768) | 2K tokens | ONNX local | Confidentialité, on-device, inférence rapide |
| Nomic Embed v1.5 | 768 (flexible: 64-768) | 8K tokens | ONNX local | Open source, reproductible |
| StarEncoder | 768 | 1K tokens | Local | Natif code, 86 langages |
| CodeBERT | 768 | 512 tokens | Local | Legacy, contextes plus courts |
Choisir le bon modèle
Pour une précision maximale (cloud acceptable) :
- Voyage-3-large ou OpenAI text-embedding-3-large
- Meilleure compréhension sémantique, supporte les longs fichiers
Pour la confidentialité et le déploiement local :
- EmbeddingGemma ou Nomic Embed
- Exécution entièrement hors ligne avec ONNX Runtime
- C'est l'approche de Semantiq
Pour des tâches spécialisées de code :
- StarCoder embeddings pour les codebases multi-langages
- Fine-tuning sur votre domaine (sécurité, web, systèmes)
Considérations de performance :
- Embedding de 1000 fonctions :
- API Cloud : ~2-5 secondes (parallélisé)
- ONNX local (CPU) : ~15-30 secondes
- ONNX local (GPU) : ~3-8 secondes
Comment Semantiq utilise les embeddings
Semantiq utilise les vector embeddings dans le cadre d'une stratégie de recherche hybride, combinant compréhension sémantique et recherche textuelle traditionnelle pour des résultats optimaux.
Inférence locale avec ONNX
Semantiq exécute les modèles d'embeddings entièrement sur votre machine avec ONNX Runtime :
1// Architecture simplifiée2class SemanticIndexer {3 private model: InferenceSession;4 private vectorDB: VectorStore;56 async initialize() {7 // Charger le modèle ONNX optimisé (~300MB)8 this.model = await InferenceSession.create(9 'models/embedding-gemma-308m.onnx',10 { executionProviders: ['cuda', 'cpu'] } // GPU si disponible11 );12 }1314 async indexRepository(repoPath: string) {15 // Parser avec Tree-sitter16 const chunks = await parseCodeStructure(repoPath);1718 // Embedding par lot (efficace)19 const embeddings = await this.batchEmbed(chunks);2021 // Stocker localement (SQLite + FTS5)22 await this.vectorDB.insert(chunks, embeddings);23 }24}Avantages :
- Aucune donnée ne quitte votre machine
- Fonctionne hors ligne
- Pas de coûts d'API
- Inférence locale rapide (~5ms par chunk sur CPU moderne)
Stratégie de recherche hybride
Semantiq ne repose pas uniquement sur les embeddings. Il combine :
- Recherche vectorielle (sémantique) : Trouver du code conceptuellement similaire
- Ripgrep (exact) : Matcher des patterns et symboles précis
- FTS5 (texte intégral) : Indexer les identifiants et commentaires
1async function hybridSearch(query: string) {2 const [semanticResults, exactResults, textResults] = await Promise.all([3 vectorSearch(query), // Similarité d'embeddings4 ripgrepSearch(query), // Correspondances regex + littérales5 fts5Search(query) // Recherche textuelle basée sur tokens6 ]);78 // Fusionner et classer par score combiné9 return mergeResults(semanticResults, exactResults, textResults);10}Cette approche hybride permet :
- Rappel : Les embeddings trouvent du code sémantiquement lié que vous manqueriez avec des mots-clés
- Précision : La recherche exacte élimine les faux positifs
- Vitesse : FTS5 fournit une recherche instantanée d'identifiants
Seuils ML adaptatifs
Semantiq apprend les seuils de similarité optimaux en fonction de votre codebase :
1// Analyser la structure de la codebase2const stats = analyzeCodebase(repo);34// Ajuster les seuils en fonction de :5// - Niveau de duplication (haute dup → augmenter le seuil)6// - Diversité de langages (multi-lang → réduire le seuil)7// - Taille du projet (grand → filtrage plus strict)89const threshold = baseThreshold * (1 + stats.duplicationFactor * 0.3)10 * (1 - stats.languageDiversity * 0.2);Les petites codebases ciblées utilisent des seuils plus bas (0.65) pour afficher plus de résultats. Les grands monorepos utilisent des seuils plus élevés (0.80) pour filtrer le bruit.
Applications pratiques
Les vector embeddings permettent plusieurs fonctionnalités d'intelligence de code :
1. Recherche sémantique de code
Trouver des fonctions en décrivant ce qu'elles font :
1Requête : "parser un token JWT et extraire les claims utilisateur"23Résultats (même si aucun mot-clé ne correspond) :4- decodeAuthToken(token: string): UserClaims5- extractJWTPayload(jwt: string): Claims6- parseBearer(authHeader: string): TokenData2. Détection de duplications
Trouver du code copié-collé ou réimplémenté :
1// Original2function calculateShipping(weight: number, distance: number) {3 const baseRate = 5.0;4 return baseRate + (weight * 0.5) + (distance * 0.1);5}67// Duplication détectée (similarité 0.94)8function getDeliveryCost(kg: number, km: number) {9 const base = 5.0;10 return base + kg * 0.5 + km * 0.1;11}3. Suggestions de refactoring
Identifier les candidats à l'extraction :
1Cluster de haute similarité détecté :2- processUserData() [similarité : 0.92]3- handleCustomerInfo() [similarité : 0.91]4- transformAccountDetails() [similarité : 0.90]56Suggestion : Extraire le pattern commun dans un utilitaire partagé4. Détection de vulnérabilités
Trouver des patterns similaires à des vulnérabilités connues :
1// Pattern d'injection SQL connu (dans les données d'entraînement)2const query = "SELECT * FROM users WHERE id = " + userId;34// Pattern similaire détecté (similarité 0.88)5const sql = `DELETE FROM sessions WHERE user = ${userInput}`;6// ↑ Signalé comme risqué5. Clustering de codebase
Visualiser l'organisation du code et identifier les frontières architecturales :
1Cluster 1 (Authentification) : 45 fonctions, similarité moy. 0.822Cluster 2 (Accès base de données) : 67 fonctions, similarité moy. 0.793Cluster 3 (Handlers API) : 89 fonctions, similarité moy. 0.7545Valeurs aberrantes : 12 fonctions avec faible similarité de cluster6→ Candidats potentiels pour refactoring ou meilleure organisationConstruire le vôtre : un exemple minimal
Voici un exemple éducatif simplifié en TypeScript montrant comment construire un système basique de recherche de code par embeddings :
1// minimal-code-search.ts2import { InferenceSession, Tensor } from 'onnxruntime-node';3import Database from 'better-sqlite3';45// Tokenizer simple (vrais systèmes utilisent des tokenizers appropriés comme tiktoken)6function tokenize(code: string): number[] {7 // Simplifié : découper sur les espaces et mapper vers des IDs de tokens8 const words = code.toLowerCase().split(/\s+/);9 return words.map(w => w.charCodeAt(0) % 1000); // Mapping factice10}1112// Charger le modèle d'embeddings13async function createEmbedder(modelPath: string) {14 const session = await InferenceSession.create(modelPath);1516 return async (code: string): Promise<number[]> => {17 const tokens = tokenize(code);18 const inputTensor = new Tensor('int64',19 new BigInt64Array(tokens.map(t => BigInt(t))),20 [1, tokens.length]21 );2223 const outputs = await session.run({ input_ids: inputTensor });24 const embedding = Array.from(outputs.last_hidden_state.data);2526 // Mean pooling27 const dims = 768;28 const pooled = new Array(dims).fill(0);29 for (let i = 0; i < embedding.length; i++) {30 pooled[i % dims] += embedding[i];31 }32 return pooled.map(x => x / (embedding.length / dims));33 };34}3536// Base de données vectorielle (SQLite)37class VectorDB {38 private db: Database.Database;3940 constructor(dbPath: string) {41 this.db = new Database(dbPath);42 this.db.exec(`43 CREATE TABLE IF NOT EXISTS embeddings (44 id INTEGER PRIMARY KEY,45 code TEXT,46 embedding BLOB47 )48 `);49 }5051 insert(code: string, embedding: number[]) {52 const blob = Buffer.from(new Float32Array(embedding).buffer);53 this.db.prepare('INSERT INTO embeddings (code, embedding) VALUES (?, ?)')54 .run(code, blob);55 }5657 search(queryEmbedding: number[], topK = 5) {58 const rows = this.db.prepare('SELECT id, code, embedding FROM embeddings').all();5960 const results = rows.map(row => ({61 code: row.code,62 similarity: cosineSimilarity(63 queryEmbedding,64 Array.from(new Float32Array(row.embedding.buffer))65 )66 }));6768 return results69 .sort((a, b) => b.similarity - a.similarity)70 .slice(0, topK);71 }72}7374// Similarité cosinus75function cosineSimilarity(a: number[], b: number[]): number {76 let dotProduct = 0, magA = 0, magB = 0;77 for (let i = 0; i < a.length; i++) {78 dotProduct += a[i] * b[i];79 magA += a[i] * a[i];80 magB += b[i] * b[i];81 }82 return dotProduct / (Math.sqrt(magA) * Math.sqrt(magB));83}8485// Utilisation86async function main() {87 const embed = await createEmbedder('model.onnx');88 const db = new VectorDB('code.db');8990 // Indexer du code91 const snippets = [92 'function sort(arr) { return arr.sort(); }',93 'const sum = (nums) => nums.reduce((a,b) => a+b, 0);',94 'function multiply(a, b) { return a * b; }'95 ];9697 for (const code of snippets) {98 const embedding = await embed(code);99 db.insert(code, embedding);100 }101102 // Rechercher103 const query = 'additionner des nombres';104 const queryEmbedding = await embed(query);105 const results = db.search(queryEmbedding);106107 console.log('Résultats pour :', query);108 results.forEach(r =>109 console.log(`${r.similarity.toFixed(3)} : ${r.code}`)110 );111}112113main();Cet exemple démontre les concepts de base. Les systèmes de production ajoutent :
- Tokenisation appropriée (WordPiece, BPE)
- Traitement par lots pour l'efficacité
- Mises à jour d'index incrémentales
- Stores vectoriels avancés (FAISS, Milvus)
- Optimisation des requêtes
L'avenir des embeddings de code
Voici ce qui se profile :
Compréhension de code multi-modale
Les futurs modèles embedderont non seulement le code, mais aussi :
- Diagrammes d'architecture : Relier les UML aux implémentations
- Documentation : Lier les explications en prose au code
- Traces d'exécution : Connecter le comportement au source
- Messages de commit : Comprendre l'intention et l'évolution
1Requête : "flux d'authentification avec OAuth"23Résultats :4- Code : OAuthHandler.authenticate()5- Diagramme : auth-flow.png (diagramme de séquence)6- Docs : "Guide d'intégration OAuth" (page 12)7- Test : test_oauth_flow.tsCompréhension cross-repository
Imaginez des embeddings qui couvrent :
- Votre codebase + dépendances
- Bibliothèques publiques sur npm/PyPI
- Solutions Stack Overflow
- Exemples de code GitHub
Recherchez "middleware de limitation de débit" et trouvez :
- Votre implémentation existante
- Package Express.js rate-limiter
- Patterns similaires dans d'autres repos
- Réponses Stack Overflow associées
Tout classé et comparé sémantiquement.
Mises à jour incrémentales en temps réel
Les systèmes actuels réindexent des codebases entières. Les futurs systèmes :
- Embedderont les fichiers pendant que vous les éditez (< 50ms de latence)
- Mettront à jour seulement les fonctions modifiées
- Maintiendront la cohérence à travers les refactorings
- Propageront les changements à travers les graphes de dépendances
Modèles de domaine spécialisés
Nous verrons des modèles d'embeddings affinés pour :
- Sécurité : Reconnaître les patterns de vulnérabilités
- Performance : Identifier les opportunités d'optimisation
- Tests : Suggérer des cas de test basés sur la couverture de code
- Migration : Mapper les APIs dépréciées vers des équivalents modernes
Modèles plus petits et plus rapides
La tendance vers l'edge computing va pousser :
- Modèles < 100MB tournant sur des laptops
- Accélération matérielle (NPU, GPU)
- Quantification en 4-bit et 8-bit
- Embeddings générés < 1ms par fonction
Conclusion
Les vector embeddings ont changé la façon dont l'IA comprend le code. En convertissant la syntaxe en sémantique, ils permettent des systèmes de recherche qui saisissent l'intention, trouvent des patterns à travers les langages et révèlent des insights impossibles avec la recherche par mots-clés.
Que vous construisiez des outils de recherche de code, analysiez des vulnérabilités de sécurité ou exploriez une nouvelle codebase, les embeddings fournissent une base pour une compréhension intelligente du code. Des outils comme Semantiq apportent cette technologie sur votre machine locale—pas de cloud requis, pas de données partagées, juste une recherche de code sémantique rapide.
Essayez Semantiq pour expérimenter la recherche sémantique de code alimentée par des vector embeddings locaux. En savoir plus →