벡터 검색하기
벡터 검색하기 (Preview)
벡터 스토어 추상화는 벡터 검색 기능을 제공해요. 검색 옵션과 필터링을 어떻게 조합하는지가 이 페이지의 핵심이에요.
벡터 검색(Search)
SearchAsync 메서드는 이미 벡터화된 데이터로 검색해요. 벡터와 선택적인 VectorSearchOptions<TRecord>를 입력받죠. 이 메서드는 IVectorSearchable<TRecord>와 VectorStoreCollection<TKey, TRecord> 두 타입에서 쓸 수 있어요. 후자는 전자를 상속받기 때문이에요.
데이터가 들어 있는 컬렉션이 있다고 가정하고 Qdrant로 검색하는 예시를 볼게요. 검색 텍스트를 먼저 GenerateEmbeddingAsync로 벡터로 만든 뒤, top: 1처럼 결과 수를 제한해 검색해요.
using CommunityToolkit.VectorData.Qdrant;
using Microsoft.Extensions.VectorData;
using Qdrant.Client;
// Placeholder 임베딩 생성 메서드.
async Task<ReadOnlyMemory<float>> GenerateEmbeddingAsync(string textToVectorize)
{
// your logic here
}
// 레코드가 있는 기존 컬렉션을 선택한다.
VectorStore vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true);
VectorStoreCollection<ulong, Hotel> collection = vectorStore.GetCollection<ulong, Hotel>("skhotels");
// 검색 텍스트를 임베딩으로 변환한다.
ReadOnlyMemory<float> searchVector = await GenerateEmbeddingAsync("I'm looking for a hotel where customer happiness is the priority.");
// 검색 실행. Top 옵션으로 결과를 상위 1개로 제한한다.
var searchResult = collection.SearchAsync(searchVector, top: 1);
await foreach (var record in searchResult)
{
Console.WriteLine("Found hotel description: " + record.Record.Description);
Console.WriteLine("Found record score: " + record.Score);
}
Python에서도 비슷해요. search 메서드는 vector_property_name으로 어떤 벡터 속성을 쓸지 지정할 수 있어요.
search_results = await collection.search(
query, vector_property_name="vector"
)
hotels = [record.record async for record in search_results.results]
print(f"Found hotels: {hotels}")
지원 벡터 타입
SearchAsync는 벡터 파라미터에 제네릭 타입을 받아요. 데이터 스토어별로 지원하는 벡터 타입은 달라요. 같은 레코드에 서로 다른 타입의 벡터가 여러 개 있다면, 검색 벡터 타입이 타겟 벡터의 타입과 일치해야 해요. 레코드당 벡터가 여러 개일 때 타겟을 고르는 방법은 아래 VectorProperty에서 다뤄요.
검색 옵션
VectorSearchOptions<TRecord>로 제어할 수 있는 옵션들이에요.
VectorProperty
어느 벡터 속성을 검색할지를 지정해요. 지정하지 않고 데이터 모델에 벡터가 하나뿐이면 그 벡터를 써요. 벡터가 없거나 여러 개인데 VectorProperty를 지정하지 않으면 검색 메서드가 예외를 던져요.
레코드에 벡터가 둘 이상 있을 때 FeatureListEmbedding을 골라 검색하는 예시입니다.
using Microsoft.Extensions.VectorData;
using CommunityToolkit.VectorData.InMemory;
var vectorStore = new InMemoryVectorStore();
var collection = vectorStore.GetCollection<int, Product>("skproducts");
// FeatureListEmbedding 속성을 검색 대상으로 지정.
var vectorSearchOptions = new VectorSearchOptions<Product>
{
VectorProperty = r => r.FeatureListEmbedding
};
var searchResult = collection.SearchAsync(searchVector, top: 3, vectorSearchOptions);
public sealed class Product
{
[VectorStoreKey]
public int Key { get; set; }
[VectorStoreData]
public string Description { get; set; }
[VectorStoreData]
public List<string> FeatureList { get; set; }
[VectorStoreVector(1536)]
public ReadOnlyMemory<float> DescriptionEmbedding { get; set; }
[VectorStoreVector(1536)]
public ReadOnlyMemory<float> FeatureListEmbedding { get; set; }
}
Top과 Skip
Top과 Skip은 결과를 상위 N개로 제한하고, 결과셋 맨 위에서 몇 개를 건너뛸지 지정해요. 큰 결과를 여러 번 나눠 가져올 때 페이징에 쓸 수 있어요. Skip 기본값은 0이에요.
// 처음 40개를 건너뛴 뒤 그 다음 20개를 가져오는 예시.
var vectorSearchOptions = new VectorSearchOptions<Product>
{
Skip = 40
};
var searchResult = collection.SearchAsync(searchVector, top: 20, vectorSearchOptions);
IncludeVectors
검색 결과에 벡터를 포함해서 반환할지 지정해요. false면 반환되는 모델의 벡터 속성이 null로 남아요. 검색 중 벡터 스토어에서 가져오는 데이터 양을 크게 줄여 검색을 더 효율적으로 만들어요. 기본값은 false예요.
var vectorSearchOptions = new VectorSearchOptions<Product>
{
IncludeVectors = true
};
Filter
벡터 검색을 적용하기 전에 선택한 컬렉션의 레코드를 거르는 필터를 지정할 수 있어요. 이게 가져다주는 이점은 두 가지예요.
- 지연·처리 비용 감소 — 필터링 후 남은 레코드만 검색 벡터와 비교하면 되므로 벡터 비교 횟수가 줄어요.
- 결과셋 제한 — 사용자가 접근하면 안 되는 데이터를 걸러내는 접근 제어(access control) 목적에도 써요.
주의할 점은 필터링에 쓰려는 필드는 많은 벡터 스토어에서 먼저 인덱싱해야 한다는 거예요. 어떤 스토어는 아무 필드나 필터링을 허용하면서, 선택적으로 인덱싱으로 성능을 개선하게 하기도 해요. 필터링을 활성화하려면 데이터 모델 정의 시 IsFilterable 속성을 true로 설정해야 해요.
필터는 데이터 모델 타입을 기반으로 하는 LINQ 표현식으로 작성해요. 지원되는 LINQ 표현식 집합은 DB마다 다르지만, equals·not equals·and·or 같은 공통 표현식은 모든 DB가 넓게 지원해요.
// Category가 'External Definitions'이고 Tags에 'memory'를 포함하는 레코드만 필터링.
var vectorSearchOptions = new VectorSearchOptions<Glossary>
{
Filter = r => r.Category == "External Definitions" && r.Tags.Contains("memory")
};
sealed class Glossary
{
[VectorStoreKey]
public ulong Key { get; set; }
// Category는 필터링에 쓰이므로 인덱스로 표시.
[VectorStoreData(IsIndexed = true)]
public string Category { get; set; }
// Tags도 필터링에 쓰이므로 인덱스로 표시.
[VectorStoreData(IsIndexed = true)]
public List<string> Tags { get; set; }
[VectorStoreData]
public string Term { get; set; }
[VectorStoreData]
public string Definition { get; set; }
[VectorStoreVector(1536)]
public ReadOnlyMemory<float> DefinitionEmbedding { get; set; }
}
Python의 필터
Python에선 filter 파라미터를 람다 표현식(또는 그 문자열)으로 전달해요. lambda record: record.property == "value" 같은 형태죠. 여기서 중요한 건 이 필터가 직접 실행되는 게 아니라 벡터 스토어에 맞는 문법으로 파싱된다는 거예요. 유일한 예외는 필터를 직접 실행하는 InMemoryCollection이에요. 이 유연성 때문에 스토어마다 어떤 필터를 지원하는지 문서를 확인해야 해요. 예를 들어 모든 벡터 스토어가 부정 필터(즉 lambda x: not x.value)를 지원하진 않아서, 이것은 검색을 실행하기 전까지 드러나지 않아요.
출처: https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/vector-search