Condividi tramite


Come inserire dati in un archivio vettoriale usando il kernel semantico (anteprima)

Avviso

La funzionalità di archiviazione vettoriale del kernel semantico è in anteprima e i miglioramenti che richiedono modifiche di rilievo possono ancora verificarsi in circostanze limitate prima del rilascio.

Questo articolo illustra come creare un'applicazione

  1. Prendere testo da ogni paragrafo in un documento di Microsoft Word
  2. Generare un incorporamento per ogni paragrafo
  3. Esegue l'upsert del testo, dell'incorporamento e di un riferimento alla posizione originale in un'istanza di Redis.

Prerequisiti

Per questo esempio è necessario

  1. Modello di generazione di incorporamento ospitato in Azure o in un altro provider di propria scelta.
  2. Istanza di Redis o Docker Desktop in modo da poter eseguire Redis in locale.
  3. Documento di Word da analizzare e caricare. Ecco un file ZIP contenente un documento di Word di esempio che è possibile scaricare e usare: vector-store-data-ingestion-input.zip.

Configurare Redis

Se si dispone già di un'istanza di Redis, è possibile usarla. Se si preferisce testare il progetto in locale, è possibile avviare facilmente un contenitore Redis usando Docker.

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

Per verificare che sia in esecuzione correttamente, visitare http://localhost:8001/redis-stack/browser nel browser.

Le altre istruzioni presuppongono che si stia usando questo contenitore usando le impostazioni precedenti.

Creare il progetto

Creare un nuovo progetto e aggiungere riferimenti al pacchetto NuGet per il connettore Redis dal kernel semantico, il pacchetto open xml per leggere il documento di word con e il connettore OpenAI dal kernel semantico per generare incorporamenti.

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

Aggiungere un modello di dati

Per caricare i dati, è necessario innanzitutto descrivere il formato dei dati nel database. A tale scopo, è possibile creare un modello di dati con attributi che descrivono la funzione di ogni proprietà.

Aggiungere un nuovo file al progetto denominato TextParagraph.cs e aggiungervi il modello seguente.

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; }
}

Si noti che il valore 1536 viene passato a VectorStoreRecordVectorAttribute. Si tratta della dimensione del vettore e deve corrispondere alle dimensioni del vettore prodotto dal generatore di incorporamento scelto.

Suggerimento

Per altre informazioni su come annotare il modello di dati e su quali opzioni aggiuntive sono disponibili per ogni attributo, vedere definizione del modello di dati.

Leggere i paragrafi nel documento

È necessario un codice per leggere il documento di parola e trovare il testo di ogni paragrafo.

Aggiungere un nuovo file al progetto denominato DocumentReader.cs e aggiungere la classe seguente per leggere i paragrafi da un 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
                };
            }
        }
    }
}

Generare incorporamenti e caricare i dati

Sarà necessario codice per generare incorporamenti e caricare i paragrafi in Redis. Eseguire questa operazione in una classe separata.

Aggiungere un nuovo file denominato DataUploader.cs e aggiungervi la classe seguente.

#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();
        }
    }
}

Combinare tutti gli elementi

Infine, dobbiamo mettere insieme i diversi pezzi. In questo esempio si userà il contenitore di inserimento delle dipendenze del kernel semantico, ma è anche possibile usare qualsiasi IServiceCollection contenitore basato.

Aggiungere il codice seguente al Program.cs file per creare il contenitore, registrare l'archivio vettoriale Redis e registrare il servizio di incorporamento. Assicurarsi di sostituire le impostazioni di generazione di incorporamento del testo con i propri valori.

#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>();

Come ultimo passaggio, si vogliono leggere i paragrafi dal documento di word e chiamare il caricatore di dati per generare gli incorporamenti e caricare i paragrafi.

// 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);

Visualizzare i dati in Redis

Passare al browser dello stack Redis, ad esempio http://localhost:8001/redis-stack/browser dove dovrebbe essere possibile visualizzare i paragrafi caricati. Di seguito è riportato un esempio di ciò che dovrebbe essere visualizzato per uno dei paragrafi caricati.

{
    "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" : [...]
}

Presto disponibile

Ulteriori istruzioni presto disponibili

Presto disponibile

Ulteriori istruzioni presto disponibili