Partilhar via


Como ingerir dados em um repositório vetorial usando o kernel semântico (visualização)

Aviso

A funcionalidade Semantic Kernel Vetor Store está em pré-visualização, e melhorias que exigem alterações de quebra ainda podem ocorrer em circunstâncias limitadas antes do lançamento.

Este artigo demonstrará como criar um aplicativo para

  1. Retirar texto de cada parágrafo em um documento do Microsoft Word
  2. Gerar uma incorporação para cada parágrafo
  3. Atualize o texto, a incorporação e uma referência ao local original em uma instância do Redis.

Pré-requisitos

Para este exemplo, você vai precisar de

  1. Um modelo de geração de incorporação hospedado no Azure ou em outro provedor de sua escolha.
  2. Uma instância do Redis ou do Docker Desktop para que você possa executar o Redis localmente.
  3. Um documento do Word para analisar e carregar. Aqui está um zip contendo um exemplo de documento do Word que você pode baixar e usar: vector-store-data-ingestion-input.zip.

Configuração Redis

Se você já tiver uma instância do Redis, poderá usá-la. Se preferir testar seu projeto localmente, você pode facilmente iniciar um contêiner Redis usando o docker.

docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest

Para verificar se está a ser executado com sucesso, visite http://localhost:8001/redis-stack/browser no seu navegador.

O restante destas instruções assumirá que você está usando esse contêiner usando as configurações acima.

Criar o seu projeto

Crie um novo projeto e adicione referências de pacote nuget para o conector Redis do Semantic Kernel, o pacote open xml para ler o documento do Word e o conector OpenAI do Semantic Kernel para gerar incorporações.

dotnet new console --framework net8.0 --name SKVectorIngest
cd SKVectorIngest
dotnet add package Microsoft.SemanticKernel.Connectors.AzureOpenAI
dotnet add package Microsoft.SemanticKernel.Connectors.Redis --prerelease
dotnet add package DocumentFormat.OpenXml

Adicionar um modelo de dados

Para carregar dados, precisamos primeiro descrever o formato que os dados devem ter no banco de dados. Podemos fazer isso criando um modelo de dados com atributos que descrevem a função de cada propriedade.

Adicione um novo arquivo ao projeto chamado TextParagraph.cs e adicione o seguinte modelo a ele.

using Microsoft.Extensions.VectorData;

namespace SKVectorIngest;

internal class TextParagraph
{
    /// <summary>A unique key for the text paragraph.</summary>
    [VectorStoreRecordKey]
    public required string Key { get; init; }

    /// <summary>A uri that points at the original location of the document containing the text.</summary>
    [VectorStoreRecordData]
    public required string DocumentUri { get; init; }

    /// <summary>The id of the paragraph from the document containing the text.</summary>
    [VectorStoreRecordData]
    public required string ParagraphId { get; init; }

    /// <summary>The text of the paragraph.</summary>
    [VectorStoreRecordData]
    public required string Text { get; init; }

    /// <summary>The embedding generated from the Text.</summary>
    [VectorStoreRecordVector(1536)]
    public ReadOnlyMemory<float> TextEmbedding { get; set; }
}

Observe que estamos passando o valor 1536 para o VectorStoreRecordVectorAttribute. Este é o tamanho da dimensão do vetor e tem que corresponder ao tamanho do vetor que o gerador de incorporação escolhido produz.

Gorjeta

Para obter mais informações sobre como anotar seu modelo de dados e quais opções adicionais estão disponíveis para cada atributo, consulte Definindo seu modelo de dados.

Leia os parágrafos do documento

Precisamos de algum código para ler o documento da palavra e encontrar o texto de cada parágrafo nele.

Adicione um novo arquivo ao projeto chamado DocumentReader.cs e adicione a seguinte classe para ler os parágrafos de um documento.

using System.Text;
using System.Xml;
using DocumentFormat.OpenXml.Packaging;

namespace SKVectorIngest;

internal class DocumentReader
{
    public static IEnumerable<TextParagraph> ReadParagraphs(Stream documentContents, string documentUri)
    {
        // Open the document.
        using WordprocessingDocument wordDoc = WordprocessingDocument.Open(documentContents, false);
        if (wordDoc.MainDocumentPart == null)
        {
            yield break;
        }

        // Create an XmlDocument to hold the document contents and load the document contents into the XmlDocument.
        XmlDocument xmlDoc = new XmlDocument();
        XmlNamespaceManager nsManager = new XmlNamespaceManager(xmlDoc.NameTable);
        nsManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
        nsManager.AddNamespace("w14", "http://schemas.microsoft.com/office/word/2010/wordml");

        xmlDoc.Load(wordDoc.MainDocumentPart.GetStream());

        // Select all paragraphs in the document and break if none found.
        XmlNodeList? paragraphs = xmlDoc.SelectNodes("//w:p", nsManager);
        if (paragraphs == null)
        {
            yield break;
        }

        // Iterate over each paragraph.
        foreach (XmlNode paragraph in paragraphs)
        {
            // Select all text nodes in the paragraph and continue if none found.
            XmlNodeList? texts = paragraph.SelectNodes(".//w:t", nsManager);
            if (texts == null)
            {
                continue;
            }

            // Combine all non-empty text nodes into a single string.
            var textBuilder = new StringBuilder();
            foreach (XmlNode text in texts)
            {
                if (!string.IsNullOrWhiteSpace(text.InnerText))
                {
                    textBuilder.Append(text.InnerText);
                }
            }

            // Yield a new TextParagraph if the combined text is not empty.
            var combinedText = textBuilder.ToString();
            if (!string.IsNullOrWhiteSpace(combinedText))
            {
                Console.WriteLine("Found paragraph:");
                Console.WriteLine(combinedText);
                Console.WriteLine();

                yield return new TextParagraph
                {
                    Key = Guid.NewGuid().ToString(),
                    DocumentUri = documentUri,
                    ParagraphId = paragraph.Attributes?["w14:paraId"]?.Value ?? string.Empty,
                    Text = combinedText
                };
            }
        }
    }
}

Gerar incorporações e carregar os dados

Precisaremos de algum código para gerar incorporações e carregar os parágrafos para Redis. Vamos fazer isso em uma classe separada.

Adicione um novo arquivo chamado DataUploader.cs e adicione a seguinte classe a ele.

#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Embeddings;

namespace SKVectorIngest;

internal class DataUploader(IVectorStore vectorStore, ITextEmbeddingGenerationService textEmbeddingGenerationService)
{
    /// <summary>
    /// Generate an embedding for each text paragraph and upload it to the specified collection.
    /// </summary>
    /// <param name="collectionName">The name of the collection to upload the text paragraphs to.</param>
    /// <param name="textParagraphs">The text paragraphs to upload.</param>
    /// <returns>An async task.</returns>
    public async Task GenerateEmbeddingsAndUpload(string collectionName, IEnumerable<TextParagraph> textParagraphs)
    {
        var collection = vectorStore.GetCollection<string, TextParagraph>(collectionName);
        await collection.CreateCollectionIfNotExistsAsync();

        foreach (var paragraph in textParagraphs)
        {
            // Generate the text embedding.
            Console.WriteLine($"Generating embedding for paragraph: {paragraph.ParagraphId}");
            paragraph.TextEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(paragraph.Text);

            // Upload the text paragraph.
            Console.WriteLine($"Upserting paragraph: {paragraph.ParagraphId}");
            await collection.UpsertAsync(paragraph);

            Console.WriteLine();
        }
    }
}

Juntar tudo

Finalmente, precisamos juntar as diferentes peças. Neste exemplo, usaremos o contêiner de injeção de dependência do Kernel Semântico, mas também é possível usar qualquer IServiceCollection contêiner baseado.

Adicione o seguinte código ao seu Program.cs arquivo para criar o contêiner, registrar o repositório de vetores Redis e registrar o serviço de incorporação. Certifique-se de substituir as configurações de geração de incorporação de texto por seus próprios valores.

#pragma warning disable SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0020 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using SKVectorIngest;

// Replace with your values.
var deploymentName = "text-embedding-ada-002";
var endpoint = "https://sksample.openai.azure.com/";
var apiKey = "your-api-key";

// Register Azure Open AI text embedding generation service and Redis vector store.
var builder = Kernel.CreateBuilder()
    .AddAzureOpenAITextEmbeddingGeneration(deploymentName, endpoint, apiKey)
    .AddRedisVectorStore("localhost:6379");

// Register the data uploader.
builder.Services.AddSingleton<DataUploader>();

// Build the kernel and get the data uploader.
var kernel = builder.Build();
var dataUploader = kernel.Services.GetRequiredService<DataUploader>();

Como último passo, queremos ler os parágrafos do nosso documento do Word e chamar o carregador de dados para gerar as incorporações e carregar os parágrafos.

// Load the data.
var textParagraphs = DocumentReader.ReadParagraphs(
    new FileStream(
        "vector-store-data-ingestion-input.docx",
        FileMode.Open),
    "file:///c:/vector-store-data-ingestion-input.docx");

await dataUploader.GenerateEmbeddingsAndUpload(
    "sk-documentation",
    textParagraphs);

Ver os seus dados no Redis

Navegue até o navegador de pilha Redis, por exemplo http://localhost:8001/redis-stack/browser , onde agora você deve ser capaz de ver seus parágrafos carregados. Aqui está um exemplo do que você deve ver para um dos parágrafos carregados.

{
    "DocumentUri" : "file:///c:/vector-store-data-ingestion-input.docx",
    "ParagraphId" : "14CA7304",
    "Text" : "Version 1.0+ support across C#, Python, and Java means it’s reliable, committed to non breaking changes. Any existing chat-based APIs are easily expanded to support additional modalities like voice and video.",
    "TextEmbedding" : [...]
}

Brevemente

Mais instruções em breve

Brevemente

Mais instruções em breve