Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Use a Pesquisa de Conteúdo do Aplicativo para criar um índice semântico do conteúdo no aplicativo. 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 os assistentes de IA com conhecimento específico do domínio para resultados mais personalizados e contextuais.
Especificamente, você aprenderá a usar a API AppContentIndexer para:
- Criar ou abrir um índice do conteúdo em seu aplicativo
- Adicionar cadeias de caracteres de texto ao índice e, em seguida, executar uma consulta
- Gerenciar complexidade de cadeia de caracteres de texto longo
- Indexar dados de imagem e, em seguida, pesquisar imagens relevantes
- Habilitar cenários de Geração Aumentada por Recuperação (RAG)
- Usar AppContentIndexer em um thread em segundo plano
- Fechar 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 a identidade do pacote, que só está disponível para aplicativos empacotados (incluindo aqueles com locais externos). Para habilitar a indexação semântica e o OCR (Reconhecimento de Texto), o aplicativo também deve declarar a systemaimodels funcionalidade.
Criar ou abrir um índice do conteúdo em seu aplicativo
Para criar um índice semântico do conteúdo em seu aplicativo, primeiro você deve estabelecer uma estrutura pesquisável que seu aplicativo pode usar para armazenar e recuperar conteúdo com eficiência. Esse índice atua como um mecanismo de pesquisa semântico e léxico local para o conteúdo do aplicativo.
Para usar a API AppContentIndexer, primeiro, chame GetOrCreateIndex com um nome de índice especificado. Se já existir um índice com esse nome para a identidade do aplicativo e o usuário atual, 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 mostra o tratamento de erro no caso de falha ao abrir um índice. Para simplificar, outros exemplos neste documento podem não mostrar tratamento de erros.
Adicionar cadeias de caracteres 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 referenciar o texto original. Os resultados da consulta são classificados por relevância, com o resultado superior sendo mais relevante. A indexação ocorre de forma assíncrona, portanto, as consultas podem ser executadas em dados parciais. Você pode verificar o status de indexação, conforme descrito abaixo.
Gerenciar complexidade de 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 de modelo. O AppContentIndexer gerencia esse aspecto de 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 arquivos, mas somente o conteúdo é indexado, não os próprios arquivos. 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, pesquisar imagens relevantes
Este exemplo demonstra como indexar dados de imagem como SoftwareBitmaps e, em seguida, pesquisar 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}");
}
}
}
Habilitar cenários de Geração Aumentada por Recuperação (RAG)
O RAG (Geração Aumentada por Recuperação) envolve enriquecer as consultas dos usuários aos modelos de linguagem com dados adicionais relevantes que podem ser usados para gerar respostas. A consulta do usuário serve como entrada para pesquisa semântica, que identifica informações pertinentes em um índice. Os dados resultantes da pesquisa semântica são incorporados ao prompt fornecido ao modelo de linguagem para que respostas mais precisas e com reconhecimento de contexto possam ser geradas.
Este exemplo demonstra como a API AppContentIndexer pode ser usada com uma LLM para adicionar dados contextuais à consulta de pesquisa do usuário do aplicativo. O exemplo é genérico, nenhuma LLM é especificada 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 usadas apenas para fornecer um exemplo.
Para habilitar cenários RAG com a API AppContentIndexer, você pode fazer isso seguindo 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 agile que pode operar entre threads. Determinados métodos de AppContentIndexer e seus tipos relacionados podem exigir tempo de processamento considerável. Portanto, é aconselhável evitar invocar APIs AppContentIndexer diretamente do thread de interface do usuário do aplicativo e, em vez disso, usar um thread em segundo plano.
Fechar AppContentIndexer quando não estiver mais em uso para liberar recursos
AppContentIndexer implementa a IClosable interface para determinar seu tempo de vida. O aplicativo deve fechar o indexador quando ele não estiver mais em uso. Isso permite que 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 você abrir o mesmo índice várias vezes em seu aplicativo, deverá 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.