Partager via


Comment ingérer des données dans un magasin de vecteurs à l’aide du noyau sémantique (préversion)

Avertissement

La fonctionnalité de magasin de vecteurs du noyau sémantique est en préversion et des améliorations nécessitant des modifications cassants peuvent toujours se produire dans des circonstances limitées avant la mise en production.

Cet article explique comment créer une application à

  1. Prendre du texte à partir de chaque paragraphe d’un document Microsoft Word
  2. Générer une incorporation pour chaque paragraphe
  3. Upsert le texte, l’incorporation et une référence à l’emplacement d’origine dans une instance Redis.

Prérequis

Pour cet exemple, vous aurez besoin de

  1. Modèle de génération incorporé hébergé dans Azure ou un autre fournisseur de votre choix.
  2. Instance de Redis ou Docker Desktop afin de pouvoir exécuter Redis localement.
  3. Document Word à analyser et charger. Voici un fichier zip contenant un exemple de document Word que vous pouvez télécharger et utiliser : vector-store-data-ingestion-input.zip.

Configurer Redis

Si vous disposez déjà d’une instance Redis, vous pouvez l’utiliser. Si vous préférez tester votre projet localement, vous pouvez facilement démarrer un conteneur Redis à l’aide de Docker.

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

Pour vérifier qu’elle s’exécute correctement, visitez http://localhost:8001/redis-stack/browser votre navigateur.

Le reste de ces instructions suppose que vous utilisez ce conteneur à l’aide des paramètres ci-dessus.

Créer votre projet

Créez un projet et ajoutez des références de package nuget pour le connecteur Redis à partir du noyau sémantique, le package open xml pour lire le document word avec et le connecteur OpenAI à partir du noyau sémantique pour générer des incorporations.

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

Ajouter un modèle de données

Pour charger des données, nous devons d’abord décrire le format que les données doivent avoir dans la base de données. Pour ce faire, nous pouvons créer un modèle de données avec des attributs qui décrivent la fonction de chaque propriété.

Ajoutez un nouveau fichier au projet appelé TextParagraph.cs et ajoutez-y le modèle suivant.

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

Notez que nous transmettons la valeur 1536 au VectorStoreRecordVectorAttribute. Il s’agit de la taille de dimension du vecteur et doit correspondre à la taille du vecteur produit par votre générateur d’incorporation choisi.

Conseil

Pour plus d’informations sur la façon d’annoter votre modèle de données et les options supplémentaires disponibles pour chaque attribut, reportez-vous à la définition de votre modèle de données.

Lire les paragraphes du document

Nous avons besoin d’un code pour lire le document word et trouver le texte de chaque paragraphe dans celui-ci.

Ajoutez un nouveau fichier au projet appelé DocumentReader.cs et ajoutez la classe suivante pour lire les paragraphes d’un document.

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

Générer des incorporations et charger les données

Nous aurons besoin d’un code pour générer des incorporations et charger les paragraphes dans Redis. Nous allons le faire dans une classe distincte.

Ajoutez un nouveau fichier appelé DataUploader.cs et ajoutez la classe suivante à celle-ci.

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

Assemblage

Enfin, nous devons rassembler les différentes pièces. Dans cet exemple, nous allons utiliser le conteneur d’injection de dépendances du noyau sémantique, mais il est également possible d’utiliser n’importe quel IServiceCollection conteneur basé.

Ajoutez le code suivant à votre Program.cs fichier pour créer le conteneur, inscrivez le magasin de vecteurs Redis et inscrivez le service d’incorporation. Veillez à remplacer les paramètres de génération d’incorporation de texte par vos propres valeurs.

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

À la dernière étape, nous voulons lire les paragraphes de notre document word et appeler le chargeur de données pour générer les incorporations et charger les paragraphes.

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

Afficher vos données dans Redis

Accédez au navigateur de pile Redis, par exemple, http://localhost:8001/redis-stack/browser où vous devriez maintenant être en mesure de voir vos paragraphes chargés. Voici un exemple de ce que vous devez voir pour l’un des paragraphes chargés.

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

Bientôt disponible

D’autres instructions seront bientôt disponibles

Bientôt disponible

D’autres instructions seront bientôt disponibles