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

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

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

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

在取得向量索引後,使用 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();

這讓你能篩選相似度分數,並呈現給使用者等。

混合式搜尋 結合向量相似性搜尋與傳統 全文搜尋 ,以提供更相關的結果。 向量搜尋擅長尋找語意相似的內容,而全文搜尋則更擅長精確的關鍵字匹配。 結合兩種方法並使用互惠排名融合(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)
    // 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 ,並透過左連接將結果與全文搜尋結果合併
  3. 透過結合全文與語意排名來計算 RRF 分數
  4. 按 RRF 分數排序,取出所需數量的結果並推算出原始 Article 實體。

備註

與其使用 LEFT JOIN,不如使用 FULL OUTER JOIN 更加適合此情境;這樣不論搜尋結果是否在另一側出現,都能將任一搜尋方的高排名結果納入最終結果中。 使用上述 LEFT JOIN 方法,如果某個結果的向量相似度分數非常高,但全文分數不高,則該結果永遠不會被納入最終結果。 然而,EF 目前不支援 FULL OUTER JOIN;如果你希望看到支持,請按 #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