Краткое руководство. Полнотекстовый поиск с помощью пакетов SDK Azure
Узнайте, как использовать клиентская библиотека Azure.Search.Documents для создания, загрузки и запроса индекса поиска с использованием примеров данных для полнотекстового поиска. Полнотекстовый поиск использует Apache Lucene для индексирования и запросов, а также алгоритм ранжирования BM25 для оценки результатов.
В этом кратком руководстве создается и запрашивается индекс небольшого быстрого запуска отелей, содержащий данные о четырех отелях.
Совет
Исходный код можно скачать, чтобы начать с готового проекта или выполнить следующие действия, чтобы создать собственный.
Необходимые компоненты
- Активная подписка Azure — создайте подписку бесплатно.
- Служба ИИ Azure. Создайте службу , если у вас ее нет. Вы можете использовать бесплатный уровень для этого краткого руководства.
Предварительные требования для идентификатора Microsoft Entra
Для рекомендуемой проверки подлинности без ключа с помощью идентификатора Microsoft Entra необходимо:
- Установите Azure CLI, используемый для проверки подлинности без ключа с помощью идентификатора Microsoft Entra.
- Назначьте обе роли учетной
Search Service Contributor
Search Index Data Contributor
записи пользователя. Роли можно назначить в портал Azure в разделе управления доступом (IAM)>Добавить назначение ролей. Дополнительные сведения см. в статье "Подключение к поиску ИИ Azure" с помощью ролей.
Получение сведений о ресурсе
Для проверки подлинности приложения с помощью azure AI служба необходимо получить следующие сведения:
Имя переменной | Значение |
---|---|
SEARCH_API_ENDPOINT |
Это значение можно найти в портал Azure. Выберите службу поиска, а затем в меню слева выберите "Обзор".
Значение URL-адреса в разделе Essentials — это необходимая конечная точка. Пример конечной точки может выглядеть так: https://mydemo.search.windows.net . |
Дополнительные сведения о бессерверной проверке подлинности и настройке переменных среды.
Настройка
Создайте новую папку
full-text-quickstart
для хранения приложения и откройте Visual Studio Code в этой папке с помощью следующей команды:mkdir full-text-quickstart && cd full-text-quickstart
Создайте консольное приложение со следующей командой:
dotnet new console
Установите клиентную библиотеку поиска ИИ Azure (Azure.Search.Documents) для .NET:
dotnet add package Azure.Search.Documents
Для рекомендуемой проверки подлинности без ключа с помощью идентификатора Microsoft Entra установите пакет Azure.Identity с помощью:
dotnet add package Azure.Identity
Для рекомендуемой проверки подлинности без ключа с помощью идентификатора Microsoft Entra войдите в Azure с помощью следующей команды:
az login
Создание, загрузка и запрос индекса поиска
В предыдущем разделе настройки вы создали консольное приложение и установили клиентская библиотека поиска ИИ Azure.
В этом разделе вы добавите код для создания индекса поиска, загрузки его с документами и выполнения запросов. Запустите программу, чтобы просмотреть результаты в консоли. Подробные сведения о коде см . в описании раздела кода .
Пример кода в этом кратком руководстве использует идентификатор Microsoft Entra для рекомендуемой проверки подлинности без ключей. Если вы предпочитаете использовать ключ API, можно заменить DefaultAzureCredential
объект AzureKeyCredential
объектом.
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
В Program.cs вставьте следующий код. Измените
serviceName
значения иapiKey
переменные с помощью имени службы поиска и ключа API администрирования.using System; using Azure; using Azure.Identity; using Azure.Search.Documents; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; using Azure.Search.Documents.Models; namespace AzureSearch.Quickstart { class Program { static void Main(string[] args) { // Your search service endpoint Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/"); // Use the recommended keyless credential instead of the AzureKeyCredential credential. DefaultAzureCredential credential = new(); //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key"); // Create a SearchIndexClient to send create/delete index commands SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential); // Create a SearchClient to load and query documents string indexName = "hotels-quickstart"; SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential); // Delete index if it exists Console.WriteLine("{0}", "Deleting index...\n"); DeleteIndexIfExists(indexName, searchIndexClient); // Create index Console.WriteLine("{0}", "Creating index...\n"); CreateIndex(indexName, searchIndexClient); SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName); // Load documents Console.WriteLine("{0}", "Uploading documents...\n"); UploadDocuments(ingesterClient); // Wait 2 secondsfor indexing to complete before starting queries (for demo and console-app purposes only) Console.WriteLine("Waiting for indexing...\n"); System.Threading.Thread.Sleep(2000); // Call the RunQueries method to invoke a series of queries Console.WriteLine("Starting queries...\n"); RunQueries(searchClient); // End the program Console.WriteLine("{0}", "Complete. Press any key to end this program...\n"); Console.ReadKey(); } // Delete the hotels-quickstart index to reuse its name private static void DeleteIndexIfExists(string indexName, SearchIndexClient searchIndexClient) { searchIndexClient.GetIndexNames(); { searchIndexClient.DeleteIndex(indexName); } } // Create hotels-quickstart index private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient) { FieldBuilder fieldBuilder = new FieldBuilder(); var searchFields = fieldBuilder.Build(typeof(Hotel)); var definition = new SearchIndex(indexName, searchFields); var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" }); definition.Suggesters.Add(suggester); searchIndexClient.CreateOrUpdateIndex(definition); } // Upload documents in a single Upload request. private static void UploadDocuments(SearchClient searchClient) { IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create( IndexDocumentsAction.Upload( new Hotel() { HotelId = "1", HotelName = "Secret Point Motel", Description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.", DescriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.", Category = "Boutique", Tags = new[] { "pool", "air conditioning", "concierge" }, ParkingIncluded = false, LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), Rating = 3.6, Address = new Address() { StreetAddress = "677 5th Ave", City = "New York", StateProvince = "NY", PostalCode = "10022", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "2", HotelName = "Twin Dome Motel", Description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.", DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", Category = "Boutique", Tags = new[] { "pool", "free wifi", "concierge" }, ParkingIncluded = false, LastRenovationDate = new DateTimeOffset(1979, 2, 18, 0, 0, 0, TimeSpan.Zero), Rating = 3.60, Address = new Address() { StreetAddress = "140 University Town Center Dr", City = "Sarasota", StateProvince = "FL", PostalCode = "34243", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "3", HotelName = "Triple Landscape Hotel", Description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.", DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", Category = "Resort and Spa", Tags = new[] { "air conditioning", "bar", "continental breakfast" }, ParkingIncluded = true, LastRenovationDate = new DateTimeOffset(2015, 9, 20, 0, 0, 0, TimeSpan.Zero), Rating = 4.80, Address = new Address() { StreetAddress = "3393 Peachtree Rd", City = "Atlanta", StateProvince = "GA", PostalCode = "30326", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "4", HotelName = "Sublime Cliff Hotel", Description = "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.", DescriptionFr = "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.", Category = "Boutique", Tags = new[] { "concierge", "view", "24-hour front desk service" }, ParkingIncluded = true, LastRenovationDate = new DateTimeOffset(1960, 2, 06, 0, 0, 0, TimeSpan.Zero), Rating = 4.60, Address = new Address() { StreetAddress = "7400 San Pedro Ave", City = "San Antonio", StateProvince = "TX", PostalCode = "78216", Country = "USA" } }) ); try { IndexDocumentsResult result = searchClient.IndexDocuments(batch); } catch (Exception) { // If for some reason any documents are dropped during indexing, you can compensate by delaying and // retrying. This simple demo just logs the failed document keys and continues. Console.WriteLine("Failed to index some of the documents: {0}"); } } // Run queries, use WriteDocuments to print output private static void RunQueries(SearchClient searchClient) { SearchOptions options; SearchResults<Hotel> response; // Query 1 Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n"); options = new SearchOptions() { IncludeTotalCount = true, Filter = "", OrderBy = { "" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Rating"); response = searchClient.Search<Hotel>("*", options); WriteDocuments(response); // Query 2 Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n"); options = new SearchOptions() { Filter = "Rating gt 4", OrderBy = { "Rating desc" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Rating"); response = searchClient.Search<Hotel>("hotels", options); WriteDocuments(response); // Query 3 Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n"); options = new SearchOptions() { SearchFields = { "Tags" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Tags"); response = searchClient.Search<Hotel>("pool", options); WriteDocuments(response); // Query 4 - Use Facets to return a faceted navigation structure for a given query // Filters are typically used with facets to narrow results on OnClick events Console.WriteLine("Query #4: Facet on 'Category'...\n"); options = new SearchOptions() { Filter = "" }; options.Facets.Add("Category"); options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Category"); response = searchClient.Search<Hotel>("*", options); WriteDocuments(response); // Query 5 Console.WriteLine("Query #5: Look up a specific document...\n"); Response<Hotel> lookupResponse; lookupResponse = searchClient.GetDocument<Hotel>("3"); Console.WriteLine(lookupResponse.Value.HotelId); // Query 6 Console.WriteLine("Query #6: Call Autocomplete on HotelName...\n"); var autoresponse = searchClient.Autocomplete("sa", "sg"); WriteDocuments(autoresponse); } // Write search results to console private static void WriteDocuments(SearchResults<Hotel> searchResults) { foreach (SearchResult<Hotel> result in searchResults.GetResults()) { Console.WriteLine(result.Document); } Console.WriteLine(); } private static void WriteDocuments(AutocompleteResults autoResults) { foreach (AutocompleteItem result in autoResults.Results) { Console.WriteLine(result.Text); } Console.WriteLine(); } } }
В той же папке создайте файл с именем Hotel.cs и вставьте следующий код. Этот код определяет структуру документа отеля.
using System; using System.Text.Json.Serialization; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; namespace AzureSearch.Quickstart { public partial class Hotel { [SimpleField(IsKey = true, IsFilterable = true)] public string HotelId { get; set; } [SearchableField(IsSortable = true)] public string HotelName { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] public string Description { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.FrLucene)] [JsonPropertyName("Description_fr")] public string DescriptionFr { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Category { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string[] Tags { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public bool? ParkingIncluded { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public DateTimeOffset? LastRenovationDate { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public double? Rating { get; set; } [SearchableField] public Address Address { get; set; } } }
Создайте файл с именем Hotel.cs и вставьте следующий код, чтобы определить структуру документа отеля. Атрибуты в поле определяют, как оно используется в приложении. Например, атрибут
IsFilterable
должен быть присвоен каждому полю, которое поддерживает выражение фильтра.using System; using System.Text.Json.Serialization; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; namespace AzureSearch.Quickstart { public partial class Hotel { [SimpleField(IsKey = true, IsFilterable = true)] public string HotelId { get; set; } [SearchableField(IsSortable = true)] public string HotelName { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] public string Description { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.FrLucene)] [JsonPropertyName("Description_fr")] public string DescriptionFr { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Category { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string[] Tags { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public bool? ParkingIncluded { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public DateTimeOffset? LastRenovationDate { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public double? Rating { get; set; } [SearchableField] public Address Address { get; set; } } }
Создайте файл с именем Address.cs и вставьте следующий код, чтобы определить структуру документа адреса.
using Azure.Search.Documents.Indexes; namespace AzureSearch.Quickstart { public partial class Address { [SearchableField(IsFilterable = true)] public string StreetAddress { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string City { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string StateProvince { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string PostalCode { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Country { get; set; } } }
Создайте файл с именем Hotel.Methods.cs и вставьте следующий код, чтобы определить
ToString()
переопределение дляHotel
класса.using System; using System.Text; namespace AzureSearch.Quickstart { public partial class Hotel { public override string ToString() { var builder = new StringBuilder(); if (!String.IsNullOrEmpty(HotelId)) { builder.AppendFormat("HotelId: {0}\n", HotelId); } if (!String.IsNullOrEmpty(HotelName)) { builder.AppendFormat("Name: {0}\n", HotelName); } if (!String.IsNullOrEmpty(Description)) { builder.AppendFormat("Description: {0}\n", Description); } if (!String.IsNullOrEmpty(DescriptionFr)) { builder.AppendFormat("Description (French): {0}\n", DescriptionFr); } if (!String.IsNullOrEmpty(Category)) { builder.AppendFormat("Category: {0}\n", Category); } if (Tags != null && Tags.Length > 0) { builder.AppendFormat("Tags: [ {0} ]\n", String.Join(", ", Tags)); } if (ParkingIncluded.HasValue) { builder.AppendFormat("Parking included: {0}\n", ParkingIncluded.Value ? "yes" : "no"); } if (LastRenovationDate.HasValue) { builder.AppendFormat("Last renovated on: {0}\n", LastRenovationDate); } if (Rating.HasValue) { builder.AppendFormat("Rating: {0}\n", Rating); } if (Address != null && !Address.IsEmpty) { builder.AppendFormat("Address: \n{0}\n", Address.ToString()); } return builder.ToString(); } } }
Создайте файл с именем Address.Methods.cs и вставьте следующий код, чтобы определить
ToString()
переопределение дляAddress
класса.using System; using System.Text; using System.Text.Json.Serialization; namespace AzureSearch.Quickstart { public partial class Address { public override string ToString() { var builder = new StringBuilder(); if (!IsEmpty) { builder.AppendFormat("{0}\n{1}, {2} {3}\n{4}", StreetAddress, City, StateProvince, PostalCode, Country); } return builder.ToString(); } [JsonIgnore] public bool IsEmpty => String.IsNullOrEmpty(StreetAddress) && String.IsNullOrEmpty(City) && String.IsNullOrEmpty(StateProvince) && String.IsNullOrEmpty(PostalCode) && String.IsNullOrEmpty(Country); } }
Выполните сборку и запустите приложение с помощью следующей команды:
dotnet run
Выходные данные содержат сообщения из Console.WriteLine, а также сведения о запросе и результаты.
Объяснение кода
В предыдущих разделах вы создали консольное приложение и установили клиентную библиотеку поиска ИИ Azure. Вы добавили код для создания индекса поиска, загрузки его с документами и выполнения запросов. Вы запустили программу, чтобы просмотреть результаты в консоли.
В этом разделе мы объясним код, добавленный в консольное приложение.
Создание клиента для поиска
В Program.cs вы создали два клиента:
- SearchIndexClient создает индекс.
- SearchClient загружает и запрашивает существующий индекс.
Обе клиенты нуждаются в конечной точке службы поиска и учетных данных, описанных ранее в разделе сведений о ресурсе.
Пример кода в этом кратком руководстве использует идентификатор Microsoft Entra для рекомендуемой проверки подлинности без ключей. Если вы предпочитаете использовать ключ API, можно заменить DefaultAzureCredential
объект AzureKeyCredential
объектом.
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
static void Main(string[] args)
{
// Your search service endpoint
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
// Use the recommended keyless credential instead of the AzureKeyCredential credential.
DefaultAzureCredential credential = new();
//AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
// Create a SearchIndexClient to send create/delete index commands
SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);
// Create a SearchClient to load and query documents
string indexName = "hotels-quickstart";
SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
// REDACTED FOR BREVITY . . .
}
Создание индекса
В этом кратком руководстве создается индекс hotels, который загружается с данными отеля и выполняет запросы. На этом шаге вы определяете поля в индексе. Каждое определение поля содержит имя, тип данных и атрибуты, которые определяют способ использования этого поля.
В этом примере синхронные методы библиотеки Azure.Search.Documents используются для простоты и удобства чтения. Но для рабочих сценариев лучше использовать асинхронные методы, чтобы приложение было масштабируемым и отзывчивым. Например, следует использовать CreateIndexAsync вместо CreateIndex.
Определение структур
Вы создали два вспомогательных класса, Hotel.cs и Address.cs, чтобы определить структуру документа отеля и его адреса. Класс Hotel
содержит поля для идентификатора отеля, имени, описания, категории, тегов, парковки, даты обновления, оценки и адреса. Класс Address
включает поля для уличных адресов, города, штата или провинции, почтового индекса и страны или региона.
В клиентской библиотеке Azure.Search.Documents можно использовать SearchableField и SimpleField для упрощения определений полей. Оба метода являются производными от SearchField и потенциально могут упростить код:
SimpleField
может быть любым типом данных, всегда не выполняется поиск (игнорируется для запросов полнотекстового поиска) и извлекается (не скрыто). Другие атрибуты отключены по умолчанию, но их можно включить.SimpleField
можно использовать для идентификаторов документов или полей, используемых только в фильтрах, аспектах или профилях оценки. Если это так, обязательно примените все необходимые для сценария атрибуты, напримерIsKey = true
для идентификатора документа. Чтобы узнать больше, см. SimpleFieldAttribute.cs в исходном коде.SearchableField
должен быть строкой и всегда доступен для поиска и извлечения. Другие атрибуты отключены по умолчанию, но их можно включить. Так как этот тип поля доступен для поиска, он поддерживает синонимы и полное дополнение свойств анализатора. Чтобы узнать больше, см. SearchableFieldAttribute.cs в исходном коде.
Независимо от того, используется ли базовый API SearchField
или одна из вспомогательных моделей, необходимо явно включить атрибуты фильтров, аспектов и сортировки. Например, IsFilterable, IsSortable и IsFacetable должны быть явно атрибутированы, как показано в предыдущем примере.
Создание индекса поиска
В Program.cs создается объект SearchIndex, а затем вызывается метод CreateIndex для выражения индекса в службе поиска. Индекс также содержит SearchSuggester, чтобы применить автозаполнение для указанных полей.
// Create hotels-quickstart index
private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
{
FieldBuilder fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));
var definition = new SearchIndex(indexName, searchFields);
var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
definition.Suggesters.Add(suggester);
searchIndexClient.CreateOrUpdateIndex(definition);
}
Загрузка документов
Поиск azure AI выполняет поиск по содержимому, хранящемуся в службе. На этом шаге вы загружаете документы JSON, соответствующие созданному индексу отеля.
В поиске ИИ Azure документы поиска — это структуры данных, которые являются входными данными для индексирования и выходных данных из запросов. Полученные из внешнего источника данных входные документы могут содержать строки базы данных, большие двоичные объекты из хранилища BLOB-объектов или сохраненные на диске документы JSON. В нашем примере мы выбрали самый простой путь, внедрив прямо в код документы JSON с информацией о четырех отелях.
При отправке документов необходимо использовать объект IndexDocumentsBatch. Объект IndexDocumentsBatch
содержит коллекцию Actions, каждая из которых содержит документ и свойство, указывающее службе "Поиск ИИ Azure", какие действия необходимо выполнить (отправка, слияние, удаление и слияниеOrUpload).
В Program.cs вы создаете массив документов и действий индекса, а затем передаете массив IndexDocumentsBatch
в . Следующие документы соответствуют индексу hotels-quickstart, как определено классом отеля.
// Upload documents in a single Upload request.
private static void UploadDocuments(SearchClient searchClient)
{
IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
IndexDocumentsAction.Upload(
new Hotel()
{
HotelId = "1",
HotelName = "Stay-Kay City Hotel",
Description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
DescriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.",
Category = "Boutique",
Tags = new[] { "pool", "air conditioning", "concierge" },
ParkingIncluded = false,
LastRenovationDate = new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero),
Rating = 3.6,
Address = new Address()
{
StreetAddress = "677 5th Ave",
City = "New York",
StateProvince = "NY",
PostalCode = "10022",
Country = "USA"
}
}),
// REDACTED FOR BREVITY
}
Создав экземпляр объекта IndexDocumentsBatch, вы сможете отправить его в индекс, вызвав IndexDocuments из объекта SearchClient.
Вы загружаете документы с помощью SearchClient, Main()
но операция также требует прав администратора в службе, которая обычно связана с SearchIndexClient. Один из способов настройки этой операции — получить SearchClient через SearchIndexClient
(searchIndexClient
в этом примере).
SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);
// Load documents
Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(ingesterClient);
Так как у нас есть консольное приложение, которое выполняет все команды последовательно, мы добавим 2-секундное время ожидания между индексированием и запросами.
// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
Console.WriteLine("Waiting for indexing...\n");
System.Threading.Thread.Sleep(2000);
2-секундная задержка дает достаточно времени для асинхронного индексирования, чтобы все документы уже были проиндексированы перед выполнением запросов. Задержки в коде обычно используются только в демонстрациях, тестах и примерах приложений.
Поиск в индексе
Результаты запросов можно получить сразу по завершении индексирования первого документа, но для полноценного тестирования индекса придется подождать, пока закончится индексирование всех документов.
В этом разделе мы добавим две новые функции: логику запроса и результаты. Для запросов примените метод Search. Этот метод принимает текст поиска (строку запроса) и другие параметры.
Класс SearchResults представляет результаты запроса.
В Program.csWriteDocuments
метод выводит результаты поиска в консоль.
// Write search results to console
private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
foreach (SearchResult<Hotel> result in searchResults.GetResults())
{
Console.WriteLine(result.Document);
}
Console.WriteLine();
}
private static void WriteDocuments(AutocompleteResults autoResults)
{
foreach (AutocompleteItem result in autoResults.Results)
{
Console.WriteLine(result.Text);
}
Console.WriteLine();
}
Пример запроса 1
Метод RunQueries
выполняет запросы и возвращает результаты. Результаты имеют формат объектов Hotel. В этом примере показана сигнатура метода и первый запрос. Этот запрос демонстрирует Select
параметр, позволяющий создавать результат с помощью выбранных полей из документа.
// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
SearchOptions options;
SearchResults<Hotel> response;
// Query 1
Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
options = new SearchOptions()
{
IncludeTotalCount = true,
Filter = "",
OrderBy = { "" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Address/City");
response = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);
// REDACTED FOR BREVITY
}
Пример запроса 2
Во втором запросе выполните поиск по термину, добавьте фильтр, который выбирает документы, где рейтинг больше 4, а затем сортируйте по рейтингу в порядке убывания. Фильтром называется логическое выражение, которое оценивается для всех полей в индексе с атрибутом IsFilterable. Фильтрующие запросы могут включать или исключать указанные значения. Таким образом, оценка релевантности не связана с запросом фильтра.
// Query 2
Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
options = new SearchOptions()
{
Filter = "Rating gt 4",
OrderBy = { "Rating desc" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Rating");
response = searchClient.Search<Hotel>("hotels", options);
WriteDocuments(response);
Пример запроса 3
Третий запрос демонстрирует searchFields
область действия полнотекстового поиска для определенных полей.
// Query 3
Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n");
options = new SearchOptions()
{
SearchFields = { "Tags" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Tags");
response = searchClient.Search<Hotel>("pool", options);
WriteDocuments(response);
Пример запроса 4
Четвертый запрос демонстрирует facets
, который можно использовать для структуры фасетной структуры навигации.
// Query 4
Console.WriteLine("Query #4: Facet on 'Category'...\n");
options = new SearchOptions()
{
Filter = ""
};
options.Facets.Add("Category");
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Category");
response = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);
Пример запроса 5
В пятом запросе возвращается конкретный документ. Поиск документов — это типичный ответ на OnClick
событие в результирующем наборе.
// Query 5
Console.WriteLine("Query #5: Look up a specific document...\n");
Response<Hotel> lookupResponse;
lookupResponse = searchClient.GetDocument<Hotel>("3");
Console.WriteLine(lookupResponse.Value.HotelId);
Пример запроса 6
В последнем запросе показан синтаксис автозавершения, имитация частичного пользовательского ввода sa , разрешающего два возможных совпадения в sourceFields, связанных с предложением, определенным в индексе.
// Query 6
Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");
var autoresponse = searchClient.Autocomplete("sa", "sg");
WriteDocuments(autoresponse);
Сводка запросов
В предыдущих запросах показано несколько способов сопоставления условий в запросе: полнотекстовый поиск, фильтры и автозавершение.
Полнотекстовый поиск и фильтрация выполняются с помощью метода SearchClient.Search. Строку поиска можно передать в параметре searchText
, а выражение фильтра — свойстве Filter класса SearchOptions. Чтобы выполнить фильтрацию без поиска, передайте "*"
в качестве значения параметра searchText
в метод Search. Чтобы выполнить поиск без фильтрации, оставьте Filter
свойство неустановленным или не передайте в SearchOptions
экземпляре вообще.
Узнайте, как использовать клиентская библиотека Azure.Search.Documents для создания, загрузки и запроса индекса поиска с использованием примеров данных для полнотекстового поиска. Полнотекстовый поиск использует Apache Lucene для индексирования и запросов, а также алгоритм ранжирования BM25 для оценки результатов.
В этом кратком руководстве создается и запрашивается индекс небольшого быстрого запуска отелей, содержащий данные о четырех отелях.
Совет
Исходный код можно скачать, чтобы начать с готового проекта или выполнить следующие действия, чтобы создать собственный.
Необходимые компоненты
- Активная подписка Azure — создайте подписку бесплатно.
- Служба ИИ Azure. Создайте службу , если у вас ее нет. Вы можете использовать бесплатный уровень для этого краткого руководства.
Предварительные требования для идентификатора Microsoft Entra
Для рекомендуемой проверки подлинности без ключа с помощью идентификатора Microsoft Entra необходимо:
- Установите Azure CLI, используемый для проверки подлинности без ключа с помощью идентификатора Microsoft Entra.
- Назначьте обе роли учетной
Search Service Contributor
Search Index Data Contributor
записи пользователя. Роли можно назначить в портал Azure в разделе управления доступом (IAM)>Добавить назначение ролей. Дополнительные сведения см. в статье "Подключение к поиску ИИ Azure" с помощью ролей.
Получение сведений о ресурсе
Для проверки подлинности приложения с помощью azure AI служба необходимо получить следующие сведения:
Имя переменной | Значение |
---|---|
SEARCH_API_ENDPOINT |
Это значение можно найти в портал Azure. Выберите службу поиска, а затем в меню слева выберите "Обзор".
Значение URL-адреса в разделе Essentials — это необходимая конечная точка. Пример конечной точки может выглядеть так: https://mydemo.search.windows.net . |
Дополнительные сведения о бессерверной проверке подлинности и настройке переменных среды.
Настройка
Пример, приведенный в этом кратком руководстве, работает со средой выполнения Java. Установите пакет средств разработки Java, например Azul Zulu OpenJDK. Кроме того, должна работать сборка Microsoft OpenJDK или предпочтительный JDK.
Установите Apache Maven. Затем выполните команду
mvn -v
, чтобы подтвердить успешную установку.Создайте файл
pom.xml
в корне проекта и скопируйте в него следующий код:<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>azure.search.sample</groupId> <artifactId>azuresearchquickstart</artifactId> <version>1.0.0-SNAPSHOT</version> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-search-documents</artifactId> <version>11.7.3</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-core</artifactId> <version>1.53.0</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>1.15.1</version> </dependency> </dependencies> </project>
Установите зависимости, в том числе клиентская библиотека поиска ИИ Azure (Azure.Search.Documents) для клиентской библиотеки Java и удостоверений Azure для Java :
mvn clean dependency:copy-dependencies
Для рекомендуемой проверки подлинности без ключа с помощью идентификатора Microsoft Entra войдите в Azure с помощью следующей команды:
az login
Создание, загрузка и запрос индекса поиска
В предыдущем разделе настройки вы установили клиентская библиотека поиска ИИ Azure и другие зависимости.
В этом разделе вы добавите код для создания индекса поиска, загрузки его с документами и выполнения запросов. Запустите программу, чтобы просмотреть результаты в консоли. Подробные сведения о коде см . в описании раздела кода .
Пример кода в этом кратком руководстве использует идентификатор Microsoft Entra для рекомендуемой проверки подлинности без ключей. Если вы предпочитаете использовать ключ API, можно заменить DefaultAzureCredential
объект AzureKeyCredential
объектом.
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Создайте файл с именем App.java и вставьте следующий код в App.java:
import java.util.Arrays; import java.util.ArrayList; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.LocalDateTime; import java.time.LocalDate; import java.time.LocalTime; import com.azure.core.util.Configuration; import com.azure.core.util.Context; import com.azure.identity.DefaultAzureCredential; import com.azure.identity.DefaultAzureCredentialBuilder; import com.azure.search.documents.SearchClient; import com.azure.search.documents.SearchClientBuilder; import com.azure.search.documents.indexes.SearchIndexClient; import com.azure.search.documents.indexes.SearchIndexClientBuilder; import com.azure.search.documents.indexes.models.IndexDocumentsBatch; import com.azure.search.documents.models.SearchOptions; import com.azure.search.documents.indexes.models.SearchIndex; import com.azure.search.documents.indexes.models.SearchSuggester; import com.azure.search.documents.util.AutocompletePagedIterable; import com.azure.search.documents.util.SearchPagedIterable; public class App { public static void main(String[] args) { // Your search service endpoint "https://<Put your search service NAME here>.search.windows.net/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build(); //AzureKeyCredential credential = new AzureKeyCredential("<Your search service admin key>"); // Create a SearchIndexClient to send create/delete index commands SearchIndexClient searchIndexClient = new SearchIndexClientBuilder() .endpoint(searchServiceEndpoint) .credential(credential) .buildClient(); // Create a SearchClient to load and query documents String indexName = "hotels-quickstart-java"; SearchClient searchClient = new SearchClientBuilder() .endpoint(searchServiceEndpoint) .credential(credential) .indexName(indexName) .buildClient(); // Create Search Index for Hotel model searchIndexClient.createOrUpdateIndex( new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null)) .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName")))); // Upload sample hotel documents to the Search Index uploadDocuments(searchClient); // Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only) System.out.println("Waiting for indexing...\n"); try { Thread.sleep(2000); } catch (InterruptedException e) { } // Call the RunQueries method to invoke a series of queries System.out.println("Starting queries...\n"); RunQueries(searchClient); // End the program System.out.println("Complete.\n"); } // Upload documents in a single Upload request. private static void uploadDocuments(SearchClient searchClient) { var hotelList = new ArrayList<Hotel>(); var hotel = new Hotel(); hotel.hotelId = "1"; hotel.hotelName = "Stay-Kay City Hotel"; hotel.description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities."; hotel.descriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique."; hotel.category = "Boutique"; hotel.tags = new String[] { "pool", "air conditioning", "concierge" }; hotel.parkingIncluded = false; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1970, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 3.6; hotel.address = new Address(); hotel.address.streetAddress = "677 5th Ave"; hotel.address.city = "New York"; hotel.address.stateProvince = "NY"; hotel.address.postalCode = "10022"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "2"; hotel.hotelName = "Old Century Hotel"; hotel.description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts."; hotel.descriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne."; hotel.category = "Boutique"; hotel.tags = new String[] { "pool", "free wifi", "concierge" }; hotel.parkingIncluded = false; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1979, 2, 18), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 3.60; hotel.address = new Address(); hotel.address.streetAddress = "140 University Town Center Dr"; hotel.address.city = "Sarasota"; hotel.address.stateProvince = "FL"; hotel.address.postalCode = "34243"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "3"; hotel.hotelName = "Gastronomic Landscape Hotel"; hotel.description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services."; hotel.descriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne."; hotel.category = "Resort and Spa"; hotel.tags = new String[] { "air conditioning", "bar", "continental breakfast" }; hotel.parkingIncluded = true; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2015, 9, 20), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 4.80; hotel.address = new Address(); hotel.address.streetAddress = "3393 Peachtree Rd"; hotel.address.city = "Atlanta"; hotel.address.stateProvince = "GA"; hotel.address.postalCode = "30326"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "4"; hotel.hotelName = "Sublime Palace Hotel"; hotel.description = "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Palace is part of a lovingly restored 1800 palace."; hotel.descriptionFr = "Le Sublime Palace Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Palace fait partie d'un Palace 1800 restauré avec amour."; hotel.category = "Boutique"; hotel.tags = new String[] { "concierge", "view", "24-hour front desk service" }; hotel.parkingIncluded = true; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1960, 2, 06), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 4.60; hotel.address = new Address(); hotel.address.streetAddress = "7400 San Pedro Ave"; hotel.address.city = "San Antonio"; hotel.address.stateProvince = "TX"; hotel.address.postalCode = "78216"; hotel.address.country = "USA"; hotelList.add(hotel); var batch = new IndexDocumentsBatch<Hotel>(); batch.addMergeOrUploadActions(hotelList); try { searchClient.indexDocuments(batch); } catch (Exception e) { e.printStackTrace(); // If for some reason any documents are dropped during indexing, you can compensate by delaying and // retrying. This simple demo just logs failure and continues System.err.println("Failed to index some of the documents"); } } // Write search results to console private static void WriteSearchResults(SearchPagedIterable searchResults) { searchResults.iterator().forEachRemaining(result -> { Hotel hotel = result.getDocument(Hotel.class); System.out.println(hotel); }); System.out.println(); } // Write autocomplete results to console private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults) { autocompleteResults.iterator().forEachRemaining(result -> { String text = result.getText(); System.out.println(text); }); System.out.println(); } // Run queries, use WriteDocuments to print output private static void RunQueries(SearchClient searchClient) { // Query 1 System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n"); SearchOptions options = new SearchOptions(); options.setIncludeTotalCount(true); options.setFilter(""); options.setOrderBy(""); options.setSelect("HotelId", "HotelName", "Address/City"); WriteSearchResults(searchClient.search("*", options, Context.NONE)); // Query 2 System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n"); options = new SearchOptions(); options.setFilter("Rating gt 4"); options.setOrderBy("Rating desc"); options.setSelect("HotelId", "HotelName", "Rating"); WriteSearchResults(searchClient.search("hotels", options, Context.NONE)); // Query 3 System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n"); options = new SearchOptions(); options.setSearchFields("Tags"); options.setSelect("HotelId", "HotelName", "Tags"); WriteSearchResults(searchClient.search("pool", options, Context.NONE)); // Query 4 System.out.println("Query #4: Facet on 'Category'...\n"); options = new SearchOptions(); options.setFilter(""); options.setFacets("Category"); options.setSelect("HotelId", "HotelName", "Category"); WriteSearchResults(searchClient.search("*", options, Context.NONE)); // Query 5 System.out.println("Query #5: Look up a specific document...\n"); Hotel lookupResponse = searchClient.getDocument("3", Hotel.class); System.out.println(lookupResponse.hotelId); System.out.println(); // Query 6 System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n"); WriteAutocompleteResults(searchClient.autocomplete("s", "sg")); } }
Создайте файл с именем Hotel.java и вставьте следующий код в Hotel.java:
import com.azure.search.documents.indexes.SearchableField; import com.azure.search.documents.indexes.SimpleField; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonInclude.Include; import java.time.OffsetDateTime; /** * Model class representing a hotel. */ @JsonInclude(Include.NON_NULL) public class Hotel { /** * Hotel ID */ @JsonProperty("HotelId") @SimpleField(isKey = true) public String hotelId; /** * Hotel name */ @JsonProperty("HotelName") @SearchableField(isSortable = true) public String hotelName; /** * Description */ @JsonProperty("Description") @SearchableField(analyzerName = "en.microsoft") public String description; /** * French description */ @JsonProperty("DescriptionFr") @SearchableField(analyzerName = "fr.lucene") public String descriptionFr; /** * Category */ @JsonProperty("Category") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String category; /** * Tags */ @JsonProperty("Tags") @SearchableField(isFilterable = true, isFacetable = true) public String[] tags; /** * Whether parking is included */ @JsonProperty("ParkingIncluded") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public Boolean parkingIncluded; /** * Last renovation time */ @JsonProperty("LastRenovationDate") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public OffsetDateTime lastRenovationDate; /** * Rating */ @JsonProperty("Rating") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public Double rating; /** * Address */ @JsonProperty("Address") public Address address; @Override public String toString() { try { return new ObjectMapper().writeValueAsString(this); } catch (JsonProcessingException e) { e.printStackTrace(); return ""; } } }
Создайте файл с именем Address.java и вставьте следующий код в Address.java:
import com.azure.search.documents.indexes.SearchableField; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * Model class representing an address. */ @JsonInclude(Include.NON_NULL) public class Address { /** * Street address */ @JsonProperty("StreetAddress") @SearchableField public String streetAddress; /** * City */ @JsonProperty("City") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String city; /** * State or province */ @JsonProperty("StateProvince") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String stateProvince; /** * Postal code */ @JsonProperty("PostalCode") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String postalCode; /** * Country */ @JsonProperty("Country") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String country; }
Запустите новое консольное приложение:
javac Address.java App.java Hotel.java -cp ".;target\dependency\*" java -cp ".;target\dependency\*" App
Объяснение кода
В предыдущих разделах вы создали консольное приложение и установили клиентную библиотеку поиска ИИ Azure. Вы добавили код для создания индекса поиска, загрузки его с документами и выполнения запросов. Вы запустили программу, чтобы просмотреть результаты в консоли.
В этом разделе мы объясним код, добавленный в консольное приложение.
Создание клиента для поиска
В App.java вы создали два клиента:
- SearchIndexClient создает индекс.
- SearchClient загружает и запрашивает существующий индекс.
Обе клиенты нуждаются в конечной точке службы поиска и учетных данных, описанных ранее в разделе сведений о ресурсе.
Пример кода в этом кратком руководстве использует идентификатор Microsoft Entra для рекомендуемой проверки подлинности без ключей. Если вы предпочитаете использовать ключ API, можно заменить DefaultAzureCredential
объект AzureKeyCredential
объектом.
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
public static void main(String[] args) {
// Your search service endpoint
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
// Use the recommended keyless credential instead of the AzureKeyCredential credential.
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
//AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
// Create a SearchIndexClient to send create/delete index commands
SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(credential)
.buildClient();
// Create a SearchClient to load and query documents
String indexName = "hotels-quickstart-java";
SearchClient searchClient = new SearchClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(credential)
.indexName(indexName)
.buildClient();
// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
.setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
// REDACTED FOR BREVITY . . .
}
Создание индекса
В этом кратком руководстве создается индекс hotels, который загружается с данными отеля и выполняет запросы. На этом шаге вы определяете поля в индексе. Каждое определение поля содержит имя, тип данных и атрибуты, которые определяют способ использования этого поля.
В этом примере синхронные методы библиотеки Azure.Search.Documents используются для простоты и удобства чтения. Но для рабочих сценариев лучше использовать асинхронные методы, чтобы приложение было масштабируемым и отзывчивым. Например, следует использовать CreateIndexAsync вместо CreateIndex.
Определение структур
Вы создали два вспомогательных класса, Hotel.java и Address.java, чтобы определить структуру документа отеля и его адреса. Класс "Отель" содержит поля для идентификатора отеля, имени, описания, категории, тегов, парковки, даты обновления, оценки и адреса. Класс Address содержит поля для уличных адресов, города, штата или провинции, почтового индекса и страны или региона.
В клиентской библиотеке Azure.Search.Documents можно использовать SearchableField и SimpleField для упрощения определения полей.
-
SimpleField
может быть любым типом данных, всегда не выполняется поиск (игнорируется для запросов полнотекстового поиска) и извлекается (не скрыто). Другие атрибуты отключены по умолчанию, но их можно включить. Вы можете использовать SimpleField для идентификаторов документов или полей, используемых только в фильтрах, аспектах или профилях оценки. Если это так, обязательно примените все атрибуты, необходимые для сценария, например IsKey = true для идентификатора документа. -
SearchableField
должен быть строкой и всегда доступен для поиска и извлечения. Другие атрибуты отключены по умолчанию, но их можно включить. Так как этот тип поля доступен для поиска, он поддерживает синонимы и полное дополнение свойств анализатора.
Независимо от того, используется ли базовый API SearchField
или одна из вспомогательных моделей, необходимо явно включить атрибуты фильтров, аспектов и сортировки. Например, isFilterable
и isSortable
isFacetable
должен быть явным образом атрибутом, как показано в предыдущем примере.
Создание индекса поиска
В App.java
этом случае создается SearchIndex
объект в main
методе, а затем вызывается createOrUpdateIndex
метод для создания индекса в службе поиска. Индекс также включает SearchSuggester
функцию автозаполнения для указанных полей.
// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
.setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
Загрузка документов
Поиск azure AI выполняет поиск по содержимому, хранящемуся в службе. На этом шаге вы загружаете документы JSON, соответствующие созданному индексу отеля.
В поиске ИИ Azure документы поиска — это структуры данных, которые являются входными данными для индексирования и выходных данных из запросов. Полученные из внешнего источника данных входные документы могут содержать строки базы данных, большие двоичные объекты из хранилища BLOB-объектов или сохраненные на диске документы JSON. В нашем примере мы выбрали самый простой путь, внедрив прямо в код документы JSON с информацией о четырех отелях.
При отправке документов необходимо использовать объект IndexDocumentsBatch. Объект IndexDocumentsBatch
содержит коллекцию IndexActions, каждая из которых содержит документ и свойство, указывающее службе "Поиск ИИ Azure", какие действия необходимо выполнить (отправка, слияние, удаление и слияниеOrUpload).
В App.java
этом случае вы создаете документы и действия индекса, а затем передаете их IndexDocumentsBatch
в . Следующие документы соответствуют индексу hotels-quickstart, как определено классом отеля.
private static void uploadDocuments(SearchClient searchClient)
{
var hotelList = new ArrayList<Hotel>();
var hotel = new Hotel();
hotel.hotelId = "1";
hotel.hotelName = "Stay-Kay City Hotel";
hotel.description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.";
hotel.descriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.";
hotel.category = "Boutique";
hotel.tags = new String[] { "pool", "air conditioning", "concierge" };
hotel.parkingIncluded = false;
hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1970, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
hotel.rating = 3.6;
hotel.address = new Address();
hotel.address.streetAddress = "677 5th Ave";
hotel.address.city = "New York";
hotel.address.stateProvince = "NY";
hotel.address.postalCode = "10022";
hotel.address.country = "USA";
hotelList.add(hotel);
// REDACTED FOR BREVITY
var batch = new IndexDocumentsBatch<Hotel>();
batch.addMergeOrUploadActions(hotelList);
try
{
searchClient.indexDocuments(batch);
}
catch (Exception e)
{
e.printStackTrace();
// If for some reason any documents are dropped during indexing, you can compensate by delaying and
// retrying. This simple demo just logs failure and continues
System.err.println("Failed to index some of the documents");
}
}
После инициализации IndexDocumentsBatch
объекта его можно отправить в индекс, вызвав indexDocuments в объекте SearchClient
.
Вы загружаете документы с помощью SearchClient, main()
но операция также требует прав администратора в службе, которая обычно связана с SearchIndexClient. Один из способов настройки этой операции — получить SearchClient через SearchIndexClient
(searchIndexClient
в этом примере).
uploadDocuments(searchClient);
Так как у нас есть консольное приложение, которое выполняет все команды последовательно, мы добавим 2-секундное время ожидания между индексированием и запросами.
// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
System.out.println("Waiting for indexing...\n");
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
}
2-секундная задержка дает достаточно времени для асинхронного индексирования, чтобы все документы уже были проиндексированы перед выполнением запросов. Задержки в коде обычно используются только в демонстрациях, тестах и примерах приложений.
Поиск в индексе
Результаты запросов можно получить сразу по завершении индексирования первого документа, но для полноценного тестирования индекса придется подождать, пока закончится индексирование всех документов.
В этом разделе добавлены два элемента функциональности: логика запросов и результаты. Для запросов примените метод Search. Этот метод принимает текст поиска (строку запроса) и другие параметры.
В App.java
методе WriteDocuments
выводится результаты поиска в консоль.
// Write search results to console
private static void WriteSearchResults(SearchPagedIterable searchResults)
{
searchResults.iterator().forEachRemaining(result ->
{
Hotel hotel = result.getDocument(Hotel.class);
System.out.println(hotel);
});
System.out.println();
}
// Write autocomplete results to console
private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
{
autocompleteResults.iterator().forEachRemaining(result ->
{
String text = result.getText();
System.out.println(text);
});
System.out.println();
}
Пример запроса 1
Метод RunQueries
выполняет запросы и возвращает результаты. Результаты имеют формат объектов Hotel. В этом примере показана сигнатура метода и первый запрос. Этот запрос демонстрирует Select
параметр, позволяющий создавать результат с помощью выбранных полей из документа.
// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
// Query 1
System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
SearchOptions options = new SearchOptions();
options.setIncludeTotalCount(true);
options.setFilter("");
options.setOrderBy("");
options.setSelect("HotelId", "HotelName", "Address/City");
WriteSearchResults(searchClient.search("*", options, Context.NONE));
}
Пример запроса 2
Во втором запросе выполните поиск по термину, добавьте фильтр, который выбирает документы, где рейтинг больше 4, а затем сортируйте по рейтингу в порядке убывания. Фильтр — это логическое выражение, вычисляемое по isFilterable
полям в индексе. Фильтрующие запросы могут включать или исключать указанные значения. Таким образом, оценка релевантности не связана с запросом фильтра.
// Query 2
System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
options = new SearchOptions();
options.setFilter("Rating gt 4");
options.setOrderBy("Rating desc");
options.setSelect("HotelId", "HotelName", "Rating");
WriteSearchResults(searchClient.search("hotels", options, Context.NONE));
Пример запроса 3
Третий запрос демонстрирует searchFields
область действия полнотекстового поиска для определенных полей.
// Query 3
System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");
options = new SearchOptions();
options.setSearchFields("Tags");
options.setSelect("HotelId", "HotelName", "Tags");
WriteSearchResults(searchClient.search("pool", options, Context.NONE));
Пример запроса 4
Четвертый запрос демонстрирует facets
, который можно использовать для структуры фасетной структуры навигации.
// Query 4
System.out.println("Query #4: Facet on 'Category'...\n");
options = new SearchOptions();
options.setFilter("");
options.setFacets("Category");
options.setSelect("HotelId", "HotelName", "Category");
WriteSearchResults(searchClient.search("*", options, Context.NONE));
Пример запроса 5
В пятом запросе возвращается конкретный документ.
// Query 5
System.out.println("Query #5: Look up a specific document...\n");
Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
System.out.println(lookupResponse.hotelId);
System.out.println();
Пример запроса 6
Последний запрос показывает синтаксис автозавершения, имитируя частичные входные данные пользователя s , разрешающие два возможных совпадения в sourceFields
связанном с предложением, определенном в индексе.
// Query 6
System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");
WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));
Сводка запросов
В предыдущих запросах показано несколько способов сопоставления условий в запросе: полнотекстовый поиск, фильтры и автозавершение.
Полнотекстовый поиск и фильтры выполняются с помощью метода SearchClient.search . Запрос поиска можно передать в строке searchText
, а выражение фильтра можно передать в filter
свойстве класса SearchOptions . Чтобы отфильтровать без поиска, просто передайте "*" для searchText
параметра search
метода. Чтобы выполнить поиск без фильтрации, оставьте filter
свойство неустановленным или не передайте в SearchOptions
экземпляре вообще.
Узнайте, как использовать клиентская библиотека Azure.Search.Documents для создания, загрузки и запроса индекса поиска с использованием примеров данных для полнотекстового поиска. Полнотекстовый поиск использует Apache Lucene для индексирования и запросов, а также алгоритм ранжирования BM25 для оценки результатов.
В этом кратком руководстве создается и запрашивается индекс небольшого быстрого запуска отелей, содержащий данные о четырех отелях.
Совет
Исходный код можно скачать, чтобы начать с готового проекта или выполнить следующие действия, чтобы создать собственный.
Необходимые компоненты
- Активная подписка Azure — создайте подписку бесплатно.
- Служба ИИ Azure. Создайте службу , если у вас ее нет. Вы можете использовать бесплатный уровень для этого краткого руководства.
Предварительные требования для идентификатора Microsoft Entra
Для рекомендуемой проверки подлинности без ключа с помощью идентификатора Microsoft Entra необходимо:
- Установите Azure CLI, используемый для проверки подлинности без ключа с помощью идентификатора Microsoft Entra.
- Назначьте обе роли учетной
Search Service Contributor
Search Index Data Contributor
записи пользователя. Роли можно назначить в портал Azure в разделе управления доступом (IAM)>Добавить назначение ролей. Дополнительные сведения см. в статье "Подключение к поиску ИИ Azure" с помощью ролей.
Получение сведений о ресурсе
Для проверки подлинности приложения с помощью azure AI служба необходимо получить следующие сведения:
Имя переменной | Значение |
---|---|
SEARCH_API_ENDPOINT |
Это значение можно найти в портал Azure. Выберите службу поиска, а затем в меню слева выберите "Обзор".
Значение URL-адреса в разделе Essentials — это необходимая конечная точка. Пример конечной точки может выглядеть так: https://mydemo.search.windows.net . |
Дополнительные сведения о бессерверной проверке подлинности и настройке переменных среды.
Настройка
Создайте новую папку
full-text-quickstart
для хранения приложения и откройте Visual Studio Code в этой папке с помощью следующей команды:mkdir full-text-quickstart && cd full-text-quickstart
Создайте следующую
package.json
команду:npm init -y
Установите клиентскую библиотеку поиска ИИ Azure (Azure.Search.Documents) для JavaScript:
npm install @azure/search-documents
Для рекомендуемой проверки подлинности без пароля установите клиентская библиотека удостоверений Azure с помощью:
npm install @azure/identity
Создание, загрузка и запрос индекса поиска
В предыдущем разделе настройки вы установили клиентская библиотека поиска ИИ Azure и другие зависимости.
В этом разделе вы добавите код для создания индекса поиска, загрузки его с документами и выполнения запросов. Запустите программу, чтобы просмотреть результаты в консоли. Подробные сведения о коде см . в описании раздела кода .
Пример кода в этом кратком руководстве использует идентификатор Microsoft Entra для рекомендуемой проверки подлинности без ключей. Если вы предпочитаете использовать ключ API, можно заменить DefaultAzureCredential
объект AzureKeyCredential
объектом.
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Создайте файл с именем index.js и вставьте следующий код в index.js:
// Import from the @azure/search-documents library import { SearchIndexClient, odata } from "@azure/search-documents"; // Import from the Azure Identity library import { DefaultAzureCredential } from "@azure/identity"; // Importing the hotels sample data import hotelData from './hotels.json' assert { type: "json" }; // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); // Defining the index definition const indexDefinition = { "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String", "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Description_fr", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "fr.lucene" }, { "name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }; async function main() { // Your search service endpoint const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. const credential = new DefaultAzureCredential(); //const credential = new AzureKeyCredential(Your search service admin key); // Create a SearchIndexClient to send create/delete index commands const searchIndexClient = new SearchIndexClient(searchServiceEndpoint, credential); // Creating a search client to upload documents and issue queries const indexName = "hotels-quickstart"; const searchClient = searchIndexClient.getSearchClient(indexName); console.log('Checking if index exists...'); await deleteIndexIfExists(searchIndexClient, indexName); console.log('Creating index...'); let index = await searchIndexClient.createIndex(indexDefinition); console.log(`Index named ${index.name} has been created.`); console.log('Uploading documents...'); let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']); console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `); // waiting one second for indexing to complete (for demo purposes only) sleep(1000); console.log('Querying the index...'); console.log(); await sendQueries(searchClient); } async function deleteIndexIfExists(searchIndexClient, indexName) { try { await searchIndexClient.deleteIndex(indexName); console.log('Deleting index...'); } catch { console.log('Index does not exist yet.'); } } async function sendQueries(searchClient) { // Query 1 console.log('Query #1 - search everything:'); let searchOptions = { includeTotalCount: true, select: ["HotelId", "HotelName", "Rating"] }; let searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(`Result count: ${searchResults.count}`); console.log(); // Query 2 console.log('Query #2 - search with filter, orderBy, and select:'); let state = 'FL'; searchOptions = { filter: odata `Address/StateProvince eq ${state}`, orderBy: ["Rating desc"], select: ["HotelId", "HotelName", "Rating"] }; searchResults = await searchClient.search("wifi", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 3 console.log('Query #3 - limit searchFields:'); searchOptions = { select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("sublime cliff", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 4 console.log('Query #4 - limit searchFields and use facets:'); searchOptions = { facets: ["Category"], select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 5 console.log('Query #5 - Lookup document:'); let documentResult = await searchClient.getDocument('3'); console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`); console.log(); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } main().catch((err) => { console.error("The sample encountered an error:", err); });
Создайте файл с именем hotels.json и вставьте следующий код в hotels.json:
{ "value": [ { "HotelId": "1", "HotelName": "Secret Point Motel", "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.", "Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.", "Category": "Boutique", "Tags": ["pool", "air conditioning", "concierge"], "ParkingIncluded": false, "LastRenovationDate": "1970-01-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "677 5th Ave", "City": "New York", "StateProvince": "NY", "PostalCode": "10022" } }, { "HotelId": "2", "HotelName": "Twin Dome Motel", "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.", "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", "Category": "Boutique", "Tags": ["pool", "free wifi", "concierge"], "ParkingIncluded": "false", "LastRenovationDate": "1979-02-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "140 University Town Center Dr", "City": "Sarasota", "StateProvince": "FL", "PostalCode": "34243" } }, { "HotelId": "3", "HotelName": "Triple Landscape Hotel", "Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.", "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", "Category": "Resort and Spa", "Tags": ["air conditioning", "bar", "continental breakfast"], "ParkingIncluded": "true", "LastRenovationDate": "2015-09-20T00:00:00Z", "Rating": 4.8, "Address": { "StreetAddress": "3393 Peachtree Rd", "City": "Atlanta", "StateProvince": "GA", "PostalCode": "30326" } }, { "HotelId": "4", "HotelName": "Sublime Cliff Hotel", "Description": "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.", "Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.", "Category": "Boutique", "Tags": ["concierge", "view", "24-hour front desk service"], "ParkingIncluded": true, "LastRenovationDate": "1960-02-06T00:00:00Z", "Rating": 4.6, "Address": { "StreetAddress": "7400 San Pedro Ave", "City": "San Antonio", "StateProvince": "TX", "PostalCode": "78216" } } ] }
Создайте файл с именем hotels_quickstart_index.json и вставьте следующий код в hotels_quickstart_index.json:
{ "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String", "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Description_fr", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "fr.lucene" }, { "name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }
Войдите в Azure с помощью следующей команды:
az login
Запустите код JavaScript со следующей командой:
node index.js
Объяснение кода
Создание индекса
Файл hotels_quickstart_index.json определяет, как служба поиска ИИ Azure работает с документами, которые вы загружаете на следующем шаге. Каждое поле определяется name
и имеет указанный.type
Каждое поле также содержит ряд атрибутов индекса, определяющих, может ли поиск, фильтрация, сортировка и аспекты в поле поиска и поиска azure. Большинство полей имеют простой тип данных, но некоторые (например, AddressType
) являются сложными типами, что позволяет создавать сложные структуры данных в индексе. Вы можете подробнее изучить поддерживаемые типы данных и атрибуты индекса, описанные в статье Создание индекса (REST API службы "Когнитивный поиск Azure").
После определения индекса мы импортируем hotels_quickstart_index.json в верхней части файла index.js, чтобы функция main могла получить доступ к определению индекса.
const indexDefinition = require('./hotels_quickstart_index.json');
В основной функции мы создадим SearchIndexClient
, который используется для создания индексов и управления ими для поиска ИИ Azure.
const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));
Затем нужно удалить индекс, если он уже существует. Эта операция является распространенной практикой для тестового и демонстрационного кода.
Для этого нужно определить простую функцию, которая будет удалять индекс.
async function deleteIndexIfExists(indexClient, indexName) {
try {
await indexClient.deleteIndex(indexName);
console.log('Deleting index...');
} catch {
console.log('Index does not exist yet.');
}
}
Чтобы запустить функцию, извлеките имя индекса из определения индекса и передайте indexName
вместе с indexClient
в функцию deleteIndexIfExists()
.
const indexName = indexDefinition["name"];
console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);
После этого можно создать индекс с помощью метода createIndex()
.
console.log('Creating index...');
let index = await indexClient.createIndex(indexDefinition);
console.log(`Index named ${index.name} has been created.`);
Загрузка документов
В службе "Поиск ИИ Azure" документы — это структуры данных, которые являются входными и выходными данными для индексирования и выходных данных из запросов. Эти данные можно отправить в индекс или использовать индексатор. В этом случае мы будем программно отправлять документы в индекс.
Входные документы могут содержать строки базы данных, большие двоичные объекты из хранилища BLOB-объектов (как в этом примере) или сохраненные на диске документы JSON. Аналогично тому, что мы сделали с indexDefinition
этим, мы также должны импортировать hotels.json
в верхней части index.js , чтобы получить доступ к данным в основной функции.
const hotelData = require('./hotels.json');
Чтобы индексировать данные в индексе поиска, нужно создать SearchClient
. Хотя SearchIndexClient
используется для создания индекса и управления им, для отправки документов и запроса индекса используется SearchClient
.
Есть два способа создания представления данных SearchClient
. Первый вариант — создать SearchClient
с нуля:
const searchClient = new SearchClient(endpoint, indexName, new AzureKeyCredential(apiKey));
Кроме того, можно использовать метод getSearchClient()
из SearchIndexClient
для создания SearchClient
:
const searchClient = indexClient.getSearchClient(indexName);
Теперь, когда клиент определен, отправьте документы в индекс поиска. В этом случае мы используем метод, который отправляет документы или объединяет их с существующим документом mergeOrUploadDocuments()
, если документ с тем же ключом уже существует.
console.log('Uploading documents...');
let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);
Поиск в индексе
С помощью созданного индекса и отправленных документов можно отправлять запросы в индекс. В этом разделе мы отправим пять разных запросов в индекс поиска, чтобы продемонстрировать различные функциональность запросов, доступные для вас.
Запросы записываются в функцию, которую мы вызываем в главной sendQueries()
функции следующим образом:
await sendQueries(searchClient);
Запросы отправляются с помощью метода search()
объекта searchClient
. Первый параметр — это текст поиска, а второй параметр задает параметры поиска.
Пример запроса 1
Первый запрос выполняет поиск *
, что эквивалентно поиску по всем данным, и выбирает три поля в индексе. Рекомендуется использовать select
только для нужных вам полей, так как извлечение ненужных данных приведет к задержкам в запросах.
Для searchOptions
этого запроса также задано includeTotalCount
значение true
, которое возвращает количество найденных результатов сопоставления.
async function sendQueries(searchClient) {
console.log('Query #1 - search everything:');
let searchOptions = {
includeTotalCount: true,
select: ["HotelId", "HotelName", "Rating"]
};
let searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
console.log(`Result count: ${searchResults.count}`);
// remaining queries go here
}
Оставшиеся запросы, описанные ниже, также нужно добавить в функцию sendQueries()
. Они разделены здесь для удобства чтения.
Пример запроса 2
В следующем запросе укажите условие поиска "wifi"
, а также включите фильтр, который возвращает результаты только в том случае, если состояние равно 'FL'
. Результаты также упорядочиваются по данным отелей Rating
.
console.log('Query #2 - Search with filter, orderBy, and select:');
let state = 'FL';
searchOptions = {
filter: odata`Address/StateProvince eq ${state}`,
orderBy: ["Rating desc"],
select: ["HotelId", "HotelName", "Rating"]
};
searchResults = await searchClient.search("wifi", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
Пример запроса 3
Далее поиск ограничивается одним полем, поддерживающим поиск, с помощью параметра searchFields
. Этот подход является отличным вариантом, чтобы сделать запрос более эффективным, если вы знаете, что вы заинтересованы только в совпадениях в определенных полях.
console.log('Query #3 - Limit searchFields:');
searchOptions = {
select: ["HotelId", "HotelName", "Rating"],
searchFields: ["HotelName"]
};
searchResults = await searchClient.search("Sublime Palace", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
console.log();
Пример запроса 4
Еще один распространенный параметр, включаемый в запрос, — facets
. С помощью аспектов можно создавать фильтры в пользовательском интерфейсе, чтобы пользователям было проще узнать, какие значения они могут фильтровать.
console.log('Query #4 - Use facets:');
searchOptions = {
facets: ["Category"],
select: ["HotelId", "HotelName", "Rating"],
searchFields: ["HotelName"]
};
searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
Пример запроса 5
В последнем запросе используется метод getDocument()
объекта searchClient
. Это позволяет эффективно получать документ по ключу.
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
Сводка запросов
В предыдущих запросах показано несколько способов сопоставления условий в запросе: полнотекстовый поиск, фильтры и автозавершение.
Полнотекстовый поиск и фильтры выполняются с помощью searchClient.search
метода. Запрос поиска можно передать в строке searchText
, а выражение фильтра можно передать в filter
свойстве SearchOptions
класса. Чтобы отфильтровать без поиска, просто передайте "*" для searchText
параметра search
метода. Чтобы выполнить поиск без фильтрации, оставьте filter
свойство неустановленным или не передайте в SearchOptions
экземпляре вообще.
Узнайте, как использовать клиентская библиотека Azure.Search.Documents для создания, загрузки и запроса индекса поиска с использованием примеров данных для полнотекстового поиска. Полнотекстовый поиск использует Apache Lucene для индексирования и запросов, а также алгоритм ранжирования BM25 для оценки результатов.
В этом кратком руководстве создается и запрашивается индекс небольшого быстрого запуска отелей, содержащий данные о четырех отелях.
Необходимые компоненты
- Активная подписка Azure — создайте подписку бесплатно.
- Служба ИИ Azure. Создайте службу , если у вас ее нет. Вы можете использовать бесплатный уровень для этого краткого руководства.
- Visual Studio Code с расширением Python или эквивалентной интегрированной среды разработки с Python 3.10 или более поздней версии. Если у вас нет подходящей версии Python, вы можете следовать инструкциям в руководстве по Python VS Code.
Предварительные требования для идентификатора Microsoft Entra
Для рекомендуемой проверки подлинности без ключа с помощью идентификатора Microsoft Entra необходимо:
- Установите Azure CLI, используемый для проверки подлинности без ключа с помощью идентификатора Microsoft Entra.
- Назначьте обе роли учетной
Search Service Contributor
Search Index Data Contributor
записи пользователя. Роли можно назначить в портал Azure в разделе управления доступом (IAM)>Добавить назначение ролей. Дополнительные сведения см. в статье "Подключение к поиску ИИ Azure" с помощью ролей.
Получение сведений о ресурсе
Для проверки подлинности приложения с помощью azure AI служба необходимо получить следующие сведения:
Имя переменной | Значение |
---|---|
SEARCH_API_ENDPOINT |
Это значение можно найти в портал Azure. Выберите службу поиска, а затем в меню слева выберите "Обзор".
Значение URL-адреса в разделе Essentials — это необходимая конечная точка. Пример конечной точки может выглядеть так: https://mydemo.search.windows.net . |
Дополнительные сведения о бессерверной проверке подлинности и настройке переменных среды.
Настройка среды
Пример кода выполняется в записной книжке Jupyter. Поэтому необходимо настроить среду для запуска записных книжек Jupyter.
Скачайте или скопируйте пример записной книжки из GitHub.
Откройте записную книжку в Visual Studio Code.
Создайте новую среду Python для установки пакетов, необходимых для работы с этим руководством.
Внимание
Не устанавливайте пакеты в глобальную установку Python. При установке пакетов Python всегда следует использовать виртуальную или конда-среду, в противном случае можно разорвать глобальную установку Python.
Для настройки может потребоваться несколько минут. Если возникнут проблемы, ознакомьтесь со средами Python в VS Code.
Установите записные книжки Jupyter и ядро IPython для записных книжек Jupyter, если у вас их еще нет.
pip install jupyter pip install ipykernel python -m ipykernel install --user --name=.venv
Выберите ядро записной книжки.
- В правом верхнем углу записной книжки выберите " Выбрать ядро".
- Если вы видите
.venv
в списке, выберите его. Если вы этого не видите, выберите другую среду>.venv
Python ядра.>
Создание, загрузка и запрос индекса поиска
В этом разделе вы добавите код для создания индекса поиска, загрузки его с документами и выполнения запросов. Запустите программу, чтобы просмотреть результаты в консоли. Подробные сведения о коде см . в описании раздела кода .
Убедитесь, что записная книжка открыта в
.venv
ядре, как описано в предыдущем разделе.Запустите первую ячейку кода, чтобы установить необходимые пакеты, включая документы azure-search-documents.
! pip install azure-search-documents==11.6.0b1 --quiet ! pip install azure-identity --quiet ! pip install python-dotenv --quiet
Замените содержимое второй ячейки кода следующим кодом в зависимости от метода проверки подлинности.
Примечание.
Пример кода в этом кратком руководстве использует идентификатор Microsoft Entra для рекомендуемой проверки подлинности без ключей. Если вы предпочитаете использовать ключ API, можно заменить
DefaultAzureCredential
объектAzureKeyCredential
объектом.from azure.core.credentials import AzureKeyCredential from azure.identity import DefaultAzureCredential, AzureAuthorityHosts search_endpoint: str = "https://<Put your search service NAME here>.search.windows.net/" authority = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD credential = DefaultAzureCredential(authority=authority) index_name: str = "hotels-quickstart-python"
Удалите следующие две строки из ячейки "Создание кода индекса ". Учетные данные уже заданы в предыдущей ячейке кода.
from azure.core.credentials import AzureKeyCredential credential = AzureKeyCredential(search_api_key)
Запустите ячейку кода индекса , чтобы создать индекс поиска.
Запустите оставшиеся ячейки кода последовательно для загрузки документов и выполнения запросов.
Объяснение кода
Создание индекса
SearchIndexClient
используется для создания индексов и управления ими для поиска ИИ Azure. Каждое поле определяется и name
имеет указанный.type
Каждое поле также содержит ряд атрибутов индекса, определяющих, может ли поиск, фильтрация, сортировка и аспекты в поле поиска и поиска azure. Большинство полей имеют простой тип данных, но некоторые (например, AddressType
) являются сложными типами, что позволяет создавать сложные структуры данных в индексе. Вы можете подробнее изучить поддерживаемые типы данных и атрибуты индекса, описанные в статье Создание индекса (REST API службы "Когнитивный поиск Azure").
Создание полезных данных документов и отправка документов
Используйте действие индекса для типа операции, например отправку или слияние и отправку. Документы, полученные из примера HotelsData на GitHub.
Поиск в индексе
Результаты запросов можно получить сразу по завершении индексирования первого документа, но для полноценного тестирования индекса придется подождать, пока закончится индексирование всех документов.
Используйте метод поиска класса search.client.
Примеры запросов в записной книжке:
- Базовый запрос: выполняет пустой поиск (
search=*
), возвращая неподделанный список (оценка поиска = 1.0) произвольных документов. Поскольку условия не заданы, в результаты включаются все документы. - Запрос термина: добавляет целые термины в выражение поиска ("wifi"). Этот запрос указывает, что результаты содержат только поля в операторе
select
. Ограничение числа полей, которые возвращаются обратно, минимизирует объем данных, передаваемых по сети, и сокращает задержку поиска. - Отфильтрованный запрос: добавьте выражение фильтра, возвращая только те отели с рейтингом больше четырех, отсортированные по убыванию.
- Поле области. Добавление
search_fields
в область выполнения запроса области в определенные поля. - Аспекты: создание аспектов для положительных совпадений, найденных в результатах поиска. Нет нулевых совпадений. Если результаты поиска не включают термин Wifi, то wifi не отображается в фасетной структуре навигации.
- Поиск документа: возврат документа на основе его ключа. Эта операция полезна, если требуется выполнить детализацию, когда пользователь выбирает элемент в результатах поиска.
- Автозаполнение. Укажите возможные совпадения в поле поиска в качестве типов пользователей. Автозавершение использует средство предложения (
sg
), чтобы узнать, какие поля содержат потенциальные совпадения с запросами предложителя. В этом кратком руководстве эти поля:Tags
,Address/Country
Address/City
. Чтобы имитировать автозавершение, передайте буквы sa в виде частичной строки. Метод автозаполнения SearchClient возвращает подходящие термины.
Удаление индекса
Если вы закончите работу с этим индексом, его можно удалить, выполнив ячейку кода очистки. Удаление ненужных индексов освобождает пространство для пошагового просмотра дополнительных кратких руководств и руководств.
Узнайте, как использовать клиентская библиотека Azure.Search.Documents для создания, загрузки и запроса индекса поиска с использованием примеров данных для полнотекстового поиска. Полнотекстовый поиск использует Apache Lucene для индексирования и запросов, а также алгоритм ранжирования BM25 для оценки результатов.
В этом кратком руководстве создается и запрашивается индекс небольшого быстрого запуска отелей, содержащий данные о четырех отелях.
Совет
Исходный код можно скачать, чтобы начать с готового проекта или выполнить следующие действия, чтобы создать собственный.
Необходимые компоненты
- Активная подписка Azure — создайте подписку бесплатно.
- Служба ИИ Azure. Создайте службу , если у вас ее нет. Вы можете использовать бесплатный уровень для этого краткого руководства.
Предварительные требования для идентификатора Microsoft Entra
Для рекомендуемой проверки подлинности без ключа с помощью идентификатора Microsoft Entra необходимо:
- Установите Azure CLI, используемый для проверки подлинности без ключа с помощью идентификатора Microsoft Entra.
- Назначьте обе роли учетной
Search Service Contributor
Search Index Data Contributor
записи пользователя. Роли можно назначить в портал Azure в разделе управления доступом (IAM)>Добавить назначение ролей. Дополнительные сведения см. в статье "Подключение к поиску ИИ Azure" с помощью ролей.
Получение сведений о ресурсе
Для проверки подлинности приложения с помощью azure AI служба необходимо получить следующие сведения:
Имя переменной | Значение |
---|---|
SEARCH_API_ENDPOINT |
Это значение можно найти в портал Azure. Выберите службу поиска, а затем в меню слева выберите "Обзор".
Значение URL-адреса в разделе Essentials — это необходимая конечная точка. Пример конечной точки может выглядеть так: https://mydemo.search.windows.net . |
Дополнительные сведения о бессерверной проверке подлинности и настройке переменных среды.
Настройка
Создайте новую папку
full-text-quickstart
для хранения приложения и откройте Visual Studio Code в этой папке с помощью следующей команды:mkdir full-text-quickstart && cd full-text-quickstart
Создайте следующую
package.json
команду:npm init -y
Обновите ECMAScript с помощью следующей
package.json
команды:npm pkg set type=module
Установите клиентскую библиотеку поиска ИИ Azure (Azure.Search.Documents) для JavaScript:
npm install @azure/search-documents
Для рекомендуемой проверки подлинности без пароля установите клиентская библиотека удостоверений Azure с помощью:
npm install @azure/identity
Создание, загрузка и запрос индекса поиска
В предыдущем разделе настройки вы установили клиентская библиотека поиска ИИ Azure и другие зависимости.
В этом разделе вы добавите код для создания индекса поиска, загрузки его с документами и выполнения запросов. Запустите программу, чтобы просмотреть результаты в консоли. Подробные сведения о коде см . в описании раздела кода .
Пример кода в этом кратком руководстве использует идентификатор Microsoft Entra для рекомендуемой проверки подлинности без ключей. Если вы предпочитаете использовать ключ API, можно заменить DefaultAzureCredential
объект AzureKeyCredential
объектом.
const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
const credential = new DefaultAzureCredential();
Создайте файл с именем index.ts и вставьте следующий код в index.ts:
// Import from the @azure/search-documents library import { SearchIndexClient, SearchClient, SearchFieldDataType, AzureKeyCredential, odata, SearchIndex } from "@azure/search-documents"; // Import from the Azure Identity library import { DefaultAzureCredential } from "@azure/identity"; // Importing the hotels sample data import hotelData from './hotels.json' assert { type: "json" }; // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); // Defining the index definition const indexDefinition: SearchIndex = { "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String" as SearchFieldDataType, "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Description_fr", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "fr.lucene" }, { "name": "Category", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String" as SearchFieldDataType, "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }; async function main() { // Your search service endpoint const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. const credential = new DefaultAzureCredential(); //const credential = new AzureKeyCredential(Your search service admin key); // Create a SearchIndexClient to send create/delete index commands const searchIndexClient: SearchIndexClient = new SearchIndexClient( searchServiceEndpoint, credential ); // Creating a search client to upload documents and issue queries const indexName: string = "hotels-quickstart"; const searchClient: SearchClient<any> = searchIndexClient.getSearchClient(indexName); console.log('Checking if index exists...'); await deleteIndexIfExists(searchIndexClient, indexName); console.log('Creating index...'); let index: SearchIndex = await searchIndexClient.createIndex(indexDefinition); console.log(`Index named ${index.name} has been created.`); console.log('Uploading documents...'); let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']); console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `); // waiting one second for indexing to complete (for demo purposes only) sleep(1000); console.log('Querying the index...'); console.log(); await sendQueries(searchClient); } async function deleteIndexIfExists(searchIndexClient: SearchIndexClient, indexName: string) { try { await searchIndexClient.deleteIndex(indexName); console.log('Deleting index...'); } catch { console.log('Index does not exist yet.'); } } async function sendQueries(searchClient: SearchClient<any>) { // Query 1 console.log('Query #1 - search everything:'); let searchOptions: any = { includeTotalCount: true, select: ["HotelId", "HotelName", "Rating"] }; let searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(`Result count: ${searchResults.count}`); console.log(); // Query 2 console.log('Query #2 - search with filter, orderBy, and select:'); let state = 'FL'; searchOptions = { filter: odata`Address/StateProvince eq ${state}`, orderBy: ["Rating desc"], select: ["HotelId", "HotelName", "Rating"] }; searchResults = await searchClient.search("wifi", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 3 console.log('Query #3 - limit searchFields:'); searchOptions = { select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("sublime cliff", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 4 console.log('Query #4 - limit searchFields and use facets:'); searchOptions = { facets: ["Category"], select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 5 console.log('Query #5 - Lookup document:'); let documentResult = await searchClient.getDocument('3'); console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`); console.log(); } function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } main().catch((err) => { console.error("The sample encountered an error:", err); });
Создайте файл с именем hotels.json и вставьте следующий код в hotels.json:
{ "value": [ { "HotelId": "1", "HotelName": "Secret Point Motel", "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.", "Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.", "Category": "Boutique", "Tags": ["pool", "air conditioning", "concierge"], "ParkingIncluded": false, "LastRenovationDate": "1970-01-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "677 5th Ave", "City": "New York", "StateProvince": "NY", "PostalCode": "10022" } }, { "HotelId": "2", "HotelName": "Twin Dome Motel", "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.", "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", "Category": "Boutique", "Tags": ["pool", "free wifi", "concierge"], "ParkingIncluded": "false", "LastRenovationDate": "1979-02-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "140 University Town Center Dr", "City": "Sarasota", "StateProvince": "FL", "PostalCode": "34243" } }, { "HotelId": "3", "HotelName": "Triple Landscape Hotel", "Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.", "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.", "Category": "Resort and Spa", "Tags": ["air conditioning", "bar", "continental breakfast"], "ParkingIncluded": "true", "LastRenovationDate": "2015-09-20T00:00:00Z", "Rating": 4.8, "Address": { "StreetAddress": "3393 Peachtree Rd", "City": "Atlanta", "StateProvince": "GA", "PostalCode": "30326" } }, { "HotelId": "4", "HotelName": "Sublime Cliff Hotel", "Description": "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.", "Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.", "Category": "Boutique", "Tags": ["concierge", "view", "24-hour front desk service"], "ParkingIncluded": true, "LastRenovationDate": "1960-02-06T00:00:00Z", "Rating": 4.6, "Address": { "StreetAddress": "7400 San Pedro Ave", "City": "San Antonio", "StateProvince": "TX", "PostalCode": "78216" } } ] }
tsconfig.json
Создайте файл для транспиля кода TypeScript и скопируйте следующий код для ECMAScript.{ "compilerOptions": { "module": "NodeNext", "target": "ES2022", // Supports top-level await "moduleResolution": "NodeNext", "skipLibCheck": true, // Avoid type errors from node_modules "strict": true // Enable strict type-checking options }, "include": ["*.ts"] }
Транспилировать из TypeScript в JavaScript.
tsc
Войдите в Azure с помощью следующей команды:
az login
Запустите код JavaScript со следующей командой:
node index.js
Объяснение кода
Создание индекса
Создайте файл hotels_quickstart_index.json. Этот файл определяет, как служба "Поиск ИИ Azure" работает с документами, которые вы загружаете на следующем шаге. Каждое поле определяется name
и имеет указанный.type
Каждое поле также содержит ряд атрибутов индекса, определяющих, может ли поиск, фильтрация, сортировка и аспекты в поле поиска и поиска azure. Большинство полей имеют простой тип данных, но некоторые (например, AddressType
) являются сложными типами, что позволяет создавать сложные структуры данных в индексе. Вы можете подробнее изучить поддерживаемые типы данных и атрибуты индекса, описанные в статье Создание индекса (REST API службы "Когнитивный поиск Azure").
Мы хотим импортировать hotels_quickstart_index.json , чтобы основная функция может получить доступ к определению индекса.
import indexDefinition from './hotels_quickstart_index.json';
interface HotelIndexDefinition {
name: string;
fields: SimpleField[] | ComplexField[];
suggesters: SearchSuggester[];
};
const hotelIndexDefinition: HotelIndexDefinition = indexDefinition as HotelIndexDefinition;
В основной функции мы создадим SearchIndexClient
, который используется для создания индексов и управления ими для поиска ИИ Azure.
const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));
Затем нужно удалить индекс, если он уже существует. Эта операция является распространенной практикой для тестового и демонстрационного кода.
Для этого нужно определить простую функцию, которая будет удалять индекс.
async function deleteIndexIfExists(indexClient: SearchIndexClient, indexName: string): Promise<void> {
try {
await indexClient.deleteIndex(indexName);
console.log('Deleting index...');
} catch {
console.log('Index does not exist yet.');
}
}
Чтобы запустить функцию, извлеките имя индекса из определения индекса и передайте indexName
вместе с indexClient
в функцию deleteIndexIfExists()
.
// Getting the name of the index from the index definition
const indexName: string = hotelIndexDefinition.name;
console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);
После этого можно создать индекс с помощью метода createIndex()
.
console.log('Creating index...');
let index = await indexClient.createIndex(hotelIndexDefinition);
console.log(`Index named ${index.name} has been created.`);
Загрузка документов
В службе "Поиск ИИ Azure" документы — это структуры данных, которые являются входными и выходными данными для индексирования и выходных данных из запросов. Эти данные можно отправить в индекс или использовать индексатор. В этом случае мы будем программно отправлять документы в индекс.
Входные документы могут содержать строки базы данных, большие двоичные объекты из хранилища BLOB-объектов (как в этом примере) или сохраненные на диске документы JSON. Вы можете скачать файл hotels.json или создать собственный hotels.json со следующим содержимым:
Аналогично тому, что мы сделали с индексомDefinition, мы также должны импортировать hotels.json
в верхней части index.ts , чтобы получить доступ к данным в нашей главной функции.
import hotelData from './hotels.json';
interface Hotel {
HotelId: string;
HotelName: string;
Description: string;
Description_fr: string;
Category: string;
Tags: string[];
ParkingIncluded: string | boolean;
LastRenovationDate: string;
Rating: number;
Address: {
StreetAddress: string;
City: string;
StateProvince: string;
PostalCode: string;
};
};
const hotels: Hotel[] = hotelData["value"];
Чтобы индексировать данные в индексе поиска, нужно создать SearchClient
. Хотя SearchIndexClient
используется для создания индекса и управления им, для отправки документов и запроса индекса используется SearchClient
.
Есть два способа создания представления данных SearchClient
. Первый вариант — создать SearchClient
с нуля:
const searchClient = new SearchClient<Hotel>(endpoint, indexName, new AzureKeyCredential(apiKey));
Кроме того, можно использовать метод getSearchClient()
из SearchIndexClient
для создания SearchClient
:
const searchClient = indexClient.getSearchClient<Hotel>(indexName);
Теперь, когда клиент определен, отправьте документы в индекс поиска. В этом случае мы используем метод, который отправляет документы или объединяет их с существующим документом mergeOrUploadDocuments()
, если документ с тем же ключом уже существует. Затем убедитесь, что операция выполнена успешно, так как по крайней мере первый документ существует.
console.log("Uploading documents...");
const indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotels);
console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);
Снова запустите программу командой tsc && node index.ts
. Теперь набор сообщений будет немного отличаться от тех, которые вы видели на шаге 1. На этот раз индекс уже существует, а вы получите сообщение о его удалении перед тем, как приложение создаст новый индекс и поместит в него данные.
Прежде чем выполнять запросы на следующем шаге, определите функцию, чтобы программа ожидала в течение одной секунды. Это делается только для целей тестирования и демонстрации, чтобы обеспечить завершение индексирования и доступность документов в индексе для запросов.
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Чтобы программа ждала одну секунду, вызовите функцию sleep
:
sleep(1000);
Поиск в индексе
С помощью созданного индекса и отправленных документов можно отправлять запросы в индекс. В этом разделе мы отправим пять разных запросов в индекс поиска, чтобы продемонстрировать различные функциональность запросов, доступные для вас.
Запросы записываются в функцию, которую мы вызываем в главной sendQueries()
функции следующим образом:
await sendQueries(searchClient);
Запросы отправляются с помощью метода search()
объекта searchClient
. Первый параметр — это текст поиска, а второй параметр задает параметры поиска.
Пример запроса 1
Первый запрос выполняет поиск *
, что эквивалентно поиску по всем данным, и выбирает три поля в индексе. Рекомендуется использовать select
только для нужных вам полей, так как извлечение ненужных данных приведет к задержкам в запросах.
searchOptions
в этом запросе также имеет значение true
для параметра includeTotalCount
, возвращающего количество найденных результатов поиска.
async function sendQueries(
searchClient: SearchClient<Hotel>
): Promise<void> {
// Query 1
console.log('Query #1 - search everything:');
const selectFields: SearchFieldArray<Hotel> = [
"HotelId",
"HotelName",
"Rating",
];
const searchOptions1 = {
includeTotalCount: true,
select: selectFields
};
let searchResults = await searchClient.search("*", searchOptions1);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
console.log(`Result count: ${searchResults.count}`);
// remaining queries go here
}
Оставшиеся запросы, описанные ниже, также нужно добавить в функцию sendQueries()
. Они разделены здесь для удобства чтения.
Пример запроса 2
В следующем запросе укажите условие поиска "wifi"
, а также включите фильтр, который возвращает результаты только в том случае, если состояние равно 'FL'
. Результаты также упорядочиваются по данным отелей Rating
.
console.log('Query #2 - search with filter, orderBy, and select:');
let state = 'FL';
const searchOptions2 = {
filter: odata`Address/StateProvince eq ${state}`,
orderBy: ["Rating desc"],
select: selectFields
};
searchResults = await searchClient.search("wifi", searchOptions2);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
Пример запроса 3
Далее поиск ограничивается одним полем, поддерживающим поиск, с помощью параметра searchFields
. Этот подход является отличным вариантом, чтобы сделать запрос более эффективным, если вы знаете, что вы заинтересованы только в совпадениях в определенных полях.
console.log('Query #3 - limit searchFields:');
const searchOptions3 = {
select: selectFields,
searchFields: ["HotelName"] as const
};
searchResults = await searchClient.search("Sublime Palace", searchOptions3);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
Пример запроса 4
Еще один распространенный параметр, включаемый в запрос, — facets
. Аспекты позволяют предоставлять самонаправленную детализацию из результатов в пользовательском интерфейсе. Результаты аспектов можно превратить в флажки в области результатов.
console.log('Query #4 - limit searchFields and use facets:');
const searchOptions4 = {
facets: ["Category"],
select: selectFields,
searchFields: ["HotelName"] as const
};
searchResults = await searchClient.search("*", searchOptions4);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
Пример запроса 5
В последнем запросе используется метод getDocument()
объекта searchClient
. Это позволяет эффективно получать документ по ключу.
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument('3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
Сводка запросов
В предыдущих запросах показано несколько способов сопоставления условий в запросе: полнотекстовый поиск, фильтры и автозавершение.
Полнотекстовый поиск и фильтры выполняются с помощью searchClient.search
метода. Запрос поиска можно передать в строке searchText
, а выражение фильтра можно передать в filter
свойстве SearchOptions
класса. Чтобы отфильтровать без поиска, просто передайте "*" для searchText
параметра search
метода. Чтобы выполнить поиск без фильтрации, оставьте filter
свойство неустановленным или не передайте в SearchOptions
экземпляре вообще.
Очистка ресурсов
Если вы работаете в собственной подписке, в конце проекта следует решить, нужны ли вам созданные ресурсы. Ресурсы, которые продолжат работать, могут быть платными. Вы можете удалить ресурсы по отдельности либо удалить всю группу ресурсов.
Ресурсы и управление ими можно найти в портал Azure, используя ссылку "Все ресурсы" или "Группы ресурсов" в области навигации слева.
Если вы используете бесплатную службу, помните, что вы ограничены тремя индексами, индексаторами и источниками данных. Вы можете удалить отдельные элементы в портал Azure, чтобы остаться в пределах ограничения.
Следующий шаг
В этом кратком руководстве описан набор задач для создания индекса, загрузки его с документами и выполнения запросов. Мы несколько упростили решение, чтобы код было проще читать и понимать. Теперь, когда вы знакомы с основными понятиями, ознакомьтесь с руководством, которое вызывает API поиска ИИ Azure в веб-приложении.