Поиск векторных данных в поставщике 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 Майкрософт.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 для этих функций также могут быть изменены.

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

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

Чтобы использовать VECTOR_SEARCH(), необходимо создать векторный индекс в столбце векторов. Используйте метод 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 (отрицательный внутренний продукт)

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

После получения векторного VectorSearch() индекса используйте метод расширения для вашего DbSet:

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

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

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

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

Параметр topN задает максимальное количество возвращаемых результатов.

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

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

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

Гибридный поиск объединяет векторный поиск подобия с традиционным полнотекстовый поиск , чтобы обеспечить более релевантные результаты. Векторный поиск превосходит в нахождении семантически аналогичного содержимого, в то время как полнотекстовый поиск лучше подходит для точного совпадения с ключевыми словами. Сочетая оба подхода и используя слияние с взаимным ранжированием (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)
    // 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();

Этот запрос:

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

Замечание

Вместо того, чтобы использовать LEFT JOIN, полное ВНЕШНЕЕ СОЕДИНЕНИЕ будет более подходящим для этого сценария; это позволит высокоранговые результаты из любой стороны поиска быть включены в окончательный результат, даже если этот результат не отображается вообще на другой стороне. При приведенном выше подходе LEFT JOIN, если результат имеет очень высокую оценку сходства векторов, он никогда не включается в окончательный результат, если этот результат также не имеет высокой полнотекстовой оценки. Однако EF в настоящее время не поддерживает ПОЛНОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ; upvote #37633, если вы хотите, чтобы это поддерживалось.

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

SELECT TOP(@p3) [a0].[Id], [a0].[Content], [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