Jak pozyskiwać dane do magazynu wektorów przy użyciu jądra semantycznego (wersja zapoznawcza)
Ostrzeżenie
Funkcjonalność semantycznego magazynu wektorów jądra jest dostępna w wersji zapoznawczej, a ulepszenia, które wymagają zmian powodujących niezgodność, mogą nadal się zdarzać w ograniczonych okolicznościach przed ostatecznym wydaniem.
W tym artykule pokazano, jak utworzyć aplikację
- Weź tekst z każdego akapitu w dokumencie Microsoft Word
- Generowanie osadzania dla każdego akapitu
- Aktualizuj lub wstawiaj tekst, osadzony element oraz odniesienie do oryginalnej lokalizacji w instancji Redis.
Wymagania wstępne
W przypadku tego przykładu będziesz potrzebować
- Model generowania osadów hostowany na platformie Azure lub u innego wybranego dostawcy.
- Wystąpienie usługi Redis lub docker Desktop, dzięki czemu można uruchomić usługę Redis lokalnie.
- Dokument programu Word do analizowania i ładowania. Oto plik zip zawierający przykładowy dokument programu Word, który można pobrać i użyć: vector-store-data-ingestion-input.zip.
Konfigurowanie usługi Redis
Jeśli masz już wystąpienie usługi Redis, możesz go użyć. Jeśli wolisz przetestować projekt lokalnie, możesz łatwo uruchomić kontener Redis przy użyciu platformy Docker.
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
Aby sprawdzić, czy działa pomyślnie, odwiedź stronę http://localhost:8001/redis-stack/browser w przeglądarce.
W pozostałych instrukcjach przyjęto założenie, że używasz tego kontenera przy użyciu powyższych ustawień.
Tworzenie projektu
Utwórz nowy projekt i dodaj referencje pakietów NuGet dla łącznika Redis z Semantic Kernel, pakietu Open XML do odczytu dokumentu Word oraz łącznika OpenAI z Semantic Kernel do generowania embeddings.
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
Dodawanie modelu danych
Aby przekazać dane, należy najpierw opisać format danych, które powinny znajdować się w bazie danych. Możemy to zrobić, tworząc model danych z atrybutami, które opisują funkcję każdej właściwości.
Dodaj nowy plik do projektu o nazwie TextParagraph.cs
i dodaj do niego następujący model.
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; }
}
Należy pamiętać, że przekazujemy wartość 1536
do VectorStoreRecordVectorAttribute
. Jest to rozmiar wymiaru wektora i musi być zgodny z rozmiarem wektora, który generuje wybrany generator osadzania.
Napiwek
Aby uzyskać więcej informacji na temat dodawania adnotacji do modelu danych i dostępnych dodatkowych opcji dla każdego atrybutu, zapoznaj się z opisem modelu danych.
Odczytywanie akapitów w dokumencie
Potrzebujemy kodu, aby odczytać dokument Word i znaleźć tekst każdego akapitu.
Dodaj nowy plik do projektu o nazwie DocumentReader.cs
i dodaj następującą klasę, aby odczytać akapity z dokumentu.
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
};
}
}
}
}
Generowanie osadzeń i przesyłanie danych
Będziemy potrzebować kodu, aby wygenerować embeddingi i przesłać akapity do Redis. Zróbmy to w oddzielnej klasie.
Dodaj do niego nowy plik o nazwie DataUploader.cs
i dodaj do niego następującą klasę.
#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();
}
}
}
Połączenie tego wszystkiego
Wreszcie musimy zebrać różne kawałki.
W tym przykładzie użyjemy kontenera do wstrzykiwania zależności Semantic Kernel, ale można również użyć dowolnego kontenera opartego na IServiceCollection
.
Dodaj następujący kod do Program.cs
pliku, aby utworzyć kontener, zarejestrować magazyn wektorów Redis i zarejestrować usługę osadzania.
Pamiętaj, aby zastąpić ustawienia generowania osadzania tekstu własnymi wartościami.
#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 OpenAI 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>();
W ostatnim kroku chcemy odczytać akapity z naszego dokumentu Word i uruchomić moduł wgrywający dane, aby wygenerować embeddingi i przesłać akapity.
// 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);
Wyświetlanie danych w usłudze Redis
Przejdź do przeglądarki stosu Redis, np. http://localhost:8001/redis-stack/browser, gdzie powinny być teraz widoczne przesłane akapity. Oto przykład tego, co powinno zostać wyświetlone dla jednego z przekazanych akapitów.
{
"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" : [...]
}
Wkrótce
Dalsze instrukcje będą wkrótce
Wkrótce
Dalsze instrukcje będą wkrótce