다음을 통해 공유


SQL Server EF Core 공급자의 벡터 검색

비고

벡터 지원은 EF Core 10.0에서 도입되었으며 SQL Server 2025 이상에서만 지원됩니다.

SQL Server 벡터 데이터 형식을 사용하면 유사성을 효율적으로 검색할 수 있는 의미의 표현인 포함을 저장할 수 있으며 의미 체계 검색 및 RAG(검색 보강 생성)와 같은 AI 워크로드를 지원합니다.

벡터 속성 설정

데이터 형식을 vector 사용하려면 다음과 같이 차원을 지정하여 엔터티 형식에 형식 SqlVector<float> 의 .NET 속성을 추가하면 됩니다.

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

데이터베이스에 저장된 포함 항목이 있으면 벡터 유사성 검색을 수행할 준비가 된 것입니다.

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의 기본 제공 지원은 데이터 형식이 도입되기 전에 벡터 검색을 수행할 수 있었던 이전 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 norm)
dot 점 제품(음의 내부 제품)

포함 모델 및 사용 사례와 가장 일치하는 메트릭을 선택합니다. 코사인 유사성은 일반적으로 텍스트 포함에 사용되지만, 유클리드 거리는 이미지 포함에 자주 사용됩니다.

벡터 인덱스가 있으면 VectorSearch() 확장 메서드를 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}");
}

이는 다음 SQL로 변환됩니다.

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

매개 변수는 topN 반환할 결과의 최대 수를 지정합니다.

VectorSearch() 반환 VectorSearchResult<TEntity>- 엔터티와 계산된 거리 모두에 액세스할 수 있습니다.

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

이를 통해 유사성 점수를 필터링하고 사용자에게 표시할 수 있습니다.

하이브리드 검색 은 벡터 유사성 검색과 기존 전체 텍스트 검색 을 결합하여 더 관련성이 큰 결과를 제공합니다. 벡터 검색은 의미상 유사한 콘텐츠를 찾는 데 탁월하지만 전체 텍스트 검색은 정확한 키워드 일치에 더 적합합니다. 두 방법을 결합하고 RRF(상호 순위 Fusion)를 사용하여 결과를 병합하면 보다 지능적인 검색 환경을 구축할 수 있습니다.

다음 예제에서는 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을 사용하는 대신 전체 OUTER JOIN이 이 시나리오에 더 적합합니다. 이렇게 하면 해당 결과가 다른 쪽에 전혀 표시되지 않더라도 두 검색 측의 상위 순위 결과가 최종 결과에 포함될 수 있습니다. 위의 LEFT JOIN 접근 방식을 사용하면 결과에 매우 높은 벡터 유사성 점수가 있는 경우 해당 결과에 높은 전체 텍스트 점수가 없으면 최종 결과에 포함되지 않습니다. 그러나 EF는 현재 FULL OUTER JOIN을 지원하지 않습니다. 이 기능의 지원을 원하신다면 #37633 번을 추천해 주세요.

쿼리는 다음 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