Dela via


Kom igång med appinnehållssökning

Använd appinnehållssökning för att skapa ett semantiskt index för ditt innehåll i appen. Detta gör det möjligt för användare att hitta information baserat på innebörd, snarare än bara nyckelord. Indexet kan också användas för att förbättra AI-assistenter med domänspecifik kunskap för mer personliga och kontextuella resultat.

Mer specifikt får du lära dig hur du använder Api:et AppContentIndexer för att:

  • Skapa eller öppna ett index för innehållet i din app
  • Lägg till textsträngar i indexet och kör sedan en fråga
  • Hantera lång textsträngskomplexitet
  • Indexering av bilddata och sök sedan efter relevanta bilder
  • Aktivera RAG-scenarier (Retrieval-Augmented Generation)
  • Använda AppContentIndexer i en bakgrundstråd
  • Stäng AppContentIndexer när det inte längre används för att frigöra resurser

Förutsättningar

Mer information om maskinvarukraven för Windows AI API och hur du konfigurerar enheten för att skapa appar med hjälp av Windows AI-API:er finns i Kom igång med att skapa en app med Windows AI-API:er.

Krav på paketidentitet

Appar som använder AppContentIndexer måste ha paketidentitet, vilket endast är tillgängligt för paketerade appar (inklusive de med externa platser). För att aktivera semantisk indexering och textigenkänning (OCR) måste appen också deklarera funktionensystemaimodels.

Skapa eller öppna ett index för innehållet i din app

Om du vill skapa ett semantiskt index för innehållet i din app måste du först upprätta en sökbar struktur som appen kan använda för att lagra och hämta innehåll effektivt. Det här indexet fungerar som en lokal semantisk och lexikal sökmotor för appens innehåll.

För att använda AppContentIndexer-API:et anropar du först GetOrCreateIndex med ett angivet indexnamn. Om det redan finns ett index med det namnet för den aktuella appidentiteten och användaren öppnas det. annars skapas en ny.

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

Det här exemplet visar felhantering i fall av fel när man öppnar ett index. För enkelhetens skull kanske andra exempel i det här dokumentet inte visar felhantering.

Lägg till textsträngar i indexet och kör sedan en fråga

Det här exemplet visar hur du lägger till några textsträngar i det index som skapats för din app och kör sedan en fråga mot indexet för att hämta relevant information.

    // 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 innehåller endast ContentId och TextOffset/TextLength, inte själva matchande texten. Det är ditt ansvar som apputvecklare att referera till den ursprungliga texten. Frågeresultat sorteras efter relevans, där det högsta resultatet är mest relevant. Indexering sker asynkront, så frågor kan köras på partiella data. Du kan kontrollera indexeringsstatusen enligt beskrivningen nedan.

Hantera lång textsträngskomplexitet

Exemplet visar att det inte är nödvändigt för apputvecklaren att dela upp textinnehållet i mindre avsnitt för modellbearbetning. AppContentIndexer hanterar den här aspekten av komplexiteten.

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

Textdata hämtas från filer, men endast innehållet indexeras, inte själva filerna. AppContentIndexer har ingen kunskap om de ursprungliga filerna och övervakar inte uppdateringar. Om filinnehållet ändras måste appen uppdatera indexet manuellt.

Indexering av bilddata och sök sedan efter relevanta bilder

Det här exemplet visar hur du indexerade bilddata som SoftwareBitmaps och sedan söker efter relevanta bilder med hjälp av textfrågor.

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

Aktivera RAG-scenarier (Retrieval-Augmented Generation)

RAG (Retrieval-Augmented Generation) omfattar utökade användarfrågor till språkmodeller med ytterligare relevanta data som kan användas för att generera svar. Användarens fråga fungerar som indata för semantisk sökning, som identifierar relevant information i ett index. Resulterande data från den semantiska sökningen införlivas sedan i uppmaningen som ges till språkmodellen så att mer exakta och sammanhangsmedvetna svar kan genereras.

Det här exemplet visar hur Api:et AppContentIndexer kan användas för med en LLM för att lägga till kontextuella data i appanvändarens sökfråga. Exemplet är allmänt, ingen LLM har angetts och exemplet frågar bara lokala data som lagras i det index som skapats (inga externa anrop till Internet). I det här exemplet Helpers.GetUserPrompt() är och Helpers.GetResponseFromChatAgent() inte verkliga funktioner och används bara för att ge ett exempel.

Om du vill aktivera RAG-scenarier med Api:et AppContentIndexer kan du följa det här exemplet:

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

Använda AppContentIndexer i en bakgrundstråd

En AppContentIndexer-instans är inte associerad med en viss tråd. det är ett agilt objekt som kan fungera mellan trådar. Vissa metoder för AppContentIndexer och dess relaterade typer kan kräva betydande bearbetningstid. Därför bör du undvika att anropa AppContentIndexer-API :er direkt från programmets användargränssnittstråd och i stället använda en bakgrundstråd.

Stäng AppContentIndexer när det inte längre används för att frigöra resurser

AppContentIndexer implementerarIClosable gränssnittet för att fastställa dess livslängd. Programmet bör stänga indexeraren när den inte längre används. På så sätt kan AppContentIndexer frigöra sina underliggande resurser.

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

I C#-kod IClosable projiceras gränssnittet som IDisposable. C#-kod kan använda using mönstret för AppContentIndexer-instanser .

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

Om du öppnar samma index flera gånger i appen måste du anropa Close varje instans.

Att öppna och stänga ett index är en dyr åtgärd, så du bör minimera sådana åtgärder i ditt program. Ett program kan till exempel lagra en enda instans av AppContentIndexer för programmet och använda den instansen under programmets livslängd i stället för att ständigt öppna och stänga indexet för varje åtgärd som måste utföras.