Compartilhar via


O que são repositórios de vetores de kernel semântico? (versão prévia)

Aviso

A funcionalidade do Armazenamento de Vetor do Kernel Semântico é RC, e melhorias que exijam mudanças significativas ainda podem ocorrer em circunstâncias limitadas antes da versão.

Aviso

A funcionalidade do Armazenamento de vetores do kernel semântico está em versão preliminar, e melhorias que exijam mudanças significativas ainda podem ocorrer em circunstâncias limitadas antes da versão.

Dica

Se você estiver procurando informações sobre os conectores herdados do Armazenamento de Memória, consulte a página Armazenamentos de Memória.

Os bancos de dados vetoriais têm muitos casos de uso em diferentes domínios e aplicativos que envolvem processamento de linguagem natural (NLP), visão computacional (CV), sistemas de recomendação (RS) e outras áreas que exigem compreensão semântica e correspondência de dados.

Um caso de uso para armazenar informações em um banco de dados vetorial é permitir que grandes modelos de linguagem (LLMs) gerem respostas mais relevantes e coerentes. Grandes modelos de linguagem geralmente enfrentam desafios como gerar informações imprecisas ou irrelevantes; falta de consistência factual ou bom senso; repetindo ou contradizendo-se; ser tendencioso ou ofensivo. Para ajudar a superar esses desafios, você pode usar um banco de dados vetorial para armazenar informações sobre diferentes tópicos, palavras-chave, fatos, opiniões e/ou fontes relacionadas ao domínio ou gênero desejado. O banco de dados vetorial permite que você encontre com eficiência o subconjunto de informações relacionadas a uma pergunta ou tópico específico. Em seguida, você pode passar informações do banco de dados de vetores com seu prompt para o modelo de linguagem grande para gerar conteúdo mais preciso e relevante.

Por exemplo, se você deseja escrever uma postagem de blog sobre as últimas tendências em IA, pode usar um banco de dados vetorial para armazenar as informações mais recentes sobre esse tópico e passar as informações junto com a solicitação para um LLM para gerar uma postagem de blog que aproveite as informações mais recentes.

O Kernel Semântico e o .net fornecem uma abstração para interagir com o Vector Stores e uma lista de implementações prontas para uso que implementam essas abstrações para vários bancos de dados. Os recursos incluem criar, listar e excluir coleções de registros e carregar, recuperar e excluir registros. A abstração facilita a experimentação com um Armazenamento de vetor gratuito ou hospedado localmente e, em seguida, a mudança para um serviço quando for necessário para escalar verticalmente.

As implementações prontas para uso podem ser utilizadas com o Kernel Semântico, mas não dependem da pilha principal do Kernel Semântico e, portanto, podem ser utilizadas de forma completamente independente, se necessário. As implementações do Kernel Semântico fornecidas são chamadas de "conectores".

Geração Aumentada de Recuperação (RAG) com Armazenamento de Vetores

A abstração do repositório de vetores é uma api de baixo nível para adicionar e recuperar dados de repositórios de vetores. O Kernel Semântico tem suporte interno para usar qualquer uma das implementações do Vector Store para RAG. Isso é feito encapsulando IVectorSearchable<TRecord> e expondo-o como uma implementação de Pesquisa de Texto.

Dica

Para saber mais sobre como usar armazenamento de vetores para RAG, consulte Como usar armazenamento de vetores com pesquisa de texto do Kernel Semântico.

Dica

Para saber mais sobre a pesquisa de texto, consulte O que é Pesquisa Semântica de Texto do Kernel?

Dica

Para saber mais sobre como adicionar rapidamente o RAG ao agente, consulte Como adicionar RAG (Geração Aumentada de Recuperação) a Agentes de Kernel Semântico.

A abstração do Armazenamento de Vetor

As abstrações do Vector Store são fornecidas no Microsoft.Extensions.VectorData.Abstractions pacote nuget. Veja a seguir as principais classes e interfaces de base abstratas.

Microsoft.Extensions.VectorData.VectorStore

VectorStore contém operações que se estendem por todas as coleções no repositório de vetores, por exemplo, ListCollectionNames. Ele também fornece a capacidade de obter instâncias VectorStoreCollection<TKey, TRecord>.

Microsoft.Extensions.VectorData.VectorStoreCollection<TKey, TRecord>

VectorStoreCollection<TKey, TRecord> representa uma coleção. Essa coleção pode ou não existir, e a classe base abstrata fornece métodos para verificar se a coleção existe, criá-la ou excluí-la. A classe base abstrata também fornece métodos para inserir ou atualizar, obter e excluir registros. Por fim, a classe base abstrata herda de IVectorSearchable<TRecord>, que fornece recursos de pesquisa vetorial.

Microsoft.Extensions.VectorData.IVectorSearchable<TRecord>

  • SearchAsync<TRecord> pode ser usado para fazer:
    • buscas em vetores que recebem alguma entrada que pode ser vetorizada por um gerador de incorporação registrado ou pelo banco de dados de vetores, se o banco de dados suportar isso.
    • pesquisas de vetor tomando um vetor como entrada.

Geração Aumentada de Recuperação (RAG) com Armazenamento de Vetores

As abstrações do repositório de vetores são uma api de baixo nível para adicionar e recuperar dados de repositórios de vetores. O Kernel Semântico tem suporte interno para usar qualquer uma das implementações do Vector Store para RAG. Isso é feito encapsulando VectorSearchBase[TKey, TModel] com VectorizedSearchMixin[Tmodel], VectorizableTextSearchMixin[TModel] ou VectorTextSearch[TModel] e expondo-o como uma implementação de Pesquisa de Texto.

Dica

Para saber mais sobre como usar armazenamento de vetores para RAG, consulte Como usar armazenamento de vetores com pesquisa de texto do Kernel Semântico.

Dica

Para saber mais sobre a pesquisa de texto, consulte O que é Pesquisa Semântica de Texto do Kernel?

A abstração do Armazenamento de Vetor

As principais interfaces na abstração do Vector Store são as seguintes.

com.microsoft.semantickernel.data.vectorstorage.VectorStore

VectorStore contém operações que se estendem por todas as coleções no repositório de vetores, por exemplo, listCollectionNames. Ele também fornece a capacidade de obter instâncias VectorStoreRecordCollection<Key, Record>.

com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordCollection<Key, Registrar>

VectorStoreRecordCollection<Key, Record> representa uma coleção. Essa coleção pode ou não existir, e a interface fornece métodos para verificar se a coleção existe, criá-la ou excluí-la. A interface também fornece métodos para inserir, obter e excluir registros. Por fim, a interface herda do VectorizedSearch<Record>, fornecendo recursos de busca em vetores.

com.microsoft.semantickernel.data.vectorsearch.VectorizedSearch<Record>

VectorizedSearch<Record> contém um método para fazer pesquisas vetoriais. VectorStoreRecordCollection<Key, Record> herda de VectorizedSearch<Record>, o que torna possível usar VectorizedSearch<Record> de forma independente nos casos em que apenas a pesquisa é precisa e não é preciso gerenciamento de registro ou coleção.

com.microsoft.semantickernel.data.vectorsearch.VectorizableTextSearch<Record>

VectorizableTextSearch<Record> contém um método para fazer pesquisas vetoriais em que o banco de dados vetorial tem a capacidade de gerar incorporações automaticamente. Por exemplo, você pode chamar esse método com uma string de texto e o banco de dados gerará a incorporação para você e pesquisará em um campo vetorial. Isso não é suportado por todos os bancos de dados vetoriais e, portanto, é implementado apenas por conectores selecionados.

Introdução aos Repositórios de Vetores

Importar os pacotes nuget necessários

Todas as interfaces de repositório de vetores e todas as classes relacionadas à abstração estão disponíveis no Microsoft.Extensions.VectorData.Abstractions pacote nuget. Cada implementação de repositório de vetores está disponível em seu próprio pacote nuget. Para obter uma lista de implementações conhecidas, consulte a página Conectores prontos para uso.

O pacote de abstrações pode ser adicionado assim.

dotnet add package Microsoft.Extensions.VectorData.Abstractions

Definir seu modelo de dados

As abstrações do Vector Store usam uma primeira abordagem de modelo para interagir com bancos de dados. Isso significa que a primeira etapa é definir um modelo de dados que mapeie o esquema de armazenamento. Para ajudar as implementações a criar coleções de registros e mapear para o esquema de armazenamento, o modelo pode ser anotado para indicar a função de cada propriedade.

using Microsoft.Extensions.VectorData;

public class Hotel
{
    [VectorStoreKey]
    public ulong HotelId { get; set; }

    [VectorStoreData(IsIndexed = true)]
    public string HotelName { get; set; }

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

    [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineSimilarity, IndexKind = IndexKind.Hnsw)]
    public ReadOnlyMemory<float>? DescriptionEmbedding { get; set; }

    [VectorStoreData(IsIndexed = true)]
    public string[] Tags { get; set; }
}
from dataclasses import dataclass, field
from typing import Annotated
from semantic_kernel.data.vector import (
    DistanceFunction,
    IndexKind,
    VectorStoreField,
    vectorstoremodel,
)

@vectorstoremodel
@dataclass
class Hotel:
    hotel_id: Annotated[str, VectorStoreField('key')] = field(default_factory=lambda: str(uuid4()))
    hotel_name: Annotated[str, VectorStoreField('data', is_filterable=True)]
    description: Annotated[str, VectorStoreField('data', is_full_text_searchable=True)]
    description_embedding: Annotated[list[float], VectorStoreField('vector', dimensions=4, distance_function=DistanceFunction.COSINE, index_kind=IndexKind.HNSW)]
    tags: Annotated[list[str], VectorStoreField('data', is_filterable=True)]
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordData;
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordKey;
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordVector;
import com.microsoft.semantickernel.data.vectorstorage.definition.DistanceFunction;
import com.microsoft.semantickernel.data.vectorstorage.definition.IndexKind;

import java.util.Collections;
import java.util.List;

public class Hotel {
    @VectorStoreRecordKey
    private String hotelId;

    @VectorStoreRecordData(isFilterable = true)
    private String name;

    @VectorStoreRecordData(isFullTextSearchable = true)
    private String description;

    @VectorStoreRecordVector(dimensions = 4, indexKind = IndexKind.HNSW, distanceFunction = DistanceFunction.COSINE_DISTANCE)
    private List<Float> descriptionEmbedding;

    @VectorStoreRecordData(isFilterable = true)
    private List<String> tags;

    public Hotel() { }

    public Hotel(String hotelId, String name, String description, List<Float> descriptionEmbedding, List<String> tags) {
        this.hotelId = hotelId;
        this.name = name;
        this.description = description;
        this.descriptionEmbedding = Collections.unmodifiableList(descriptionEmbedding);
        this.tags = Collections.unmodifiableList(tags);
    }

    public String getHotelId() { return hotelId; }
    public String getName() { return name; }
    public String getDescription() { return description; }
    public List<Float> getDescriptionEmbedding() { return descriptionEmbedding; }
    public List<String> getTags() { return tags; }
}

Dica

Para obter mais informações sobre como anotar seu modelo de dados, consulte definindo seu modelo de dados.

Dica

Para uma alternativa à anotação do seu modelo de dados, consulte definir seu esquema com uma definição de registro.

Conecte-se ao seu banco de dados e selecione uma coleção

Depois de definir seu modelo de dados, a próxima etapa é criar uma instância do VectorStore para o banco de dados de sua escolha e selecionar uma coleção de registros.

Neste exemplo, usaremos Qdrant. Portanto, você precisará importar o pacote NuGet Qdrant.

dotnet add package Microsoft.SemanticKernel.Connectors.Qdrant --prerelease

Se você quiser executar o Qdrant localmente usando o Docker, use o comando a seguir para iniciar o contêiner Qdrant com as configurações usadas neste exemplo.

docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant:latest

Para verificar se a instância do Qdrant está em execução corretamente, visite o painel Qdrant integrado ao contêiner do Docker Qdrant: http://localhost:6333/dashboard

Como os bancos de dados oferecem suporte a muitos tipos diferentes de chaves e registros, permitimos que você especifique o tipo de chave e registro para sua coleção usando genéricos. No nosso caso, o tipo de registro será a Hotel classe que já definimos, e o tipo de chave será ulong, já que a HotelId propriedade é a ulong e Qdrant suporta apenas Guid ou ulong chaves.

using Microsoft.SemanticKernel.Connectors.Qdrant;
using Qdrant.Client;

// Create a Qdrant VectorStore object
var vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true);

// Choose a collection from the database and specify the type of key and record stored in it via Generic parameters.
var collection = vectorStore.GetCollection<ulong, Hotel>("skhotels");

Como os bancos de dados oferecem suporte a muitos tipos diferentes de chaves e registros, permitimos que você especifique o tipo de chave e registro para sua coleção usando genéricos. No nosso caso, o tipo de registro será a Hotel classe que já definimos, e o tipo de chave será str, já que a HotelId propriedade é a str e Qdrant suporta apenas str ou int chaves.

from semantic_kernel.connectors.qdrant import QdrantCollection

# Create a collection specify the type of key and record stored in it via Generic parameters.
collection: QdrantCollection[str, Hotel] = QdrantCollection(
    record_type=Hotel,
    collection_name="skhotels" # this is optional, you can also specify the collection_name in the vectorstoremodel decorator.
)

Como os bancos de dados oferecem suporte a muitos tipos diferentes de chaves e registros, permitimos que você especifique o tipo de chave e registro para sua coleção usando genéricos. No nosso caso, o tipo de registro será a classe Hotel que já definimos, e o tipo de chave será String, uma vez que a propriedade hotelId é um String e o armazenamento JDBC só suporta chaves String.

import com.microsoft.semantickernel.data.jdbc.JDBCVectorStore;
import com.microsoft.semantickernel.data.jdbc.JDBCVectorStoreOptions;
import com.microsoft.semantickernel.data.jdbc.JDBCVectorStoreRecordCollectionOptions;
import com.microsoft.semantickernel.data.jdbc.mysql.MySQLVectorStoreQueryProvider;
import com.mysql.cj.jdbc.MysqlDataSource;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Create a MySQL data source
        var dataSource = new MysqlDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/sk");
        dataSource.setPassword("root");
        dataSource.setUser("root");

        // Create a JDBC vector store
        var vectorStore = JDBCVectorStore.builder()
            .withDataSource(dataSource)
            .withOptions(
                JDBCVectorStoreOptions.builder()
                    .withQueryProvider(MySQLVectorStoreQueryProvider.builder()
                        .withDataSource(dataSource)
                        .build())
                    .build()
            )
            .build();

        // Get a collection from the vector store
        var collection = vectorStore.getCollection("skhotels",
            JDBCVectorStoreRecordCollectionOptions.<Hotel>builder()
                .withRecordClass(Hotel.class)
                .build()
        );
    }
}

Dica

Para obter mais informações sobre quais tipos de chave e campo cada implementação do Vector Store dá suporte, consulte a documentação para cada implementação.

Criar a coleção e adicionar registros

// Placeholder embedding generation method.
async Task<ReadOnlyMemory<float>> GenerateEmbeddingAsync(string textToVectorize)
{
    // your logic here
}

// Create the collection if it doesn't exist yet.
await collection.EnsureCollectionExistsAsync();

// Upsert a record.
string descriptionText = "A place where everyone can be happy.";
ulong hotelId = 1;

// Create a record and generate a vector for the description using your chosen embedding generation implementation.
await collection.UpsertAsync(new Hotel
{
    HotelId = hotelId,
    HotelName = "Hotel Happy",
    Description = descriptionText,
    DescriptionEmbedding = await GenerateEmbeddingAsync(descriptionText),
    Tags = new[] { "luxury", "pool" }
});

// Retrieve the upserted record.
Hotel? retrievedHotel = await collection.GetAsync(hotelId);

Criar a coleção e adicionar registros

# Create the collection if it doesn't exist yet.
await collection.ensure_collection_exists()

# Upsert a record.
description = "A place where everyone can be happy."
hotel_id = "1"

await collection.upsert(Hotel(
    hotel_id = hotel_id,
    hotel_name = "Hotel Happy",
    description = description,
    description_embedding = await GenerateEmbeddingAsync(description),
    tags = ["luxury", "pool"]
))

# Retrieve the upserted record.
retrieved_hotel = await collection.get(hotel_id)
// Create the collection if it doesn't exist yet.
collection.createCollectionAsync().block();

// Upsert a record.
var description = "A place where everyone can be happy";
var hotelId = "1";
var hotel = new Hotel(
    hotelId, 
    "Hotel Happy", 
    description, 
    generateEmbeddingsAsync(description).block(), 
    List.of("luxury", "pool")
);

collection.upsertAsync(hotel, null).block();

// Retrieve the upserted record.
var retrievedHotel = collection.getAsync(hotelId, null).block();

Dica

Para obter mais informações sobre como gerar incorporações, consulte geração de incorporações.

// Placeholder embedding generation method.
async Task<ReadOnlyMemory<float>> GenerateEmbeddingAsync(string textToVectorize)
{
    // your logic here
}

// Generate a vector for your search text, using your chosen embedding generation implementation.
ReadOnlyMemory<float> searchVector = await GenerateEmbeddingAsync("I'm looking for a hotel where customer happiness is the priority.");

// Do the search.
var searchResult = collection.SearchAsync(searchVector, top: 1);

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

Faça uma pesquisa vetorial

O método de pesquisa pode ser usado para pesquisar registros na coleção. Ele recebe uma cadeia de caracteres, que é vetorizada usando a configuração de geração de incorporação no modelo ou na coleção, ou um vetor que já foi gerado.

# Do a search.
search_result = await collection.search("I'm looking for a hotel where customer happiness is the priority.", vector_property_name="description_embedding", top=3)

# Inspect the returned hotels.
async for result in search_result.results:
    print(f"Found hotel description: {result.record.description}")

Criar uma função de pesquisa

Para criar uma função de pesquisa simples que pode ser usada para pesquisar hotéis, você pode usar o create_search_function método na coleção.

O nome e a descrição, bem como os nomes e descrições dos parâmetros, são usados para gerar uma assinatura de função que é enviada para o LLM quando a chamada de função é usada. Isso significa que ajustar isso pode ser útil para que o LLM gere a chamada de função correta.

collection.create_search_function(
    function_name="hotel_search",
    description="A hotel search engine, allows searching for hotels in specific cities, "
    "you do not have to specify that you are searching for hotels, for all, use `*`."
)

Há muitos outros parâmetros, por exemplo, essa é a aparência de uma versão mais complexa, observe a personalização dos parâmetros e a string_mapper função usada para converter o registro em uma cadeia de caracteres.

from semantic_kernel.function import KernelParameterMetadata

collection.create_search_function(
    function_name="hotel_search",
    description="A hotel search engine, allows searching for hotels in specific cities, "
    "you do not have to specify that you are searching for hotels, for all, use `*`.",
    search_type="keyword_hybrid", # default is "vector"
    parameters=[
        KernelParameterMetadata(
            name="query",
            description="The terms you want to search for in the hotel database.",
            type="str",
            is_required=True,
            type_object=str,
        ),
        KernelParameterMetadata(
            name="tags",
            description="The tags you want to search for in the hotel database, use `*` to match all.",
            type="str",
            type_object=str,
            default_value="*",
        ),
        KernelParameterMetadata(
            name="top",
            description="Number of results to return.",
            type="int",
            default_value=5,
            type_object=int,
        ),
    ],
    # finally, we specify the `string_mapper` function that is used to convert the record to a string.
    # This is used to make sure the relevant information from the record is passed to the LLM.
    string_mapper=lambda x: f"Hotel {x.record.hotel_name}: {x.record.description}. Tags: {x.record.tags} (hotel_id: {x.record.hotel_id}) ", 
)

Dica

Para obter mais exemplos, incluindo exemplos de ponta a ponta, consulte o repositório De exemplos de kernel semântico.

// Generate a vector for your search text, using your chosen embedding generation implementation.
// Just showing a placeholder method here for brevity.
var searchVector = generateEmbeddingsAsync("I'm looking for a hotel where customer happiness is the priority.").block();

// Do the search.
var searchResult = collection.searchAsync(searchVector, VectorSearchOptions.builder()
    .withTop(1).build()
).block();

Hotel record = searchResult.getResults().get(0).getRecord();
System.out.printf("Found hotel description: %s\n", record.getDescription());

Dica

Para obter mais informações sobre como gerar incorporações, consulte geração de incorporações.

Próximas etapas