Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Użyj funkcji Wyszukiwania zawartości aplikacji, aby utworzyć semantyczny indeks zawartości w aplikacji. Dzięki temu użytkownicy mogą znajdować informacje na podstawie znaczenia, a nie tylko słów kluczowych. Indeks może również służyć do ulepszania asystentów sztucznej inteligencji dzięki wiedzy specyficznej dla domeny w celu uzyskania bardziej spersonalizowanych i kontekstowych wyników.
W szczególności dowiesz się, jak używać interfejsu API AppContentIndexer do:
- Tworzenie lub otwieranie indeksu zawartości w aplikacji
- Dodawanie ciągów tekstowych do indeksu, a następnie uruchamianie zapytania
- Zarządzanie złożonością długiego ciągu tekstowego
- Indeksowanie danych obrazu, a następnie wyszukiwanie odpowiednich obrazów
- Umożliwienie scenariuszy generowania wspomaganego wyszukiwaniem (RAG)
- Użyj AppContentIndexer w wątku w tle
- Zamknij funkcję AppContentIndexer, gdy nie jest już używana, aby zwolnić zasoby.
Wymagania wstępne
Aby dowiedzieć się więcej na temat wymagań sprzętowych interfejsu API sztucznej inteligencji systemu Windows i sposobu konfigurowania urządzenia do pomyślnego kompilowania aplikacji przy użyciu interfejsów API sztucznej inteligencji systemu Windows, zobacz Wprowadzenie do tworzenia aplikacji za pomocą interfejsów API sztucznej inteligencji systemu Windows.
Wymaganie dotyczące tożsamości pakietu
Aplikacje korzystające z AppContentIndexer muszą mieć tożsamość pakietu, która jest dostępna tylko dla spakowanych aplikacji (w tym aplikacji z lokalizacjami zewnętrznymi). Aby włączyć indeksowanie semantyczne i rozpoznawanie tekstu (OCR), aplikacja musi również zadeklarować systemaimodels możliwość.
Tworzenie lub otwieranie indeksu zawartości w aplikacji
Aby utworzyć semantyczny indeks zawartości w aplikacji, należy najpierw ustanowić strukturę z możliwością wyszukiwania, której aplikacja może używać do wydajnego przechowywania i pobierania zawartości. Ten indeks działa jako lokalna semantyczna i leksykalna wyszukiwarka zawartości aplikacji.
Aby użyć interfejsu API AppContentIndexer, najpierw wywołaj metodę GetOrCreateIndex z określoną nazwą indeksu. Jeśli indeks o tej nazwie już istnieje dla bieżącej tożsamości aplikacji i użytkownika, zostanie otwarty; w przeciwnym razie zostanie utworzony nowy.
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...
}
W tym przykładzie pokazano obsługę błędów w przypadku niepowodzenia otwierania indeksu. Dla uproszczenia inne przykłady w tym dokumencie mogą nie pokazywać obsługi błędów.
Dodawanie ciągów tekstowych do indeksu, a następnie uruchamianie zapytania
W tym przykładzie pokazano, jak dodać kilka ciągów tekstowych do indeksu utworzonego dla aplikacji, a następnie uruchomić zapytanie względem tego indeksu, aby pobrać odpowiednie informacje.
// 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 zawiera tylko ContentId i TextOffset/TextLength, a nie pasującego tekstu. Twoim zadaniem jako dewelopera aplikacji jest odniesienie się do oryginalnego tekstu. Wyniki zapytania są sortowane według trafności, a najlepszy wynik jest najbardziej odpowiedni. Indeksowanie odbywa się asynchronicznie, więc zapytania mogą być uruchamiane na częściowych danych. Stan indeksowania można sprawdzić zgodnie z poniższym opisem.
Zarządzanie złożonością długiego ciągu tekstowego
W przykładzie pokazano, że deweloper aplikacji nie musi dzielić zawartości tekstowej na mniejsze sekcje na potrzeby przetwarzania modelu. AppContentIndexer zarządza tym aspektem złożoności.
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);
}
}
}
}
Dane tekstowe pochodzą z plików, ale tylko zawartość jest indeksowana, a nie same pliki. Element AppContentIndexer nie zna oryginalnych plików i nie monitoruje aktualizacji. Jeśli zawartość pliku ulegnie zmianie, aplikacja musi ręcznie zaktualizować indeks.
Indeksowanie danych obrazu, a następnie wyszukiwanie odpowiednich obrazów
W tym przykładzie pokazano, jak indeksować dane obrazów oraz następnie wyszukiwać odpowiednie obrazy przy użyciu zapytań tekstowych.
// 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}");
}
}
}
Umożliwienie scenariuszy generowania wspomaganego wyszukiwaniem (RAG)
RAG (Retrieval-Augmented Generation) obejmuje rozszerzanie zapytań użytkowników na modele językowe z dodatkowymi odpowiednimi danymi, których można użyć do generowania odpowiedzi. Zapytanie użytkownika służy jako dane wejściowe dla wyszukiwania semantycznego, które identyfikuje istotne informacje w indeksie. Wynikowe dane z wyszukiwania semantycznego są następnie dołączane do monitu podanego do modelu językowego, aby można było wygenerować dokładniejsze i kontekstowe odpowiedzi.
W tym przykładzie pokazano, jak interfejs API AppContentIndexer może być używany z modelem LLM do dodawania danych kontekstowych do zapytania wyszukiwania użytkownika aplikacji. Przykład jest ogólny, nie określono modułu LLM i przykład wykonuje zapytania tylko o dane lokalne przechowywane w utworzonym indeksie (bez wywołań zewnętrznych do Internetu). W tym przykładzie Helpers.GetUserPrompt() i Helpers.GetResponseFromChatAgent() nie są rzeczywistymi funkcjami i są po prostu używane do przedstawienia przykładu.
Aby włączyć scenariusze RAG za pomocą interfejsu API AppContentIndexer , możesz skorzystać z następującego przykładu:
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);
}
Użyj AppContentIndexer w wątku w tle
Wystąpienie klasy AppContentIndexer nie jest skojarzone z określonym wątkiem; jest to obiekt agile, który może działać między wątkami. Niektóre metody klasy AppContentIndexer i powiązane z nimi typy mogą wymagać znacznego czasu przetwarzania. Dlatego zaleca się unikanie wywoływania interfejsów API AppContentIndexer bezpośrednio z wątku interfejsu użytkownika aplikacji oraz zamiast tego korzystać z wątku w tle.
Zamknij funkcję AppContentIndexer, gdy nie jest już używana, aby zwolnić zasoby.
AppContentIndexer implementuje interfejs IClosable w celu określenia jego okresu istnienia. Aplikacja powinna zamknąć indeksator, gdy nie jest już używany. Dzięki temu usługa AppContentIndexer może zwolnić swoje zasoby bazowe.
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.
}
W kodzie w języku C#, interfejs IClosable jest rzutowany jako IDisposable. Kod języka C# może używać using wzorca dla wystąpień klasy AppContentIndexer .
public void IndexerUsingSample()
{
using var indexer = AppContentIndexer.GetOrCreateIndex("myindex").Indexer;
// use indexer
//indexer.Dispose() is automatically called
}
W przypadku wielokrotnego otwierania tego samego indeksu w aplikacji, należy wykonać Close dla każdej instancji.
Otwieranie i zamykanie indeksu jest kosztowną operacją, dlatego należy zminimalizować takie operacje w aplikacji. Na przykład aplikacja może przechowywać pojedyncze wystąpienie klasy AppContentIndexer dla aplikacji i używać tego wystąpienia przez cały okres istnienia aplikacji zamiast stale otwierać i zamykać indeks dla każdej akcji, która musi zostać wykonana.