Como ingerir dados em um Repositório de Vetores usando o Kernel Semântico (Versão Prévia)
Aviso
A funcionalidade do Repositório de Vetores do Kernel Semântico está em versão prévia e as melhorias que exigem alterações significativas ainda podem ocorrer em circunstâncias limitadas antes do lançamento.
Este artigo demonstrará como criar um aplicativo para
- Obter texto de cada parágrafo em um documento do Microsoft Word
- Gerar uma incorporação para cada parágrafo
- Upsert o texto, a incorporação e uma referência ao local original em uma instância do Redis.
Pré-requisitos
Para este exemplo, você precisará de:
- Um modelo de geração de inserção hospedado no Azure ou em outro provedor de sua escolha.
- Uma instância do Redis ou do Docker Desktop para que você possa executar o Redis localmente.
- Um documento do Word para analisar e carregar. Aqui está um zip contendo um documento do Word de amostra que você pode baixar e usar: vector-store-data-ingestion-input.zip.
Configurar 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 do Redis usando o docker.
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
Para verificar se ele está sendo executado com sucesso, visite http://localhost:8001/redis-stack/browser em seu navegador.
O restante dessas instruções pressupõe que você esteja usando esse contêiner usando as configurações acima.
Criar seu projeto
Crie um novo projeto e adicione referências de pacote nuget para o conector Redis do Kernel Semântico, o pacote xml aberto para ler o documento do Word e o conector OpenAI do Kernel Semântico para gerar inserçõ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 fazer upload de dados, precisamos primeiro descrever qual formato 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 para o VectorStoreRecordVectorAttribute
.1536
Este é o tamanho da dimensão do vetor e deve corresponder ao tamanho do vetor que o gerador de incorporação escolhido produz.
Dica
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 definição de seu modelo de dados.
Leia os parágrafos do documento
Precisamos de algum código para ler o documento do Word 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 fazer upload dos parágrafos para o Redis. Vamos fazer isso em uma aula 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();
}
}
}
Colocar tudo isso junto
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 código a seguir ao arquivo Program.cs
para criar o contêiner, registrar o repositório de vetores do Redis e registrar o serviço de inserçã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 última etapa, queremos ler os parágrafos do nosso documento do Word e chamar o uploader 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 seus dados no Redis
Navegue até o navegador de pilha Redis, por exemplo http://localhost:8001/redis-stack/browser , onde agora você poderá ver os 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" : [...]
}
Em breve
Mais instruções em breve
Em breve
Mais instruções em breve