Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Usare la ricerca del contenuto dell'app per creare un indice semantico del contenuto in-app. In questo modo gli utenti possono trovare informazioni in base al significato, anziché solo parole chiave. L'indice può essere usato anche per migliorare gli assistenti di intelligenza artificiale con conoscenze specifiche del dominio per risultati più personalizzati e contestuali.
In particolare, si apprenderà come usare l'API AppContentIndexer per:
- Creare o aprire un indice del contenuto nell'app
- Aggiungere stringhe di testo all'indice e quindi eseguire una query
- Gestire la complessità delle stringhe di testo lunghe
- Indicizzare i dati dell'immagine e quindi cercare immagini pertinenti
- Abilitare gli scenari RAG (Generazione Augmentata dal Recupero)
- Usare AppContentIndexer su un thread in background
- Chiudere AppContentIndexer quando non è più in uso per rilasciare le risorse
Prerequisiti
Per informazioni sui requisiti hardware dell'API windows per intelligenza artificiale e su come configurare il dispositivo per compilare correttamente le app usando le API windows per intelligenza artificiale, vedere Introduzione alla creazione di un'app con le API windows per intelligenza artificiale.
Requisito di identità del pacchetto
Le app che usano AppContentIndexer devono avere l'identità del pacchetto, disponibile solo per le app in pacchetto (incluse quelle con posizioni esterne). Per abilitare l'indicizzazione semantica e il riconoscimento del testo (OCR), l'app deve anche dichiarare la systemaimodels funzionalità.
Creare o aprire un indice del contenuto nell'app
Per creare un indice semantico del contenuto nella tua app, devi prima stabilire una struttura ricercabile che l'app possa usare per archiviare e recuperare contenuti in modo efficiente. Questo indice funge da motore di ricerca semantico e lessicale locale per il contenuto dell'app.
Per usare l'API AppContentIndexer , chiamare GetOrCreateIndex prima con un nome di indice specificato. Se esiste già un indice con tale nome per l'identità e l'utente dell'app corrente, viene aperto; in caso contrario, ne viene creato uno nuovo.
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...
}
Questo esempio mostra la gestione degli errori nel caso di fallimento per l'apertura di un indice. Per semplicità, altri esempi in questo documento potrebbero non mostrare la gestione degli errori.
Aggiungere stringhe di testo all'indice e quindi eseguire una query
Questo esempio illustra come aggiungere alcune stringhe di testo all'indice creato per l'app e quindi eseguire una query su tale indice per recuperare le informazioni pertinenti.
// 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 include solo ContentId e TextOffset/TextLength, non il testo corrispondente stesso. È responsabilità dello sviluppatore dell'app fare riferimento al testo originale. I risultati delle query vengono ordinati in base alla pertinenza, con il risultato principale più rilevante. L'indicizzazione viene eseguita in modo asincrono, quindi le query possono essere eseguite su dati parziali. È possibile controllare lo stato di indicizzazione come descritto di seguito.
Gestire la complessità delle stringhe di testo lunghe
L'esempio dimostra che non è necessario per lo sviluppatore dell'app dividere il contenuto di testo in sezioni più piccole per l'elaborazione del modello. AppContentIndexer gestisce questo aspetto della complessità.
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);
}
}
}
}
I dati di testo vengono originati dai file, ma solo il contenuto viene indicizzato, non i file stessi. AppContentIndexer non conosce i file originali e non monitora gli aggiornamenti. Se il contenuto del file cambia, l'app deve aggiornare manualmente l'indice.
Indicizzare i dati dell'immagine e quindi cercare immagini pertinenti
In questo esempio viene illustrato come indicizzare i dati dell'immagine come SoftwareBitmaps e quindi cercare immagini pertinenti usando query di testo.
// 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}");
}
}
}
Abilitare gli scenari RAG (Generazione Augmentata dal Recupero)
RAG (Retrieval-Augmented Generation) implica l'arricchimento delle query degli utenti nei modelli di linguaggio con dati pertinenti aggiuntivi che possono essere usati per generare risposte. La query dell'utente funge da input per la ricerca semantica, che identifica le informazioni pertinenti in un indice. I dati risultanti dalla ricerca semantica vengono quindi incorporati nella richiesta fornita al modello linguistico in modo che sia possibile generare risposte più accurate e con riconoscimento del contesto.
Questo esempio illustra come usare l'API AppContentIndexer con un LLM per aggiungere dati contestuali alla query di ricerca dell'utente dell'app. L'esempio è generico, non viene specificato alcun LLM e l'esempio esegue solo query sui dati locali archiviati nell'indice creato (nessuna chiamata esterna a Internet). In questo esempio e Helpers.GetUserPrompt()Helpers.GetResponseFromChatAgent() non sono funzioni reali e vengono usate solo per fornire un esempio.
Per abilitare gli scenari RAG con l'API AppContentIndexer , è possibile seguire questo esempio:
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);
}
Usare AppContentIndexer su un thread in background
Un'istanza di AppContentIndexer non è associata a un thread specifico; si tratta di un oggetto Agile che può funzionare tra thread. Alcuni metodi di AppContentIndexer e i relativi tipi correlati possono richiedere tempi di elaborazione notevoli. Pertanto, è consigliabile evitare di richiamare le API AppContentIndexer direttamente dal thread dell'interfaccia utente dell'applicazione e usare invece un thread in background.
Chiudere AppContentIndexer quando non è più in uso per rilasciare le risorse
AppContentIndexer implementa l'interfaccia IClosable per determinare la sua durata. L'applicazione deve chiudere l'indicizzatore quando non è più in uso. In questo modo AppContentIndexer può rilasciare le risorse sottostanti.
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.
}
Nel codice C# l'interfaccia IClosable viene proiettata come IDisposable. Il codice C# può usare il using modello per le istanze di AppContentIndexer .
public void IndexerUsingSample()
{
using var indexer = AppContentIndexer.GetOrCreateIndex("myindex").Indexer;
// use indexer
//indexer.Dispose() is automatically called
}
Se si apre più volte lo stesso indice nell'app, è necessario chiamare Close in ogni istanza.
L'apertura e la chiusura di un indice è un'operazione costosa, pertanto è consigliabile ridurre al minimo tali operazioni nell'applicazione. Ad esempio, un'applicazione può archiviare una singola istanza di AppContentIndexer per l'applicazione e usare tale istanza per tutta la durata dell'applicazione anziché aprire e chiudere costantemente l'indice per ogni azione che deve essere eseguita.