Partilhar via


Pesquisa vetorial no provedor SQL Server EF Core

Observação

O suporte a vetores foi introduzido no EF Core 10.0 e só é suportado com o SQL Server 2025 e superior.

O tipo de dados vetorial SQL Server permite armazenar representações embutidas, que são representações de significado que podem ser pesquisadas eficientemente por semelhança, alavancando cargas de trabalho de IA como pesquisa semântica e geração aumentada por recuperação (RAG).

Configuração das propriedades vetoriais

Para usar o vector tipo de dados, basta adicionar uma propriedade .NET do tipo SqlVector<float> ao seu tipo de entidade, especificando as dimensões da seguinte maneira:

public class Blog
{
    // ...

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

Depois que sua propriedade for adicionada e a coluna correspondente criada no banco de dados, você poderá começar a inserir incorporações. A geração de incorporação é feita fora do banco de dados, geralmente por meio de um serviço, e os detalhes para fazer isso estão fora do escopo desta documentação. No entanto, a biblioteca .NET Microsoft.Extensions.AI contém IEmbeddingGenerator, que é uma abstração sobre geradores de embedding e possui implementações para os principais fornecedores.

Depois de escolheres o teu gerador de embedding e o configurares, usa-o para gerar embeddings e insere-os da seguinte forma:

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

Depois de ter os embeddings guardados na sua base de dados, está pronto para realizar uma pesquisa vetorial por similaridade sobre eles.

Pesquisa exata com VECTOR_DISTANCE()

A EF.Functions.VectorDistance() função calcula a distância exata entre dois vetores. Use-o para realizar uma pesquisa de similaridade para uma determinada consulta de utilizador:

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

Esta função calcula a distância entre o vetor de consulta e cada linha da tabela, depois devolve as correspondências mais próximas. Embora isto forneça resultados perfeitamente precisos, pode ser lento para grandes conjuntos de dados porque o SQL Server tem de analisar todas as linhas e calcular distâncias para cada uma.

Observação

O suporte interno no EF 10 substitui a extensão EFCore.SqlServer.VectorSearch anterior, que permitia executar a pesquisa vetorial antes que o vector tipo de dados fosse introduzido. Como parte da atualização para o EF 10, remova a extensão dos seus projetos.

Advertência

VECTOR_SEARCH() e os índices vetoriais são atualmente funcionalidades experimentais no SQL Server e estão sujeitas a alterações. As APIs do EF Core para estas funcionalidades também estão sujeitas a alterações.

Para conjuntos de dados grandes, calcular distâncias exatas para cada linha pode ser proibitivamente lento. O SQL Server 2025 introduz suporte para pesquisa aproximada através de um índice vetorial, que oferece um desempenho muito melhor à custa de devolver itens que são aproximadamente semelhantes – em vez de exatamente semelhantes – à consulta.

Índices vetoriais

Para usar VECTOR_SEARCH(), deve criar um índice vetorial na sua coluna vetorial. Use o HasVectorIndex() método na configuração do seu modelo:

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

Isto irá gerar a seguinte migração SQL:

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

As seguintes métricas de distância são suportadas para índices vetoriais:

Métrico Descrição
cosine Similaridade de cosseno (distância angular)
euclidean Distância euclidiana (norma L2)
dot Produto escalar (produto interno negativo)

Escolha a métrica que melhor se adequa ao seu modelo de embedding e ao seu caso de uso. A similaridade do cosseno é frequentemente utilizada para embeddings de texto, enquanto a distância euclidiana é geralmente usada para embeddings de imagem.

** Depois de ter um índice vetorial, use o método de extensão VectorSearch() para o seu 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}");
}

Isso se traduz no seguinte SQL:

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

O topN parâmetro especifica o número máximo de resultados a devolver.

VectorSearch() devolve VectorSearchResult<TEntity>, que permite aceder tanto à entidade como à distância calculada:

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

Isto permite-te filtrar pela pontuação de similaridade, apresentá-la aos utilizadores, etc.

A pesquisa híbrida combina a pesquisa por similaridade vetorial com a pesquisa tradicional em texto completo para fornecer resultados mais relevantes. A pesquisa vetorial destaca-se por encontrar conteúdos semanticamente semelhantes, enquanto a pesquisa em texto completo é melhor na correspondência exata de palavras-chave. Ao combinar ambas as abordagens e usar a Fusão de Classificação Recíproca (RRF) para fundir os resultados, pode construir experiências de pesquisa mais inteligentes.

O exemplo seguinte mostra como implementar pesquisa híbrida usando EF Core, combinando FreeTextTable() e VectorSearch() numa única consulta:

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

Esta consulta:

  1. Realiza uma pesquisa em texto completo em Article
  2. Realiza uma pesquisa vetorial em Article e combina os resultados com os resultados da pesquisa em texto completo através de um LEFT JOIN
  3. Calcula a pontuação RRF combinando tanto o texto completo como a classificação semântica
  4. Ordena por pontuação RRF, seleciona o número desejado de resultados e extrai as entidades originais Article.

Observação

Em vez de usar uma UNIÃO À ESQUERDA, uma UNIÃO EXTERIOR COMPLETA seria mais adequada para este cenário; Isto permitiria que resultados de alta classificação de qualquer um dos lados de pesquisa fossem incluídos no resultado final, mesmo que esse resultado não apareça de todo do outro lado. Com a abordagem LEFT JOIN acima, se um resultado tiver uma pontuação de similaridade vetorial muito alta, nunca é incluído no resultado final se esse resultado não tiver também uma pontuação alta no texto completo. No entanto, o EF atualmente não suporta FULL OUTER JOIN; vote a favor #37633 se gostaria de ver isso suportado.

A consulta produz o seguinte SQL:

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