하이브리드 검색하기

하이브리드 검색하기 (Preview)

Semantic Kernel 벡터 스토어 추상화는 하이브리드 검색(hybrid search)도 지원해요. 하이브리드 검색은 벡터 검색 + 키워드 검색을 병렬로 실행한 뒤, 두 결과셋의 합집합(union) 을 반환하는 방식이에요. 스파스 벡터 기반 하이브리드 검색은 현재 지원되지 않아요.

하이브리드 검색 전제 조건

하이브리드 검색을 실행하려면 DB 스키마에 벡터 필드전체 텍스트 검색(full text search)이 활성화된 문자열 필드가 둘 다 있어야 해요. Semantic Kernel 벡터 스토어 커넥터로 컬렉션을 만든다면, 키워드 검색 대상인 문자열 필드의 IsFullTextIndexed 옵션을 켜 두는 걸 잊지 마세요.

HybridSearchAsync 메서드

HybridSearchAsync 메서드는 벡터와 문자열 키워드의 ICollection을 받아 검색하고, 선택적으로 HybridSearchOptions<TRecord>를 입력받아요. 이 메서드는 IKeywordHybridSearchable<TRecord> 인터페이스에서 사용할 수 있는데, 벡터+키워드 하이브리드 검색을 지원하는 DB의 커넥터만 이 인터페이스를 구현해요. Qdrant 예시를 볼게요.

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);
IKeywordHybridSearchable<Hotel> collection = (IKeywordHybridSearchable<Hotel>)vectorStore.GetCollection<ulong, Hotel>("skhotels");

// 검색 텍스트를 벡터로 변환.
ReadOnlyMemory<float> searchVector = await GenerateEmbeddingAsync("I'm looking for a hotel where customer happiness is the priority.");

// 벡터와 키워드 리스트를 함께 전달해 하이브리드 검색 실행.
var searchResult = collection.HybridSearchAsync(searchVector, ["happiness", "hotel", "customer"], top: 1);

await foreach (var record in searchResult)
{
    Console.WriteLine("Found hotel description: " + record.Record.Description);
    Console.WriteLine("Found record score: " + record.Score);
}

Python에서는 hybrid_search 메서드를 쓰는데, vector_property_name과 함께 전체 텍스트 검색에 쓸 additional_property_name을 지정해요.

from semantic_kernel.connectors.azure_ai_search import AzureAISearchCollection, AzureAISearchStore

store = AzureAISearchStore()
collection: AzureAISearchCollection[str, Hotels] = store.get_collection(Hotels, collection_name="skhotels")

search_results = await collection.hybrid_search(
    query, vector_property_name="vector", additional_property_name="description"
)
hotels = [record.record async for record in search_results.results]
print(f"Found hotels: {hotels}")

하이브리드 검색 옵션

HybridSearchOptions<TRecord>로 제어할 수 있는 옵션들이에요.

VectorProperty와 AdditionalProperty

어느 벡터 속성어느 전체 텍스트 검색 속성을 검색 대상으로 할지 정해요.

  • VectorProperty를 지정하지 않고 데이터 모델에 벡터가 하나뿐이면 그 벡터를 써요. 벡터가 없거나 여러 개인데 지정하지 않으면 예외를 던져요.
  • AdditionalProperty도 같은 규칙이에요. 전체 텍스트 검색 속성이 하나뿐이면 그 속성을, 없거나 여러 개인데 지정하지 않으면 예외를 던져요.
// DescriptionEmbedding 벡터 속성과 Description 전체 텍스트 검색 속성을 지정.
var hybridSearchOptions = new HybridSearchOptions<Product>
{
    VectorProperty = r => r.DescriptionEmbedding,
    AdditionalProperty = r => r.Description
};

var searchResult = collection.HybridSearchAsync(searchVector, ["happiness", "hotel", "customer"], top: 3, hybridSearchOptions);

public sealed class Product
{
    [VectorStoreKey]
    public int Key { get; set; }

    [VectorStoreData(IsFullTextIndexed = true)]
    public string Name { get; set; }

    [VectorStoreData(IsFullTextIndexed = true)]
    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

TopSkip은 결과를 상위 N개로 제한하고 결과셋 맨 위에서 몇 개를 건너뛸지 지정해요. 큰 결과를 여러 번 나눠 가져올 때 페이징에 쓸 수 있어요. Skip 기본값은 0이에요.

// 처음 40개를 건너뛰고 그 다음 20개를 가져오는 예시.
var hybridSearchOptions = new HybridSearchOptions<Product>
{
    Skip = 40
};
var searchResult = collection.HybridSearchAsync(searchVector, ["happiness", "hotel", "customer"], top: 20, hybridSearchOptions);

IncludeVectors

검색 결과에 벡터를 포함해 반환할지 지정해요. false면 반환 모델의 벡터 속성이 null로 남아 검색 중 가져오는 데이터 양을 줄여 효율을 높여요. 기본값은 false예요.

var hybridSearchOptions = new HybridSearchOptions<Product>
{
    IncludeVectors = true
};

Filter

벡터 검색을 적용하기 전에 레코드를 거르는 필터예요. 필터링 후 남은 레코드만 검색 벡터와 비교하므로 지연·처리 비용을 줄이고, 접근 제어 목적으로 결과셋을 제한할 수도 있어요. 벡터 검색과 같은 규칙이 적용돼요. 필터링에 쓰려는 필드는 많은 벡터 스토어에서 먼저 인덱싱해야 하고, 필터링을 활성화하려면 데이터 모델 정의 시 IsFilterabletrue로 설정해야 해요.

필터는 LINQ 표현식으로 작성해요. 지원 집합은 DB마다 다르지만 equals·not equals·and·or 같은 공통 표현식은 널리 지원돼요.

var hybridSearchOptions = new HybridSearchOptions<Glossary>
{
    Filter = r => r.Category == "External Definitions" && r.Tags.Contains("memory")
};

var searchResult = collection.HybridSearchAsync(searchVector, ["happiness", "hotel", "customer"], top: 3, hybridSearchOptions);

Python의 filter 파라미터는 람다 표현식(또는 문자열)으로 전달돼요. 이 필터는 직접 실행되는 게 아니라 벡터 스토어 문법으로 파싱되며, InMemoryCollection만 직접 실행해요. 스토어마다 지원 필터가 다르므로 문서를 확인해야 하고, 예를 들어 부정 필터(lambda x: not x.value)는 모든 스토어가 지원하지 않아요.

출처: https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/hybrid-search