Поиск векторных данных в поставщике SQL Server для EF Core

Замечание

Поддержка векторов была введена в EF Core 10.0 и поддерживается только с SQL Server 2025 и более поздних версий.

Тип векторных данных SQL Server позволяет хранить векторные представления, которые используется для представления смысла и могут эффективно искаться по схожести, что позволяет использовать задачи искусственного интеллекта, такие как семантический поиск и создание на основе дополненного извлечения (RAG).

Настройка свойств вектора

Чтобы использовать тип данных vector, просто добавьте свойство .NET типа SqlVector<float> в тип сущности, указав измерения следующим образом:

public class Blog
{
    // ...

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

После добавления свойства и создания соответствующего столбца в базе данных, вы можете начать вставку встраиваний. Создание внедрения выполняется за пределами базы данных, как правило, через службу, и сведения об этом отсутствуют в этой документации. Однако библиотека .NET Microsoft.Extensions.AI содержит IEmbeddingGenerator, которая представляет собой абстракцию поверх генераторов встраиваний и имеет реализации для основных поставщиков.

Выбрав генератор встраивания и настроив его, используйте его для создания встраиваний и вставки этих встраиваний следующим образом:

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

Как только векторные представления будут сохранены в вашу базу данных, вы сможете выполнить поиск по векторному сходству.

Замечание

Начиная с EF Core 11 свойства векторов по умолчанию не загружаются при запросе сущностей, так как векторы обычно большие и редко требуются для чтения обратно. До EF Core 11 свойства вектора всегда загружались, как и любое другое свойство.

Точный поиск с помощью VECTOR_DISTANCE()

Функция EF.Functions.VectorDistance() вычисляет точное расстояние между двумя векторами. Используйте его для выполнения поиска сходства для заданного запроса пользователя:

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

Эта функция вычисляет расстояние между вектором запроса и каждой строкой в таблице, а затем возвращает ближайшие совпадения. Хотя это обеспечивает совершенно точные результаты, для больших наборов данных это может быть медленно, так как SQL Server должен сканировать все строки и вычислять расстояния для каждой из них.

Замечание

Встроенная поддержка в EF 10 заменяет предыдущее расширение EFCore.SqlServer.VectorSearch , которое позволило выполнять векторный поиск до vector появления типа данных. В рамках обновления до EF 10 удалите расширение из проектов.

Предупреждение

VECTOR_SEARCH() и векторные индексы в настоящее время являются экспериментальными функциями в SQL Server и подлежат изменению. Api в EF Core для этих функций также могут быть изменены.

VECTOR_SEARCH() табличная функция SQL Server возвращает строки на основе сходства векторов. В отличие VECTOR_DISTANCE() от того, что вычисляет расстояние между двумя конкретными векторами, VECTOR_SEARCH() выполняет поиск всей таблицы для наиболее похожих векторов заданного вектора запроса.

Используйте метод расширения VectorSearch() в вашем DbSet и последовательно вызовите OrderBy(), Take() и WithApproximate(), чтобы выполнить поиск приближённых ближайших соседей (ANN) с использованием векторного индекса:

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}");
}

Это преобразуется в следующий SQL:

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() возвращает значение VectorSearchResult<TEntity>, позволяющее получить доступ как к сущности, так и к вычисляемому расстоянию:

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

Это позволяет отфильтровать оценку сходства, представить ее пользователям и т. д.

WithApproximate()

WithApproximate() указывает SQL Server использовать векторный индекс для поиска приблизительного ближайшего соседа (ANN), что обеспечивает значительно более высокую производительность для больших наборов данных. Это приводит к добавлению WITH APPROXIMATE в предложение SQL TOP. WithApproximate() должен вызываться после Take(), который указывает количество возвращаемых результатов.

Без WithApproximate() этого запрос выполняет точный поиск k ближайших соседей (kNN), при котором сканируются все строки без использования векторного индекса:

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

Векторные индексы

Чтобы использовать приблизительный поиск с WithApproximate(), необходимо создать векторный индекс для столбца с векторами. Используйте метод HasVectorIndex() в конфигурации вашей модели.

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

При этом будет создана следующая миграция SQL:

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

Для индексов векторов поддерживаются следующие метрики расстояния:

Единица измерения Описание
cosine Сходство косинуса (угловое расстояние)
euclidean Евклидово расстояние (L2-норма)
dot Dot product (отрицательный внутренний продукт)

Выберите метрику, которая лучше всего соответствует модели внедрения и варианту использования. Косинусное сходство часто используется для текстовых векторов, в то время как эвклидово расстояние часто используется для векторов изображений.

Гибридный поиск объединяет векторный поиск подобия с традиционным полнотекстовый поиск , чтобы обеспечить более релевантные результаты. Векторный поиск превосходит в нахождении семантически аналогичного содержимого, в то время как полнотекстовый поиск лучше подходит для точного совпадения с ключевыми словами. Сочетая оба подхода и используя слияние с взаимным ранжированием (RRF) для объединения результатов, вы можете создавать более интеллектуальные поисковые системы.

В следующем примере показано, как реализовать гибридный поиск с помощью EF Core, сочетая FreeTextTable() и VectorSearch() в одном запросе:

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

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

Этот запрос:

  1. Выполняет полнотекстовый поиск Article
  2. Выполняет векторный поиск по Article и объединяет полученные результаты с результатами полнотекстового поиска с помощью FULL JOIN
  3. Вычисляет оценку RRF путем объединения полнотекстового и семантического ранжирования
  4. Упорядочение по оценке RRF выбирает требуемое количество результатов и извлекает из оригинальных сущностей Article.

Запрос создает следующий SQL:

SELECT TOP(@__p_4) COALESCE([a].[Id], [t].[Id]) AS [Id], COALESCE([a].[Content], [t].[Content]) AS [Content], COALESCE([a].[Title], [t].[Title]) AS [Title]
FROM FREETEXTTABLE([Articles], *, @__textualQuery_0, @__k_1) AS [f]
INNER JOIN [Articles] AS [a] ON [f].[KEY] = [a].[Id]
FULL 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 [a].[Id] = [t].[Id]
ORDER BY ISNULL(1.0E0 / CAST(@__k_1 + [f].[RANK] AS float), 0.0E0) + ISNULL(1.0E0 / (CAST(@__k_1 AS float) + [t].[Distance]), 0.0E0) DESC