Partilhar via


Comece com a Pesquisa de Conteúdos em Aplicações

Use a App Content Search para criar um índice semântico do seu conteúdo dentro da aplicação. Isso permite que os usuários encontrem informações com base no significado, em vez de apenas palavras-chave. O índice também pode ser usado para aprimorar assistentes de IA com conhecimento específico de domínio para resultados mais personalizados e contextuais.

Especificamente, vai aprender a usar a API do AppContentIndexer para:

  • Criar ou abrir um índice do conteúdo em seu aplicativo
  • Adicionar cadeias de texto ao índice e, em seguida, executar uma consulta
  • Gerenciar a complexidade da cadeia de caracteres de texto longo
  • Indexar dados de imagem e, em seguida, procurar imagens relevantes
  • Ativar cenários de Geração Aumentada por Recuperação (RAG)
  • Usar AppContentIndexer em um thread em segundo plano
  • Feche o AppContentIndexer quando não estiver mais em uso para liberar recursos

Pré-requisitos

Para saber mais sobre os requisitos de hardware da API de IA do Windows e como configurar seu dispositivo para criar aplicativos com êxito usando as APIs de IA do Windows, consulte Introdução à criação de um aplicativo com APIs de IA do Windows.

Requisito de identidade do pacote

Os aplicativos que usam AppContentIndexer devem ter identidade de pacote, que só está disponível para aplicativos empacotados (incluindo aqueles com locais externos). Para habilitar a indexação semântica e o Reconhecimento de Texto (OCR), o aplicativo também deve declarar o systemaimodels recurso.

Criar ou abrir um índice do conteúdo em seu aplicativo

Para criar um índice semântico do conteúdo em seu aplicativo, você deve primeiro estabelecer uma estrutura pesquisável que seu aplicativo possa usar para armazenar e recuperar conteúdo de forma eficiente. Esse índice atua como um mecanismo de pesquisa semântica e lexical local para o conteúdo do seu aplicativo.

Para usar a API AppContentIndexer, primeiro faça uma chamada a GetOrCreateIndex com um nome de índice especificado. Se já existir um índice com esse nome para a identidade e o usuário atuais do aplicativo, ele será aberto; caso contrário, um novo será criado.

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

Este exemplo ilustra o tratamento de erros em caso de falha ao abrir um índice. Para simplificar, outros exemplos neste documento podem não mostrar o tratamento de erros.

Adicionar cadeias de texto ao índice e, em seguida, executar uma consulta

Este exemplo demonstra como adicionar algumas cadeias de caracteres de texto ao índice criado para seu aplicativo e, em seguida, executar uma consulta nesse índice para recuperar informações relevantes.

    // 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 inclui apenas ContentId e TextOffset/TextLength, não o texto correspondente em si. É sua responsabilidade, como desenvolvedor do aplicativo, fazer referência ao texto original. Os resultados da consulta são classificados por relevância, sendo o resultado superior o mais relevante. A indexação ocorre de forma assíncrona, portanto, as consultas podem ser executadas em dados parciais. Você pode verificar o status da indexação conforme descrito abaixo.

Gerenciar a complexidade da cadeia de caracteres de texto longo

O exemplo demonstra que não é necessário que o desenvolvedor do aplicativo divida o conteúdo do texto em seções menores para processamento do modelo. O AppContentIndexer gerencia esse aspeto da complexidade.

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

Os dados de texto são provenientes de ficheiros, mas apenas o conteúdo é indexado, não os ficheiros em si. AppContentIndexer não tem conhecimento dos arquivos originais e não monitora atualizações. Se o conteúdo do arquivo for alterado, o aplicativo deverá atualizar o índice manualmente.

Indexar dados de imagem e, em seguida, procurar imagens relevantes

Este exemplo demonstra como indexar dados de imagem como SoftwareBitmaps e, em seguida, procurar imagens relevantes usando consultas de texto.

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

Ativar cenários de Geração Aumentada por Recuperação (RAG)

RAG (Retrieval-Augmented Generation) envolve aumentar as consultas do usuário para modelos de linguagem com dados relevantes adicionais que podem ser usados para gerar respostas. A consulta do usuário serve como entrada para a pesquisa semântica, que identifica informações pertinentes em um índice. Os dados resultantes da pesquisa semântica são então incorporados no prompt dado ao modelo de linguagem para que respostas mais precisas e sensíveis ao contexto possam ser geradas.

Este exemplo demonstra como a API AppContentIndexer pode ser usada com um LLM para adicionar dados contextuais à consulta de pesquisa do utilizador da sua aplicação. O exemplo é genérico, nenhum LLM é especificado e o exemplo consulta apenas os dados locais armazenados no índice criado (sem chamadas externas para a Internet). Neste exemplo, Helpers.GetUserPrompt() e Helpers.GetResponseFromChatAgent() não são funções reais e são apenas usados para fornecer um exemplo.

Para habilitar cenários RAG com a API AppContentIndexer , você pode seguir este exemplo:

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

Usar AppContentIndexer em um thread em segundo plano

Uma instância AppContentIndexer não está associada a um thread específico; É um objeto ágil que pode operar através de threads. Certos métodos de AppContentIndexer e seus tipos relacionados podem exigir um tempo de processamento considerável. Portanto, é aconselhável evitar invocar APIs AppContentIndexer diretamente do thread da interface do usuário do aplicativo e, em vez disso, usar um thread em segundo plano.

Feche o AppContentIndexer quando não estiver mais em uso para liberar recursos

AppContentIndexer implementa a interface para determinar seu IClosable tempo de vida. O aplicativo deve fechar o indexador quando ele não estiver mais em uso. Isso permite que o AppContentIndexer libere seus recursos subjacentes.

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

No código C#, a IClosable interface é projetada como IDisposable. O código C# pode usar o using padrão para instâncias AppContentIndexer .

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

Se abrir o mesmo índice várias vezes na sua aplicação, deve chamar Close em cada instância.

Abrir e fechar um índice é uma operação cara, portanto, você deve minimizar essas operações em seu aplicativo. Por exemplo, um aplicativo pode armazenar uma única instância do AppContentIndexer para o aplicativo e usar essa instância durante todo o tempo de vida do aplicativo, em vez de abrir e fechar constantemente o índice para cada ação que precisa ser executada.