Partager via


Recherche vectorielle dans le fournisseur EF Core SQL Server

Note

La prise en charge des vecteurs a été introduite dans EF Core 10.0 et est prise en charge uniquement avec SQL Server 2025 et versions ultérieures.

Le type de données vectorielles SQL Server permet de stocker des embeddings, qui sont des représentations de la signification pouvant être recherchées efficacement en fonction de la similarité, pour alimenter les charges de travail d'IA telles que la recherche sémantique et la génération augmentée par récupération (RAG).

Configuration des propriétés de vecteur

Pour utiliser le vector type de données, ajoutez simplement une propriété .NET de type SqlVector<float> à votre type d’entité, en spécifiant les dimensions comme suit :

public class Blog
{
    // ...

    [Column(TypeName = "vector(1536)")]
    public SqlVector<float> Embedding { get; set; }
}

Une fois votre propriété ajoutée et la colonne correspondante créée dans la base de données, vous pouvez commencer à insérer des incorporations. La génération d’incorporation est effectuée en dehors de la base de données, généralement par le biais d’un service, et les détails pour ce faire sont hors de portée pour cette documentation. Toutefois, la bibliothèque .NET Microsoft.Extensions.AI contient IEmbeddingGenerator, qui est une abstraction sur les générateurs d’incorporation qui ont des implémentations pour les principaux fournisseurs.

Une fois que vous avez choisi votre générateur d’incorporation et configuré, utilisez-le pour générer des incorporations et les insérer comme suit :

IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator = /* Set up your preferred embedding generator */;

var embedding = await embeddingGenerator.GenerateVectorAsync("Some text to be vectorized");
context.Blogs.Add(new Blog
{
    Name = "Some blog",
    Embedding = new SqlVector<float>(embedding)
});
await context.SaveChangesAsync();

Une fois que vous avez enregistré des incorporations dans votre base de données, vous êtes prêt à effectuer une recherche de similarité vectorielle sur celles-ci.

Recherche exacte avec VECTOR_DISTANCE()

La EF.Functions.VectorDistance() fonction calcule la distance exacte entre deux vecteurs. Utilisez-la pour effectuer une recherche de similarité pour une requête utilisateur donnée :

var sqlVector = new SqlVector<float>(await embeddingGenerator.GenerateVectorAsync("Some user query to be vectorized"));
var topSimilarBlogs = await context.Blogs
    .OrderBy(b => EF.Functions.VectorDistance("cosine", b.Embedding, sqlVector))
    .Take(3)
    .ToListAsync();

Cette fonction calcule la distance entre le vecteur de requête et chaque ligne de la table, puis retourne les correspondances les plus proches. Bien que cela fournit des résultats parfaitement précis, il peut être lent pour les jeux de données volumineux, car SQL Server doit analyser toutes les lignes et distances de calcul pour chacune d’elles.

Note

La prise en charge intégrée d’EF 10 remplace l’extension EFCore.SqlServer.VectorSearch précédente, qui a permis d’effectuer une recherche vectorielle avant l’introduction du vector type de données. Dans le cadre de la mise à niveau vers EF 10, supprimez l’extension de vos projets.

Avertissement

VECTOR_SEARCH() et les index vectoriels sont actuellement des fonctionnalités expérimentales dans SQL Server et sont susceptibles de changer. Les API dans EF Core pour ces fonctionnalités sont également susceptibles de changer.

Pour les jeux de données volumineux, le calcul des distances exactes pour chaque ligne peut être prohibitifment lent. SQL Server 2025 introduit la prise en charge de la recherche approximative par le biais d’un index vectoriel, qui offre de meilleures performances au détriment du retour d’éléments qui sont approximativement similaires , plutôt que exactement similaires à la requête.

Index vectoriels

Pour utiliser VECTOR_SEARCH(), vous devez créer un index vectoriel sur votre colonne vectorielle. Utilisez la méthode HasVectorIndex() dans la configuration de votre modèle :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasVectorIndex(b => b.Embedding, "cosine");
}

Cela génère la migration SQL suivante :

CREATE VECTOR INDEX [IX_Blogs_Embedding]
    ON [Blogs] ([Embedding])
    WITH (METRIC = COSINE)

Les métriques de distance suivantes sont prises en charge pour les index vectoriels :

Unité de mesure Description
cosine Similarité cosinus (distance angulaire)
euclidean Distance euclide (norme L2)
dot Produit dot (produit interne négatif)

Choisissez la métrique qui correspond le mieux à votre modèle d’incorporation et à votre cas d’usage. La similarité cosinus est couramment utilisée pour les incorporations de texte, tandis que la distance euclide est souvent utilisée pour les incorporations d’images.

Une fois que vous avez un index vectoriel, utilisez la méthode d’extension VectorSearch() sur votre DbSet:

var blogs = await context.Blogs
    .VectorSearch(b => b.Embedding, "cosine", embedding, topN: 5)
    .ToListAsync();

foreach (var (blog, score) in blogs)
{
    Console.WriteLine($"Blog {blog.Id} with score {score}");
}

Cela se traduit par le code SQL suivant :

SELECT [v].[Id], [v].[Embedding], [v].[Name]
FROM VECTOR_SEARCH([Blogs], 'Embedding', @__embedding, 'metric = cosine', @__topN)

Le topN paramètre spécifie le nombre maximal de résultats à retourner.

VectorSearch() retourne VectorSearchResult<TEntity>, qui vous permet d’accéder à la fois à l’entité et à la distance calculée :

var searchResults = await context.Blogs
    .VectorSearch(b => b.Embedding, "cosine", embedding, topN: 5)
    .Where(r => r.Distance < 0.05)
    .Select(r => new { Blog = r.Value, Distance = r.Distance })
    .ToListAsync();

Cela vous permet de filtrer le score de similarité, de le présenter aux utilisateurs, etc.

La recherche hybride combine la recherche de vecteurs de similarité avec la recherche en texte intégral classique pour fournir des résultats plus pertinents. La recherche vectorielle excelle dans la recherche de contenu sémantiquement similaire, tandis que la recherche en texte intégral est préférable à la correspondance exacte des mots clés. En combinant les deux approches et l’utilisation de la fusion de classement réciproque (RRF) pour fusionner les résultats, vous pouvez créer des expériences de recherche plus intelligentes.

L’exemple suivant montre comment implémenter la recherche hybride à l’aide d’EF Core, en combinant FreeTextTable() et VectorSearch() en une seule requête :

var k = 20;
string textualQuery = ...;
SqlVector<float> queryEmbedding = ...;

var results = await context.Articles
    // Perform full-text search
    .FreeTextTable<Article, int>(textualQuery, topN: k)
    // Perform vector (semantic) search, joining the results of both searches together
    .LeftJoin(
        context.Articles.VectorSearch(b => b.Embedding, queryEmbedding, "cosine", topN: k),
        fts => fts.Key,
        vs => vs.Value.Id,
        (fts, vs) => new
        {
            Article = vs.Value,
            FullTextRank = fts.Rank,
            VectorDistance = (double?)vs.Distance
        })
    // Apply Reciprocal Rank Fusion (RRF) to combine the results
    .Select(x => new
    {
        x.Article,
        RrfScore = (1.0 / (k + x.FullTextRank)) + (1.0 / (k + x.VectorDistance) ?? 0.0)
    })
    .OrderByDescending(x => x.RrfScore)
    .Take(10)
    .Select(x => x.Article)
    .ToListAsync();

Cette requête :

  1. Effectue une recherche en texte intégral sur Article
  2. Effectue une recherche vectorielle sur Article et combine les résultats aux résultats de recherche en texte intégral via une jointure LEFT JOIN
  3. Calcule le score RRF en combinant le texte intégral et le classement sémantique
  4. Trie par score RRF, prend le nombre souhaité de résultats et projette les entités d’origine Article.

Note

Au lieu d’utiliser une JOINTURE LEFT, une JOINTURE EXTERNE COMPLÈTE serait plus adaptée à ce scénario ; cela permettrait d’inclure les résultats hautement classés de l’un ou l’autre côté de la recherche dans le résultat final, même si ce résultat n’apparaît pas du tout de l’autre côté. Avec l’approche LEFT JOIN ci-dessus, si un résultat a un score de similarité vectorielle très élevé, il n’est jamais inclus dans le résultat final si ce résultat n’a pas également un score de texte intégral élevé. Toutefois, EF ne prend actuellement pas en charge FULL OUTER JOIN ; upvote #37633 si c’est quelque chose que vous souhaitez voir pris en charge.

La requête produit le code SQL suivant :

SELECT TOP(@p3) [a0].[Id], [a0].[Content], [a0].[Embedding], [a0].[Title]
FROM FREETEXTTABLE([Articles], *, @p, @p1) AS [f]
LEFT JOIN VECTOR_SEARCH(
    TABLE = [Articles] AS [a0],
    COLUMN = [Embedding],
    SIMILAR_TO = @p2,
    METRIC = 'cosine',
    TOP_N = @p3
) AS [v] ON [f].[KEY] = [a0].[Id]
ORDER BY 1.0E0 / CAST(10 + [f].[RANK] AS float) + ISNULL(1.0E0 / (10.0E0 + [v].[Distance]), 0.0E0) DESC