Compartir a través de


Ingesta de datos en un almacén de vectores mediante kernel semántico (versión preliminar)

Advertencia

La funcionalidad Almacén de vectores de kernel semántico está en versión preliminar y las mejoras que requieren cambios importantes pueden producirse en circunstancias limitadas antes de la versión.

En este artículo se muestra cómo crear una aplicación para

  1. Tomar texto de cada párrafo de un documento de Microsoft Word
  2. Generación de una inserción para cada párrafo
  3. Upsert the text, embedding and a reference to the original location into a Redis instance.

Requisitos previos

Para este ejemplo, necesitará

  1. Un modelo de generación de inserción hospedado en Azure u otro proveedor de su elección.
  2. Instancia de Redis o Docker Desktop para que pueda ejecutar Redis localmente.
  3. Un documento de Word para analizar y cargar. Este es un archivo ZIP que contiene un documento de Word de ejemplo que puede descargar y usar: vector-store-data-ingestion-input.zip.

Configuración de Redis

Si ya tiene una instancia de Redis, puede usarla. Si prefiere probar el proyecto localmente, puede iniciar fácilmente un contenedor de Redis mediante Docker.

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

Para comprobar que se está ejecutando correctamente, visite http://localhost:8001/redis-stack/browser en el explorador.

En el resto de estas instrucciones se supone que usa este contenedor mediante la configuración anterior.

Creación del proyecto

Cree un nuevo proyecto y agregue referencias de paquete nuget para el conector de Redis desde kernel semántico, el paquete open xml con el que leer el documento de palabras con y el conector openAI del kernel semántico para generar incrustaciones.

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

Agregar un modelo de datos

Para cargar datos, primero es necesario describir el formato que deben tener los datos en la base de datos. Para ello, se crea un modelo de datos con atributos que describen la función de cada propiedad.

Agregue un nuevo archivo al proyecto llamado TextParagraph.cs y agréguele el siguiente modelo.

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

Tenga en cuenta que pasamos el valor 1536 a VectorStoreRecordVectorAttribute. Este es el tamaño de dimensión del vector y tiene que coincidir con el tamaño del vector que genera el generador de inserción elegido.

Sugerencia

Para obtener más información sobre cómo anotar el modelo de datos y qué opciones adicionales están disponibles para cada atributo, consulte definición del modelo de datos.

Leer los párrafos del documento

Necesitamos código para leer el documento de palabras y encontrar el texto de cada párrafo en él.

Agregue un nuevo archivo al proyecto llamado DocumentReader.cs y agregue la siguiente clase para leer los párrafos de 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
                };
            }
        }
    }
}

Generar incrustaciones y cargar los datos

Necesitaremos código para generar inserciones y cargar los párrafos en Redis. Vamos a hacerlo en una clase independiente.

Agregue un nuevo archivo llamado DataUploader.cs y agréguele la siguiente clase.

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

Colocación de todo junto

Por último, tenemos que juntar las diferentes piezas. En este ejemplo, usaremos el contenedor de inserción de dependencias de kernel semántico, pero también es posible usar cualquier IServiceCollection contenedor basado.

Agregue el código siguiente al Program.cs archivo para crear el contenedor, registrar el almacén de vectores de Redis y registrar el servicio de inserción. Asegúrese de reemplazar la configuración de generación de inserción de texto por sus propios 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 paso, queremos leer los párrafos de nuestro documento de palabras y llamar al cargador de datos para generar las inserciones y cargar los párrafos.

// 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 los datos en Redis

Vaya al explorador de pila de Redis, por ejemplo http://localhost:8001/redis-stack/browser , donde ahora debería poder ver los párrafos cargados. Este es un ejemplo de lo que debería ver para uno de los párrafos cargados.

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

Próximamente

Próximamente se proporcionan instrucciones adicionales

Próximamente

Próximamente se proporcionan instrucciones adicionales