Ricerca vettoriale nel provider EF Core SQL Server

Annotazioni

Il supporto dei vettori è stato introdotto in EF Core 10.0 ed è supportato solo con SQL Server 2025 e versioni successive.

Il tipo di dati vettoriale SQL Server consente di archiviare embedding, che sono rappresentazioni di significato che possono essere ricercate in modo efficiente per ottenere somiglianze, alimentando carichi di lavoro di intelligenza artificiale come la ricerca semantica e la generazione aumentata di recupero (RAG).

Impostazione delle proprietà vettoriali

Per usare il tipo di dati vector, è sufficiente aggiungere una proprietà .NET di tipo SqlVector<float> al tipo di entità, specificando le dimensioni come indicato di seguito:

public class Blog
{
    // ...

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

Dopo aver aggiunto la proprietà e la colonna corrispondente creata nel database, è possibile iniziare a inserire incorporamenti. La generazione di incorporamento viene eseguita all'esterno del database, in genere tramite un servizio e i dettagli per questa operazione non rientrano nell'ambito di questa documentazione. Tuttavia, la libreria .NET Microsoft.Extensions.AI contiene IEmbeddingGenerator, che è un'astrazione sui generatori di incorporamento con implementazioni per i principali provider.

Dopo aver scelto il generatore di incorporamento e configurarlo, usarlo per generare incorporamenti e inserirli nel modo seguente:

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();

Dopo aver salvato gli incorporamenti nel database, è possibile eseguire ricerche di somiglianza vettoriale su di esse.

Annotazioni

A partire da EF Core 11, le proprietà vettoriali non vengono caricate per impostazione predefinita durante l'esecuzione di query sulle entità, poiché i vettori sono in genere di grandi dimensioni e raramente devono essere rilette. Prima di EF Core 11, le proprietà vettoriali venivano sempre caricate come qualsiasi altra proprietà.

Ricerca esatta con VECTOR_DISTANCE()

La EF.Functions.VectorDistance() funzione calcola la distanza esatta tra due vettori. Usarlo per eseguire una ricerca di somiglianza per una determinata query utente:

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();

Questa funzione calcola la distanza tra il vettore di query e ogni riga della tabella, quindi restituisce le corrispondenze più vicine. Anche se offre risultati perfettamente accurati, può essere lento per set di dati di grandi dimensioni perché SQL Server deve analizzare tutte le righe e le distanze di calcolo per ognuna di esse.

Annotazioni

Il supporto predefinito in EF 10 sostituisce l'estensione EFCore.SqlServer.VectorSearch precedente, che consentiva l'esecuzione della ricerca vettoriale prima dell'introduzione del vector tipo di dati. Come parte dell'aggiornamento a EF 10, rimuovere l'estensione dai progetti.

Avviso

VECTOR_SEARCH() e gli indici vettoriali sono attualmente funzionalità sperimentali in SQL Server e sono soggette a modifiche. Anche le API in EF Core per queste funzionalità sono soggette a modifiche.

La funzione VECTOR_SEARCH() con valori di tabella di SQL Server recupera le righe in base alla similarità vettoriale. A differenza VECTOR_DISTANCE() di , che calcola la distanza tra due vettori specifici, VECTOR_SEARCH() cerca in un'intera tabella i vettori più simili a un determinato vettore di query.

Usa il metodo di estensione VectorSearch() su DbSet e concatena OrderBy(), Take() e WithApproximate() per eseguire una ricerca approssimativa del vicino più prossimo (ANN) che utilizza un indice vettoriale:

var results = await context.Blogs
    .VectorSearch(b => b.Embedding, embedding, "cosine")
    .OrderBy(r => r.Distance)
    .Take(5)
    .WithApproximate()
    .ToListAsync();

foreach (var result in results)
{
    Console.WriteLine($"Blog {result.Value.Id} with distance {result.Distance}");
}

Questo comportamento si traduce nel codice SQL seguente:

SELECT TOP(@__p_1) WITH APPROXIMATE [b].[Id], [b].[Name], [v].[Distance]
FROM VECTOR_SEARCH(
    TABLE = [Blogs] AS [b],
    COLUMN = [Embedding],
    SIMILAR_TO = @__embedding_0,
    METRIC = 'cosine'
) AS [v]
ORDER BY [v].[Distance]

VectorSearch() restituisce VectorSearchResult<TEntity>, che consente di accedere sia all'entità che alla distanza calcolata:

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

In questo modo è possibile filtrare in base al punteggio di somiglianza, presentarlo agli utenti e così via.

WithApproximate()

WithApproximate() indica a SQL Server di utilizzare l'indice vettoriale per la ricerca approssimata del vicino più prossimo (ANN), che offre prestazioni significativamente migliori per set di dati di grandi dimensioni. Fa sì che WITH APPROXIMATE venga aggiunto alla clausola SQL TOP. WithApproximate() deve essere chiamato dopo Take(), che specifica il numero di risultati da restituire.

Senza WithApproximate(), la query esegue una ricerca k-nearest neighbor (kNN) esatta che analizza tutte le righe, senza usare l'indice vettoriale:

// Exact kNN search (no vector index used)
var blogs = await context.Blogs
    .VectorSearch(b => b.Embedding, embedding, "cosine")
    .OrderBy(r => r.Distance)
    .Take(5)
    .ToListAsync();

Indici vettoriali

Per usare la ricerca approssimativa con WithApproximate(), è necessario creare un indice vettoriale nella colonna vettoriale. Usare il HasVectorIndex() metodo nella configurazione del modello:

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

Verrà generata la migrazione SQL seguente:

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

Per gli indici vettoriali sono supportate le metriche di distanza seguenti:

Metrica Descrizione
cosine Somiglianza coseno (distanza angolare)
euclidean Distanza euclidea (norma L2)
dot Prodotto punto (prodotto interno negativo)

Scegliere la metrica più adatta al modello di incorporamento e al caso d'uso. La somiglianza del coseno viene comunemente usata per gli incorporamenti di testo, mentre la distanza euclidea viene spesso usata per incorporamenti di immagini.

La ricerca ibrida combina la ricerca di somiglianza vettoriale con la ricerca full-text tradizionale per fornire risultati più pertinenti. La ricerca vettoriale è eccellente nel trovare contenuti semanticamente simili, mentre la ricerca full-text è migliore per la corrispondenza esatta con le parole chiave. Combinando entrambi gli approcci e usando L'RRF (Reciprocal Rank Fusion) per unire i risultati, è possibile creare esperienze di ricerca più intelligenti.

L'esempio seguente illustra come implementare la ricerca ibrida usando EF Core, combinando FreeTextTable() e VectorSearch() in una singola query:

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")
            .OrderBy(r => r.Distance)
            .Take(k)
            .WithApproximate(),
        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();

Questa query:

  1. Esegue una ricerca a testo completo in Article
  2. Esegue una ricerca vettoriale su Article e combina i risultati ai risultati della ricerca full-text tramite LEFT JOIN
  3. Calcola il punteggio RRF combinando sia il testo completo che la classificazione semantica
  4. Ordina per punteggio RRF, prende il numero desiderato di risultati e proietta le entità originali Article.

Annotazioni

Invece di usare un LEFT JOIN, un FULL OUTER JOIN sarebbe più adatto per questo scenario; in questo modo i risultati con classificazione elevata da entrambi i lati della ricerca possono essere inclusi nel risultato finale, anche se tale risultato non appare affatto dall'altro lato. Con l'approccio LEFT JOIN precedente, se un risultato ha un punteggio di somiglianza vettoriale molto elevato, non viene mai incluso nel risultato finale se tale risultato non ha anche un punteggio full-text elevato. EF attualmente non supporta FULL OUTER JOIN; metti un voto positivo su #37633 se desideri che venga supportato.

La query produce il codice SQL seguente:

SELECT TOP(@__p_4) [a0].[Id], [a0].[Content], [a0].[Title]
FROM FREETEXTTABLE([Articles], *, @__textualQuery_0, @__k_1) AS [f]
LEFT JOIN (
    SELECT TOP(@__k_1) WITH APPROXIMATE [a].[Id], [a].[Content], [a].[Title], [v].[Distance]
    FROM VECTOR_SEARCH(
        TABLE = [Articles] AS [a],
        COLUMN = [Embedding],
        SIMILAR_TO = @__queryEmbedding_2,
        METRIC = 'cosine'
    ) AS [v]
    ORDER BY [v].[Distance]
) AS [t] ON [f].[KEY] = [t].[Id]
ORDER BY 1.0E0 / CAST(@__k_1 + [f].[RANK] AS float) + ISNULL(1.0E0 / (CAST(@__k_1 AS float) + [t].[Distance]), 0.0E0) DESC