SQL Server EF Core 提供者中的向量搜尋

備註

向量支援於 EF Core 10.0 引入,僅支援 SQL Server 2025 及以上版本。

SQL Server 的向量資料類型允許儲存嵌入向量,這些是對意義的表示,可以有效地進行相似性搜尋,支持 AI 工作負載,如語義搜尋和檢索輔助生成(RAG)。

設定向量屬性

要使用 vector 資料型態,只需在實體類型中加入 .NET 屬性 SqlVector<float>,並指定以下尺寸:

public class Blog
{
    // ...

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

新增屬性並在資料庫中建立對應的欄後,您就可以開始插入內嵌。 內嵌產生是在資料庫外部完成,通常是透過服務,執行此動作的詳細資料超出本檔的範圍。 然而,the .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中可能會變更。 EF Core 中這些功能的 API 也可能有所變動。

SQL Server 的 VECTOR_SEARCH() 表值函數會根據向量相似度檢索資料列。 與計算兩個特定向量間距離的 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)

以下距離度量支援向量索引:

Metric 說明
cosine 餘弦相似度(角距離)
euclidean 歐幾里得距離(L2 範數)
dot 點積(負內積)

選擇最適合你嵌入模型和使用情境的指標。 餘弦相似度常用於文字嵌入,而歐氏距離則常用於影像嵌入。

混合式搜尋 結合向量相似性搜尋與傳統 全文搜尋 ,以提供更相關的結果。 向量搜尋擅長尋找語意相似的內容,而全文搜尋則更擅長精確的關鍵字匹配。 結合兩種方法並使用互惠排名融合(Reciprocal Rank Fusion,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