비고
벡터 지원은 EF Core 10.0에서 도입되었으며 SQL Server 2025 이상에서만 지원됩니다.
SQL Server 벡터 데이터 형식을 사용하면 유사성을 효율적으로 검색할 수 있는 의미의 표현인 embeddings 저장하여 의미 체계 검색 및 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();
데이터베이스에 저장된 포함 항목이 있으면 벡터 유사성 검색을 수행할 준비가 된 것입니다.
비고
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의 기본 제공 지원은 데이터 형식이 도입되기 전에 벡터 검색을 수행할 수 있었던 이전 vector 확장을 대체합니다. EF 10으로 업그레이드하는 과정의 일환으로 프로젝트에서 확장을 제거합니다.
VECTOR_SEARCH()를 사용하여 검색
경고
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() 큰 데이터 세트에 대해 훨씬 더 나은 성능을 제공하는 근사한 인접 항목(ANN) 검색에 벡터 인덱스를 사용하도록 SQL Server 지시합니다. SQL WITH APPROXIMATE 절에 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 norm) |
dot |
점 제품(음의 내부 제품) |
포함 모델 및 사용 사례와 가장 일치하는 메트릭을 선택합니다. 코사인 유사성은 일반적으로 텍스트 포함에 사용되지만, 유클리드 거리는 이미지 포함에 자주 사용됩니다.
하이브리드 검색
하이브리드 검색 은 벡터 유사성 검색과 기존 전체 텍스트 검색 을 결합하여 더 관련성이 큰 결과를 제공합니다. 벡터 검색은 의미상 유사한 콘텐츠를 찾는 데 탁월하지만 전체 텍스트 검색은 정확한 키워드 일치에 더 적합합니다. 두 방법을 결합하고 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)
.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();
이 쿼리:
-
Article에서 전체 텍스트 검색을 수행합니다. - 벡터 검색을
Article수행하고 FULL JOIN을 통해 전체 텍스트 검색 결과와 결과를 결합합니다. - 전체 텍스트와 의미 체계 순위를 결합하여 RRF 점수를 계산합니다.
- 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
.NET