Aller au contenu principal

Cas d'Usage : Code Embeddings

Mis à jour le 29 juillet 2026

Au-delà de la recherche

Les embeddings de code ne servent pas uniquement à la recherche sémantique. Ils ouvrent la porte à des applications avancées : analytics de répertoire, clustering, détection de doublons, et assistants de développement. Dans cette leçon, vous allez explorer ces cas d’usage avec du code Python concret.

Le point commun de ces quatre usages est qu’ils exploitent la géométrie de l’index plutôt qu’une requête. Une fois vos fichiers placés dans l’espace vectoriel, leurs positions relatives racontent quelque chose du projet lui-même : ce qui se ressemble, ce qui se répète, ce qui détonne.

Analytics de répertoire

Analysez la structure sémantique d’un projet pour comprendre comment le code est organisé. K-Means regroupe les fichiers en cinq familles selon leur proximité vectorielle, sans jamais regarder l’arborescence des dossiers. Le résultat est souvent instructif : sur un projet mal rangé, vous verrez des fichiers issus de répertoires très différents atterrir dans le même cluster parce qu’ils traitent en réalité du même sujet.

import numpy as np
from sklearn.cluster import KMeans
from mistralai import Mistral

client = Mistral(api_key=api_key)

def analyze_repo(code_files, n_clusters=5):
    """Analyser la structure sémantique d'un répertoire."""
    # Encoder tous les fichiers
    inputs = [f["content"][:3000] for f in code_files]
    response = client.embeddings.create(
        model="codestral-embed",
        inputs=inputs,
    )
    embeddings = np.array([d.embedding for d in response.data])

    # Clustering K-Means
    kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
    labels = kmeans.fit_predict(embeddings)

    # Regrouper les fichiers par cluster
    clusters = {}
    for i, label in enumerate(labels):
        if label not in clusters:
            clusters[label] = []
        clusters[label].append(code_files[i]["path"])

    return clusters

# Utilisation
clusters = analyze_repo(code_files, n_clusters=5)
for cluster_id, files in clusters.items():
    print(f"\nCluster {cluster_id} ({len(files)} fichiers):")
    for f in files[:5]:
        print(f"  - {f}")

Le random_state=42 fixe l’initialisation : sans lui, deux exécutions successives donneraient des numéros de clusters différents et vous ne pourriez pas comparer deux analyses dans le temps.

Détection de doublons

Identifiez le code dupliqué ou quasi-dupliqué dans votre base. Contrairement à un outil de détection classique qui compare les textes, cette approche repère aussi les duplications réécrites — le fichier copié d’un module à l’autre puis renommé, avec des variables différentes et la même logique.

from sklearn.metrics.pairwise import cosine_similarity

def find_duplicates(code_files, embeddings, threshold=0.95):
    """Trouver les paires de fichiers quasi-identiques."""
    sim_matrix = cosine_similarity(embeddings)
    duplicates = []

    for i in range(len(code_files)):
        for j in range(i + 1, len(code_files)):
            if sim_matrix[i][j] >= threshold:
                duplicates.append({
                    "file_a": code_files[i]["path"],
                    "file_b": code_files[j]["path"],
                    "similarity": float(sim_matrix[i][j]),
                })

    # Trier par similarité décroissante
    duplicates.sort(key=lambda x: x["similarity"], reverse=True)
    return duplicates

# Trouver les doublons
dupes = find_duplicates(code_files, embeddings, threshold=0.92)
print(f"{len(dupes)} paires de doublons potentiels trouvées:")
for d in dupes[:10]:
    print(f"  {d['similarity']:.3f} : {d['file_a']} <-> {d['file_b']}")

La boucle interne démarre à i + 1 pour ne comparer chaque paire qu’une seule fois. Attention toutefois au coût : la matrice de similarité est quadratique, et au-delà de quelques milliers de fichiers vous devrez passer par un index approximatif plutôt que par un calcul exhaustif. Descendre le seuil de 0,95 à 0,92 élargit la pêche aux duplications remaniées, au prix de quelques faux positifs à trier à la main.

Clustering par fonctionnalité

K-Means impose de connaître le nombre de groupes à l’avance, ce qui est rarement le cas quand on découvre un projet. DBSCAN répond à cette limite : il détermine lui-même le nombre de groupes et, surtout, il assume qu’un fichier puisse n’appartenir à aucun — c’est le sens de l’étiquette « Non classé » dans le code ci-dessous.

from sklearn.cluster import DBSCAN

def cluster_by_functionality(code_files, embeddings, eps=0.3, min_samples=2):
    """Regrouper le code par fonctionnalité avec DBSCAN."""
    # DBSCAN sur les distances cosinus
    distance_matrix = 1 - cosine_similarity(embeddings)
    clustering = DBSCAN(eps=eps, min_samples=min_samples, metric="precomputed")
    labels = clustering.fit_predict(distance_matrix)

    # Organiser par cluster
    groups = {}
    for i, label in enumerate(labels):
        group_name = f"Groupe {label}" if label >= 0 else "Non classé"
        if group_name not in groups:
            groups[group_name] = []
        groups[group_name].append(code_files[i]["path"])

    return groups

groups = cluster_by_functionality(code_files, embeddings)
for name, files in groups.items():
    print(f"\n{name} ({len(files)} fichiers):")
    for f in files[:3]:
        print(f"  - {f}")

Le passage par 1 - cosine_similarity convertit une similarité en distance, format qu’attend DBSCAN avec metric="precomputed". Le paramètre eps=0.3 signifie donc que deux fichiers sont voisins à partir d’une similarité cosinus de 0,7.

Assistant de code contextuel

Ce dernier exemple assemble les briques précédentes en un véritable RAG sur le code : recherche des fichiers pertinents, construction d’un contexte, puis appel au LLM. Le seuil à 0,5 fait le tri en amont — mieux vaut donner deux fichiers vraiment liés à la question que trois, dont un hors sujet qui égarera le modèle.

def code_assistant(question, code_files, embeddings, top_k=3):
    """Trouver le code pertinent pour répondre à une question."""
    # Encoder la question
    response = client.embeddings.create(
        model="codestral-embed",
        inputs=[question],
    )
    query_emb = np.array(response.data[0].embedding)

    # Trouver le code le plus pertinent
    sims = cosine_similarity([query_emb], embeddings)[0]
    top_indices = np.argsort(sims)[::-1][:top_k]

    context_parts = []
    for idx in top_indices:
        if sims[idx] > 0.5:  # Seuil minimal
            context_parts.append(
                f"# Fichier: {code_files[idx]['path']}\n"
                f"{code_files[idx]['content'][:1500]}"
            )

    context = "\n\n---\n\n".join(context_parts)

    # Construire le prompt pour le LLM
    prompt = f"""Voici du code pertinent de notre projet :

{context}

Question du développeur : {question}

Répondez en vous basant sur le code ci-dessus."""

    # Appeler le LLM avec le contexte
    chat_response = client.chat.complete(
        model="mistral-large-latest",
        messages=[{"role": "user", "content": prompt}],
    )

    return chat_response.choices[0].message.content

# Utilisation
answer = code_assistant(
    "Comment fonctionne l'authentification dans ce projet ?",
    code_files,
    embeddings,
)
print(answer)

Le chemin du fichier est injecté en commentaire au-dessus de chaque extrait. Ce n’est pas décoratif : le modèle s’en sert pour citer ses sources, et le développeur qui lit la réponse sait immédiatement où aller vérifier.

Métriques de qualité de code

Les embeddings servent enfin à repérer ce qui sort de l’ordinaire. La fonction calcule le centroïde de l’index — le « fichier moyen » du projet — puis mesure l’éloignement de chacun. Un z-score supérieur à 2 signale un fichier qui ne ressemble à rien d’autre : parfois un morceau de code généré ou copié depuis l’extérieur, parfois simplement le seul fichier écrit dans un style ancien, et dans les deux cas un bon candidat à la revue.

def detect_outliers(code_files, embeddings, threshold=2.0):
    """Détecter les fichiers de code atypiques."""
    # Calculer le centroïde (vecteur moyen)
    centroid = embeddings.mean(axis=0)

    # Distance de chaque fichier au centroïde
    distances = np.linalg.norm(embeddings - centroid, axis=1)

    # Fichiers au-delà de N écarts-types
    mean_dist = distances.mean()
    std_dist = distances.std()

    outliers = []
    for i, dist in enumerate(distances):
        z_score = (dist - mean_dist) / std_dist
        if z_score > threshold:
            outliers.append({
                "path": code_files[i]["path"],
                "z_score": float(z_score),
            })

    outliers.sort(key=lambda x: x["z_score"], reverse=True)
    return outliers

outliers = detect_outliers(code_files, embeddings)
print(f"{len(outliers)} fichiers atypiques:")
for o in outliers:
    print(f"  z={o['z_score']:.2f} : {o['path']}")

Retenez que « atypique » ne veut pas dire « mauvais ». L’outil vous rend une liste de fichiers à regarder, pas un verdict : c’est un point de départ pour une revue humaine, jamais une métrique à afficher dans un tableau de bord de qualité.

Points clés à retenir

  • Les embeddings de code permettent le clustering automatique par fonctionnalité
  • La détection de doublons utilise un seuil de similarité cosinus (0.92-0.95)
  • Un assistant de code combine la recherche sémantique avec un LLM pour du RAG sur le code
  • Les fichiers atypiques (outliers) peuvent signaler du code à réviser
  • DBSCAN est préférable à K-Means quand le nombre de groupes est inconnu