Condividi tramite


Ricerca vettoriale nel provider EF Core di 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 di SQL Server consente l'archiviazione di incorporamenti, ovvero rappresentazioni di significato che possono essere ricercate in modo efficiente per ottenere somiglianze, alimentando i carichi di lavoro di intelligenza artificiale, ad esempio la ricerca semantica e la generazione avanzata del recupero.

Impostazione delle proprietà vettoriali

Per utilizzare il tipo di dati vector, basta aggiungere una proprietà .NET di tipo SqlVector<float> al tipo di entità, specificando le dimensioni come segue:

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 di Microsoft.Extensions.AI .NET 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.

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. Sebbene ciò fornisca 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() Gli indici vettoriali sono attualmente funzionalità sperimentali in SQL Server e sono soggetti a modifiche. Anche le API in EF Core per queste funzionalità sono soggette a modifiche.

Per i set di dati di grandi dimensioni, l'elaborazione di distanze esatte per ogni riga può essere proibitivamente lenta. SQL Server 2025 introduce il supporto per la ricerca approssimativa tramite un indice vettoriale, che offre prestazioni molto migliori a scapito della restituzione di elementi approssimativamente simili, anziché esattamente simili, alla query.

Indici vettoriali

Per usare VECTOR_SEARCH(), è 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.

Dopo aver ottenuto un indice vettoriale, usare il VectorSearch() metodo di estensione per 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}");
}

Questo comportamento si traduce nel codice SQL seguente:

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

Il topN parametro specifica il numero massimo di risultati da restituire.

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

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

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

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

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(@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