Jak używać magazynów wektorowych z funkcją wyszukiwania tekstowego w Semantic Kernel

Wszystkie konektory Vector Store mogą być używane do wyszukiwania tekstowego.

  1. Użyj łącznika Vector Store, aby pobrać kolekcję rekordów, którą chcesz wyszukać.
  2. Opakuj kolekcję rekordów za pomocą polecenia VectorStoreTextSearch.
  3. Przekonwertuj na wtyczkę do wykorzystania w scenariuszach RAG i/lub wywoływania funkcji.

Jest bardzo prawdopodobne, że chcesz dostosować funkcję wyszukiwania wtyczki, aby jej opis odzwierciedlał typ danych dostępnych w kolekcji rekordów. Jeśli na przykład kolekcja rekordów zawiera informacje o hotelach, opis funkcji wyszukiwania wtyczki powinien o tym wspomnieć. Pozwoli to zarejestrować wiele wtyczek, np. jedną do wyszukiwania hoteli, inną do wyszukiwania restauracji, a jeszcze inną do wyszukiwania atrakcji.

Abstrakcje wyszukiwania tekstowego zawierają funkcję zwracającą znormalizowany wynik wyszukiwania, to znaczy instancję klasy TextSearchResult. Ten znormalizowany wynik wyszukiwania zawiera wartość i opcjonalnie nazwę i link. abstrakcje wyszukiwania tekstowego obejmują funkcję zwracającą wartość tekstową, np. jako wynik wyszukiwania zostanie zwrócona jedna z właściwości modelu danych. Aby wyszukiwanie tekstowe działało poprawnie, należy zapewnić sposób mapowania modelu danych magazynu wektorów na wystąpienie TextSearchResult. W następnej sekcji opisano dwie opcje, których można użyć do wykonania tego mapowania.

Wskazówka

Aby uruchomić przykłady wyświetlane na tej stronie, przejdź do pozycji GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs.

Mapowanie z modelu danych magazynu wektorowego na element TextSearchResult można deklaratywnie wykonywać przy użyciu atrybutów.

  1. [TextSearchResultValue] — Dodaj ten atrybut do właściwości modelu danych, który będzie wartością TextSearchResult, np. tekstowych danych, których model AI będzie używać do odpowiadania na pytania.
  2. [TextSearchResultName] - Dodaj ten atrybut do właściwości modelu danych, który będzie nazwą TextSearchResult.
  3. [TextSearchResultLink] — Dodaj ten atrybut do właściwości modelu danych, który będzie linkiem do elementu TextSearchResult.

Poniższy przykład przedstawia model danych, który ma zastosowane atrybuty wyników wyszukiwania tekstu.

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Data;

public sealed class DataModel
{
    [VectorStoreKey]
    [TextSearchResultName]
    public Guid Key { get; init; }

    [VectorStoreData]
    [TextSearchResultValue]
    public string Text { get; init; }

    [VectorStoreData]
    [TextSearchResultLink]
    public string Link { get; init; }

    [VectorStoreData(IsFilterable = true)]
    public required string Tag { get; init; }

    [VectorStoreVector(1536)]
    public ReadOnlyMemory<float> Embedding { get; init; }
}

Mapowanie z modelu danych wektorowej bazy danych na string lub TextSearchResult można również wykonać poprzez dostarczenie implementacji odpowiednio ITextSearchStringMapper i ITextSearchResultMapper.

Możesz zdecydować się na utworzenie niestandardowych modułów mapowania w następujących scenariuszach:

  1. Należy połączyć wiele właściwości modelu danych, np. jeśli do uzyskania wartości trzeba połączyć kilka właściwości.
  2. Dodatkowa logika jest wymagana do wygenerowania jednej z właściwości, np. jeśli właściwość łącza musi zostać obliczona z właściwości modelu danych.

Poniższy przykład przedstawia model danych i dwie przykładowe implementacje mapowania, które mogą być używane z modelem danych.

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Data;

protected sealed class DataModel
{
    [VectorStoreKey]
    public Guid Key { get; init; }

    [VectorStoreData]
    public required string Text { get; init; }

    [VectorStoreData]
    public required string Link { get; init; }

    [VectorStoreData(IsFilterable = true)]
    public required string Tag { get; init; }

    [VectorStoreVector(1536)]
    public ReadOnlyMemory<float> Embedding { get; init; }
}

/// <summary>
/// String mapper which converts a DataModel to a string.
/// </summary>
protected sealed class DataModelTextSearchStringMapper : ITextSearchStringMapper
{
    /// <inheritdoc />
    public string MapFromResultToString(object result)
    {
        if (result is DataModel dataModel)
        {
            return dataModel.Text;
        }
        throw new ArgumentException("Invalid result type.");
    }
}

/// <summary>
/// Result mapper which converts a DataModel to a TextSearchResult.
/// </summary>
protected sealed class DataModelTextSearchResultMapper : ITextSearchResultMapper
{
    /// <inheritdoc />
    public TextSearchResult MapFromResultToTextSearchResult(object result)
    {
        if (result is DataModel dataModel)
        {
            return new TextSearchResult(value: dataModel.Text) { Name = dataModel.Key.ToString(), Link = dataModel.Link };
        }
        throw new ArgumentException("Invalid result type.");
    }
}

Implementacje modułów mapujących można przekazać jako parametry podczas tworzenia VectorStoreTextSearch, jak pokazano poniżej:

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Data;

// Create custom mapper to map a <see cref="DataModel"/> to a <see cref="string"/>
var stringMapper = new DataModelTextSearchStringMapper();

// Create custom mapper to map a <see cref="DataModel"/> to a <see cref="TextSearchResult"/>
var resultMapper = new DataModelTextSearchResultMapper();

// Add code to create instances of VectorStoreCollection and ITextEmbeddingGenerationService 

// Create a text search instance using the vector store collection.
var result = new VectorStoreTextSearch<DataModel>(vectorStoreCollection, textEmbeddingGeneration, stringMapper, resultMapper);

Poniższy przykład pokazuje, jak utworzyć instancję VectorStoreTextSearch przy użyciu kolekcji rekordów Vector Store.

Wskazówka

Poniższe przykłady wymagają wystąpień znaczników VectorStoreCollection i ITextEmbeddingGenerationService. Aby utworzyć instancję VectorStoreCollection, zapoznaj się z dokumentacją każdego konektora. Aby utworzyć instancję ITextEmbeddingGenerationService, wybierz usługę, której chcesz użyć, np. Azure OpenAI, OpenAI, ... lub skorzystaj z modelu lokalnego, takiego jak ONNX, Ollama, ... i utwórz instancję odpowiedniej implementacji ITextEmbeddingGenerationService.

Wskazówka

Obiekt VectorStoreTextSearch można również utworzyć z instancji klasy IVectorizableTextSearch. W takim przypadku ITextEmbeddingGenerationService nie jest potrzebne.

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;

// Add code to create instances of VectorStoreCollection and ITextEmbeddingGenerationService 

// Create a text search instance using the vector store collection.
var textSearch = new VectorStoreTextSearch<DataModel>(vectorStoreCollection, textEmbeddingGeneration);

// Search and return results as TextSearchResult items
var query = "What is the Semantic Kernel?";
KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 2, Skip = 0 });
Console.WriteLine("\n--- Text Search Results ---\n");
await foreach (TextSearchResult result in textResults.Results)
{
    Console.WriteLine($"Name:  {result.Name}");
    Console.WriteLine($"Value: {result.Value}");
    Console.WriteLine($"Link:  {result.Link}");
}

Tworzenie wtyczki wyszukiwania z magazynu wektorów

W poniższym przykładzie pokazano, jak utworzyć wtyczkę o nazwie SearchPlugin na podstawie wystąpienia programu VectorStoreTextSearch. Użycie CreateWithGetTextSearchResults tworzy nową wtyczkę z jedną funkcją GetTextSearchResults, która wywołuje bazową implementację wyszukiwania w kolekcji rekordów Vector Store. Element SearchPlugin jest dodawany do elementu Kernel, dzięki czemu jest dostępny do wywołania podczas renderowania monitu. Szablon promptu zawiera wywołanie do {{SearchPlugin.Search $query}}, które uruchomi SearchPlugin w celu pobrania wyników związanych z bieżącym zapytaniem. Wyniki zostaną następnie wstawione do renderowanego monitu przed wysłaniem ich do modelu.

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;

// Create a kernel with OpenAI chat completion
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatCompletion(
        modelId: TestConfiguration.OpenAI.ChatModelId,
        apiKey: TestConfiguration.OpenAI.ApiKey);
Kernel kernel = kernelBuilder.Build();

// Add code to create instances of VectorStoreCollection and ITextEmbeddingGenerationService

// Create a text search instance using the vector store collection.
var textSearch = new VectorStoreTextSearch<DataModel>(vectorStoreCollection, textEmbeddingGeneration);

// Build a text search plugin with vector store search and add to the kernel
var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin");
kernel.Plugins.Add(searchPlugin);

// Invoke prompt and use text search plugin to provide grounding information
var query = "What is the Semantic Kernel?";
string promptTemplate = """
    {{#with (SearchPlugin-GetTextSearchResults query)}}  
        {{#each this}}  
        Name: {{Name}}
        Value: {{Value}}
        Link: {{Link}}
        -----------------
        {{/each}}  
    {{/with}}  

    {{query}}

    Include citations to the relevant information where it is referenced in the response.
    """;
KernelArguments arguments = new() { { "query", query } };
HandlebarsPromptTemplateFactory promptTemplateFactory = new();
Console.WriteLine(await kernel.InvokePromptAsync(
    promptTemplate,
    arguments,
    templateFormat: HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat,
    promptTemplateFactory: promptTemplateFactory
));

Używanie magazynu wektorów z wywoływaniem funkcji

Poniższy przykład również tworzy wystąpienie SearchPlugin na podstawie wystąpienia klasy VectorStoreTextSearch. Ta wtyczka będzie udostępniana modelowi do użycia przy automatycznym wywoływaniu funkcji za pomocą elementu FunctionChoiceBehavior w ustawieniach wykonywania promptu. Po uruchomieniu tego przykładu model wywoła funkcję wyszukiwania, aby pobrać dodatkowe informacje, aby odpowiedzieć na pytanie. Prawdopodobnie po prostu wyszuka "Jądro semantyczne", a nie całe zapytanie.

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;

// Create a kernel with OpenAI chat completion
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatCompletion(
        modelId: TestConfiguration.OpenAI.ChatModelId,
        apiKey: TestConfiguration.OpenAI.ApiKey);
Kernel kernel = kernelBuilder.Build();

// Add code to create instances of VectorStoreCollection and ITextEmbeddingGenerationService

// Create a text search instance using the vector store collection.
var textSearch = new VectorStoreTextSearch<DataModel>(vectorStoreCollection, textEmbeddingGeneration);

// Build a text search plugin with vector store search and add to the kernel
var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin");
kernel.Plugins.Add(searchPlugin);

// Invoke prompt and use text search plugin to provide grounding information
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
KernelArguments arguments = new(settings);
Console.WriteLine(await kernel.InvokePromptAsync("What is the Semantic Kernel?", arguments));

Dostosowywanie funkcji wyszukiwania

Poniższy przykładowy sposób dostosowywania opisu funkcji wyszukiwania, która jest dodawana do elementu SearchPlugin. Oto niektóre czynności, które warto wykonać:

  1. Zmień nazwę funkcji wyszukiwania, aby odzwierciedlić, co znajduje się w skojarzonej kolekcji rekordów, np. możesz nazwać funkcję SearchForHotels , jeśli kolekcja rekordów zawiera informacje o hotelu.
  2. Zmień opis funkcji. Dokładny opis funkcji pomaga modelowi sztucznej inteligencji wybrać najlepszą funkcję do wywołania. Jest to szczególnie ważne w przypadku dodawania wielu funkcji wyszukiwania.
  3. Dodaj dodatkowy parametr do funkcji wyszukiwania. Jeśli kolekcja rekordów zawiera informacje o hotelu, a jedną z właściwości jest nazwa miasta, możesz dodać właściwość do funkcji wyszukiwania, aby określić miasto. Filtr zostanie automatycznie dodany i będzie filtrować wyniki wyszukiwania według miasta.

Wskazówka

W poniższym przykładzie użyto domyślnej implementacji wyszukiwania. Możesz zdecydować się na własną implementację, która odwołuje się do bazowej kolekcji rekordów magazynu wektorowego z dodatkowymi opcjami, aby precyzyjnie dostroić wyszukiwanie.

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;

// Create a kernel with OpenAI chat completion
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatCompletion(
        modelId: TestConfiguration.OpenAI.ChatModelId,
        apiKey: TestConfiguration.OpenAI.ApiKey);
Kernel kernel = kernelBuilder.Build();

// Add code to create instances of VectorStoreCollection and ITextEmbeddingGenerationService

// Create a text search instance using the vector store collection.
var textSearch = new VectorStoreTextSearch<DataModel>(vectorStoreCollection, textEmbeddingGeneration);

// Create options to describe the function I want to register.
var options = new KernelFunctionFromMethodOptions()
{
    FunctionName = "Search",
    Description = "Perform a search for content related to the specified query from a record collection.",
    Parameters =
    [
        new KernelParameterMetadata("query") { Description = "What to search for", IsRequired = true },
        new KernelParameterMetadata("top") { Description = "Number of results", IsRequired = false, DefaultValue = 2 },
        new KernelParameterMetadata("skip") { Description = "Number of results to skip", IsRequired = false, DefaultValue = 0 },
    ],
    ReturnParameter = new() { ParameterType = typeof(KernelSearchResults<string>) },
};

// Build a text search plugin with vector store search and add to the kernel
var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin", "Search a record collection", [textSearch.CreateSearch(options)]);
kernel.Plugins.Add(searchPlugin);

// Invoke prompt and use text search plugin to provide grounding information
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
KernelArguments arguments = new(settings);
Console.WriteLine(await kernel.InvokePromptAsync("What is the Semantic Kernel?", arguments));

Wkrótce

Wkrótce pojawi się więcej.

Wkrótce

Wkrótce pojawi się więcej.

Następne kroki