Freigeben über


Erste Schritte mit der App-Inhaltssuche

Verwenden Sie die App-Inhaltssuche, um einen semantischen Index Ihrer In-App-Inhalte zu erstellen. Auf diese Weise können Benutzer Informationen basierend auf Bedeutungen statt nur auf Schlüsselwörtern finden. Der Index kann auch verwendet werden, um KI-Assistenten mit domänenspezifischem Wissen zu verbessern, um personalisiertere und kontextbezogene Ergebnisse zu erzielen.

Insbesondere erfahren Sie, wie Sie die AppContentIndexer-API verwenden, um:

  • Erstellen oder Öffnen eines Indexes des Inhalts in Ihrer App
  • Fügen Sie dem Index Textzeichenfolgen hinzu, und führen Sie dann eine Abfrage aus.
  • Verwalten der Komplexität langer Textzeichenfolgen
  • Bilddaten indizieren und dann nach relevanten Bildern suchen
  • Aktivieren von RAG-Szenarien (Rückgewinnungs-unterstützte Generierung)
  • Verwenden von AppContentIndexer in einem Hintergrundthread
  • Schließen Sie AppContentIndexer, wenn sie nicht mehr zum Freigeben von Ressourcen verwendet wird

Voraussetzungen

Informationen zu den Hardwareanforderungen der Windows AI-API und zum Konfigurieren Ihres Geräts zum erfolgreichen Erstellen von Apps mithilfe der Windows AI-APIs finden Sie unter "Erste Schritte beim Erstellen einer App mit Windows AI-APIs".

Paketidentitätsanforderung

Apps, die AppContentIndexer verwenden, müssen eine Paketidentität aufweisen, die nur für verpackte Apps (einschließlich der Apps mit externen Speicherorten) verfügbar ist. Um die semantische Indizierung und texterkennung (OCR) zu aktivieren, muss die App auch die systemaimodels Funktion deklarieren.

Erstellen oder Öffnen eines Indexes des Inhalts in Ihrer App

Um einen semantischen Index des Inhalts in Ihrer App zu erstellen, müssen Sie zunächst eine durchsuchbare Struktur einrichten, die Ihre App zum effizienten Speichern und Abrufen von Inhalten verwenden kann. Dieser Index fungiert als lokale semantische und lexikalische Suchmaschine für den Inhalt Ihrer App.

Um die AppContentIndexer-API zu verwenden, rufen Sie zuerst GetOrCreateIndex mit einem angegebenen Indexnamen auf. Wenn bereits ein Index mit diesem Namen für die aktuelle App-Identität und den aktuellen Benutzer vorhanden ist, wird er geöffnet. andernfalls wird ein neues erstellt.

public void SimpleGetOrCreateIndexSample()
{
    GetOrCreateIndexResult result = AppContentIndexer.GetOrCreateIndex("myindex");
    if (!result.Succeeded)
    {
        throw new InvalidOperationException($"Failed to open index. Status = '{result.Status}', Error = '{result.ExtendedError}'");
    }
    // If result.Succeeded is true, result.Status will either be CreatedNew or OpenedExisting
    if (result.Status == GetOrCreateIndexStatus.CreatedNew)
    {
        Console.WriteLine("Created a new index");
    }
    else if(result.Status == GetOrCreateIndexStatus.OpenedExisting)
    {
        Console.WriteLine("Opened an existing index");
    }
    using AppContentIndexer indexer = result.Indexer;
    // Use indexer...
}

In diesem Beispiel wird die Fehlerbehandlung des Fehlerszenarios beim Öffnen eines Indexes gezeigt. Aus Gründen der Einfachheit zeigen andere Beispiele in diesem Dokument möglicherweise keine Fehlerbehandlung an.

Fügen Sie dem Index Textzeichenfolgen hinzu, und führen Sie dann eine Abfrage aus.

In diesem Beispiel wird veranschaulicht, wie Sie dem für Ihre App erstellten Index einige Textzeichenfolgen hinzufügen und dann eine Abfrage für diesen Index ausführen, um relevante Informationen abzurufen.

    // This is some text data that we want to add to the index:
    Dictionary<string, string> simpleTextData = new Dictionary<string, string>
    {
        {"item1", "Here is some information about Cats: Cats are cute and fluffy. Young cats are very playful." },
        {"item2", "Dogs are loyal and affectionate animals known for their companionship, intelligence, and diverse breeds." },
        {"item3", "Fish are aquatic creatures that breathe through gills and come in a vast variety of shapes, sizes, and colors." },
        {"item4", "Broccoli is a nutritious green vegetable rich in vitamins, fiber, and antioxidants." },
        {"item5", "Computers are powerful electronic devices that process information, perform calculations, and enable communication worldwide." },
        {"item6", "Music is a universal language that expresses emotions, tells stories, and connects people through rhythm and melody." },
    };

    public void SimpleTextIndexingSample()
    {
        AppContentIndexer indexer = GetIndexerForApp();
        // Add some text data to the index:
        foreach (var item in simpleTextData)
        {
            IndexableAppContent textContent = AppManagedIndexableAppContent.CreateFromString(item.Key, item.Value);
            indexer.AddOrUpdate(textContent);
        }
    }

    public void SimpleTextQueryingSample()
    {
        AppContentIndexer indexer = GetIndexerForApp();
        // We search the index using a semantic query:
        AppIndexTextQuery queryCursor = indexer.CreateTextQuery("Facts about kittens.");
        IReadOnlyList<TextQueryMatch> textMatches = queryCursor.GetNextMatches(5);
        // Nothing in the index exactly matches what we queried but item1 is similar to the query so we expect
        // that to be the first match.
        foreach (var match in textMatches)
        {
            Console.WriteLine(match.ContentId);
            if (match.ContentKind == QueryMatchContentKind.AppManagedText)
            {
                AppManagedTextQueryMatch textResult = (AppManagedTextQueryMatch)match;
                // Only part of the original string may match the query. So we can use TextOffset and TextLength to extract the match.
                // In this example, we might imagine that the substring "Cats are cute and fluffy" from "item1" is the top match for the query.
                string matchingData = simpleTextData[match.ContentId];
                string matchingString = matchingData.Substring(textResult.TextOffset, textResult.TextLength);
                Console.WriteLine(matchingString);
            }
        }
    }

QueryMatch enthält nur ContentId und TextOffset/TextLengthnicht den passenden Text selbst. Es liegt in Ihrer Verantwortung als App-Entwickler, auf den ursprünglichen Text zu verweisen. Abfrageergebnisse werden nach Relevanz sortiert, wobei das oberste Ergebnis am relevantesten ist. Die Indizierung erfolgt asynchron, sodass Abfragen auf Teildaten ausgeführt werden können. Sie können den Indizierungsstatus wie unten beschrieben überprüfen.

Verwalten der Komplexität langer Textzeichenfolgen

Das Beispiel zeigt, dass es für den App-Entwickler nicht erforderlich ist, den Textinhalt in kleinere Abschnitte für die Modellverarbeitung zu unterteilen. Der AppContentIndexer verwaltet diesen Aspekt der Komplexität.

    Dictionary<string, string> textFiles = new Dictionary<string, string>
    {
        {"file1", "File1.txt" },
        {"file2", "File2.txt" },
        {"file3", "File3.txt" },
    };
    public void TextIndexingSample2()
    {
        AppContentIndexer indexer = GetIndexerForApp();
        var folderPath = Windows.ApplicationModel.Package.Current.InstalledLocation.Path;
        // Add some text data to the index:
        foreach (var item in textFiles)
        {
            string contentId = item.Key;
            string filename = item.Value;
            // Note that the text here can be arbitrarily large. The AppContentIndexer will take care of chunking the text
            // in a way that works effectively with the underlying model. We do not require the app author to break the text
            // down into small pieces.
            string text = File.ReadAllText(Path.Combine(folderPath, filename));
            IndexableAppContent textContent = AppManagedIndexableAppContent.CreateFromString(contentId, text);
            indexer.AddOrUpdate(textContent);
        }
    }

    public void TextIndexingSample2_RunQuery()
    {
        AppContentIndexer indexer = GetIndexerForApp();
        var folderPath = Windows.ApplicationModel.Package.Current.InstalledLocation.Path;
        // Search the index
        AppIndexTextQuery query = indexer.CreateTextQuery("Facts about kittens.");
        IReadOnlyList<TextQueryMatch> textMatches = query.GetNextMatches(5);
        if (textMatches != null) 
        {
            foreach (var match in textMatches)
            {
                Console.WriteLine(match.ContentId);
                if (match is AppManagedTextQueryMatch textResult)
                {
                    // We load the content of the file that contains the match:
                    string matchingFilename = textFiles[match.ContentId];
                    string fileContent = File.ReadAllText(Path.Combine(folderPath, matchingFilename));
    
                    // Find the substring within the loaded text that contains the match:
                    string matchingString = fileContent.Substring(textResult.TextOffset, textResult.TextLength);
                    Console.WriteLine(matchingString);
                }
            }
        }
    }

Textdaten stammen aus Dateien, aber nur der Inhalt wird indiziert, nicht die Dateien selbst. AppContentIndexer hat keine Kenntnisse über die originalen Dateien und überwacht keine Updates. Wenn sich der Dateiinhalt ändert, muss die App den Index manuell aktualisieren.

Bilddaten indizieren und dann nach relevanten Bildern suchen

In diesem Beispiel wird veranschaulicht, wie Bilddaten SoftwareBitmaps mithilfe von Textabfragen indiziert und dann nach relevanten Bildern gesucht werden.

    // We load the image data from a set of known files and send that image data to the indexer.
    // The image data does not need to come from files on disk, it can come from anywhere.
    Dictionary<string, string> imageFilesToIndex = new Dictionary<string, string>
        {
            {"item1", "Cat.jpg" },
            {"item2", "Dog.jpg" },
            {"item3", "Fish.jpg" },
            {"item4", "Broccoli.jpg" },
            {"item5", "Computer.jpg" },
            {"item6", "Music.jpg" },
        };
    public void SimpleImageIndexingSample()
    {
        AppContentIndexer indexer = GetIndexerForApp();

        // Add some image data to the index.
        foreach (var item in imageFilesToIndex)
        {
            var file = item.Value;
            var softwareBitmap = Helpers.GetSoftwareBitmapFromFile(file);
            IndexableAppContent imageContent = AppManagedIndexableAppContent.CreateFromBitmap(item.Key, softwareBitmap);
            indexer.AddOrUpdate(imageContent);
        }
    }
    public void SimpleImageIndexingSample_RunQuery()
    {
        AppContentIndexer indexer = GetIndexerForApp();
        // We query the index for some data to match our text query.
        AppIndexImageQuery query = indexer.CreateImageQuery("cute pictures of kittens");
        IReadOnlyList<ImageQueryMatch> imageMatches = query.GetNextMatches(5);
        // One of the images that we indexed was a photo of a cat. We expect this to be the first match to match the query.
        foreach (var match in imageMatches)
        {
            Console.WriteLine(match.ContentId);
            if (match.ContentKind == QueryMatchContentKind.AppManagedImage)
            {
                AppManagedImageQueryMatch imageResult = (AppManagedImageQueryMatch)match;
                var matchingFileName = imageFilesToIndex[match.ContentId];

                // It might be that the match is at a particular region in the image. The result includes
                // the subregion of the image that includes the match.

                Console.WriteLine($"Matching file: '{matchingFileName}' at location {imageResult.Subregion}");
            }
        }
    }

Aktivieren von RAG-Szenarien (Rückgewinnungs-unterstützte Generierung)

RAG (Retrieval-Augmented Generation) umfasst das Erweitern von Benutzerabfragen auf Sprachmodelle mit zusätzlichen relevanten Daten, die zum Generieren von Antworten verwendet werden können. Die Abfrage des Benutzers dient als Eingabe für die semantische Suche, die relevante Informationen in einem Index identifiziert. Die resultierenden Daten aus der semantischen Suche werden dann in die Aufforderung des Sprachmodells integriert, sodass präzisere und kontextbezogenere Antworten generiert werden können.

In diesem Beispiel wird veranschaulicht, wie die AppContentIndexer-API mit einem LLM verwendet werden kann, um kontextbezogene Daten zur Suchabfrage des App-Benutzers hinzuzufügen. Das Beispiel ist generisch, es wird kein LLM angegeben, und das Beispiel fragt nur die lokalen Daten ab, die im erstellten Index gespeichert sind (keine externen Aufrufe an das Internet). In diesem Beispiel sind Helpers.GetUserPrompt() und Helpers.GetResponseFromChatAgent() keine echten Funktionen und werden nur verwendet, um ein Beispiel bereitzustellen.

Um RAG-Szenarien mit der AppContentIndexer-API zu aktivieren, können Sie diesem Beispiel folgen:

    public void SimpleRAGScenario()
    {
        AppContentIndexer indexer = GetIndexerForApp();
        // These are some text files that had previously been added to the index.
        // The key is the contentId of the item.
        Dictionary<string, string> data = new Dictionary<string, string>
        {
            {"file1", "File1.txt" },
            {"file2", "File2.txt" },
            {"file3", "File3.txt" },
        };
        string userPrompt = Helpers.GetUserPrompt();
        // We execute a query against the index using the user's prompt string as the query text.
        AppIndexTextQuery query = indexer.CreateTextQuery(userPrompt);
        IReadOnlyList<TextQueryMatch> textMatches = query.GetNextMatches(5);
        StringBuilder promptStringBuilder = new StringBuilder();
        promptStringBuilder.AppendLine("Please refer to the following pieces of information when responding to the user's prompt:");
        // For each of the matches found, we include the relevant snippets of the text files in the augmented query that we send to the language model
        foreach (var match in textMatches)
        {
            if (match is AppManagedTextQueryMatch textResult)
            {
                // We load the content of the file that contains the match:
                string matchingFilename = data[match.ContentId];
                string fileContent = File.ReadAllText(matchingFilename);
                // Find the substring within the loaded text that contains the match:
                string matchingString = fileContent.Substring(textResult.TextOffset, textResult.TextLength);
                promptStringBuilder.AppendLine(matchingString);
                promptStringBuilder.AppendLine();
            }
        }
        promptStringBuilder.AppendLine("Please provide a response to the following user prompt:");
        promptStringBuilder.AppendLine(userPrompt);
        var response = Helpers.GetResponseFromChatAgent(promptStringBuilder.ToString());
        Console.WriteLine(response);
    }

Verwenden von AppContentIndexer in einem Hintergrundthread

Eine AppContentIndexer-Instanz ist keinem bestimmten Thread zugeordnet. es ist ein agiles Objekt, das über Threads hinweg ausgeführt werden kann. Bestimmte Methoden von AppContentIndexer und zugehörigen Typen erfordern möglicherweise erhebliche Verarbeitungszeiten. Daher ist es ratsam, das Aufrufen von AppContentIndexer-APIs direkt aus dem UI-Thread der Anwendung zu vermeiden und stattdessen einen Hintergrundthread zu verwenden.

Schließen Sie AppContentIndexer, wenn sie nicht mehr zum Freigeben von Ressourcen verwendet wird

AppContentIndexer implementiert die IClosable Schnittstelle, um die Lebensdauer zu bestimmen. Die Anwendung sollte den Indexer schließen, wenn sie nicht mehr verwendet wird. Dadurch kann AppContentIndexer die zugrunde liegenden Ressourcen freigeben.

    public void IndexerDisposeSample()
    {
        var indexer = AppContentIndexer.GetOrCreateIndex("myindex").Indexer;
        // use indexer
        indexer.Dispose();
        // after this point, it would be an error to try to use indexer since it is now Closed.
    }

In C#-Code wird die IClosable Schnittstelle als IDisposableprojiziert. C#-Code kann das using Muster für AppContentIndexer-Instanzen verwenden.

    public void IndexerUsingSample()
    {
        using var indexer = AppContentIndexer.GetOrCreateIndex("myindex").Indexer;
        // use indexer
        //indexer.Dispose() is automatically called
    }

Wenn Sie denselben Index mehrmals in Ihrer App öffnen, müssen Sie für jede Instanz Close aufrufen.

Das Öffnen und Schließen eines Indexes ist ein teurer Vorgang, sodass Sie solche Vorgänge in Ihrer Anwendung minimieren sollten. Beispielsweise kann eine Anwendung eine einzelne Instanz des AppContentIndexer für die Anwendung speichern und diese Instanz während der gesamten Lebensdauer der Anwendung verwenden, anstatt den Index für jede auszuführende Aktion ständig zu öffnen und zu schließen.