Краткое руководство. Полнотекстовый поиск с помощью пакетов SDK Azure

Узнайте, как использовать клиентская библиотека Azure.Search.Documents в пакете SDK Azure для создания, загрузки и запроса индекса поиска с использованием примеров данных для полнотекстового поиска. Полнотекстовый поиск использует Apache Lucene для индексирования и запросов, а также алгоритм ранжирования BM25 для оценки результатов.

В этом кратком руководстве описаны следующие пакеты SDK:

Необходимые компоненты

  • Учетная запись Azure с активной подпиской. Создайте учетную запись бесплатно .

  • Служба ИИ Azure. Создайте службу , если у вас ее нет. Вы можете использовать бесплатный уровень для этого краткого руководства.

  • Ключ API и конечная точка службы. Войдите в портал Azure и найдите службу поиска.

    В обзоре скопируйте URL-адрес и сохраните его в Блокнот для последующего шага. Пример конечной точки может выглядеть так: https://mydemo.search.windows.net.

    В ключах скопируйте и сохраните ключ администратора для полного доступа к созданию и удалению объектов. Существует две взаимозаменяемых пары первичного и вторичного ключей. Выберите один из них.

    Получение конечной точки HTTP и ключа доступа

Создание, загрузка и запрос индекса

Выберите язык программирования для следующего шага. Клиентские библиотеки Azure.Search.Documents доступны в пакетах SDK Azure для .NET, Python, Java и JavaScript.

Создайте консольное приложение с помощью клиентской библиотеки Azure.Search.Documents для создания, загрузки и запроса индекса поиска. Кроме того, можно скачать исходный код , чтобы начать с готового проекта или выполнить следующие действия, чтобы создать собственный.

Настройка среды

  1. Запустите Visual Studio и создайте проект для консольного приложения.

  2. В разделе Инструменты>Диспетчер пакетов NuGet выберите Управление пакетами NugGet для решения....

  3. Выберите Обзор.

  4. Найдите пакет Azure.Search.Documents и выберите версию 11.0 или более позднюю.

  5. Выберите " Установить " справа, чтобы добавить сборку в проект и решение.

Создание клиента для поиска

  1. В файле Program.cs измените пространство имен на AzureSearch.SDK.Quickstart.v11, а затем добавьте следующие директивы using.

    using Azure;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    using Azure.Search.Documents.Models;
    
  2. Создайте два клиента: SearchIndexClient создает индекс, а SearchClient загружает и запрашивает существующий индекс. Обоим клиентам нужны конечная точка службы и ключ API администрирования, чтобы пройти проверку подлинности и получить права на создание и удаление.

    Так как код создает универсальный код ресурса (URI) для вас, укажите только имя службы поиска в свойстве serviceName.

     static void Main(string[] args)
     {
         string serviceName = "<your-search-service-name>";
         string apiKey = "<your-search-service-admin-api-key>";
         string indexName = "hotels-quickstart";
    
         // Create a SearchIndexClient to send create/delete index commands
         Uri serviceEndpoint = new Uri($"https://{serviceName}.search.windows.net/");
         AzureKeyCredential credential = new AzureKeyCredential(apiKey);
         SearchIndexClient adminClient = new SearchIndexClient(serviceEndpoint, credential);
    
         // Create a SearchClient to load and query documents
         SearchClient srchclient = new SearchClient(serviceEndpoint, indexName, credential);
         . . . 
     }
    

Создание индекса

В этом кратком руководстве показано, как создать индекс Hotels, в который вы позже загрузите данные об отелях и к которому будете выполнять запросы. На этом шаге следует определить поля в этом индексе. Каждое определение поля содержит имя, тип данных и атрибуты, которые определяют способ использования этого поля.

В нашем примере для простоты и удобочитаемости используются синхронные методы библиотеки Azure.Search.Documents. Но для рабочих сценариев лучше использовать асинхронные методы, чтобы приложение было масштабируемым и отзывчивым. Например, следует использовать CreateIndexAsync вместо CreateIndex.

  1. Добавление пустого определения класса в проект: Hotel.cs

  2. Скопируйте следующий код в Hotel.cs, чтобы определить структуру документа hotel. Атрибуты в поле определяют, как оно используется в приложении. Например, атрибут 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; }
        }
    }
    

    В клиентской библиотеке Azure.Search.Documents можно использовать SearchableField и SimpleField для упрощения определения полей. Оба метода являются производными от SearchField и потенциально могут упростить код:

    • SimpleField может быть любым типом данных, он всегда недоступен для поиска (он игнорируется для запросов полнотекстового поиска) и его можно извлечь (он не скрыт). Другие атрибуты отключены по умолчанию, но их можно включить. SimpleField можно использовать для идентификаторов документов или полей, используемых только в фильтрах, аспектах или профилях оценки. Если это так, обязательно примените все необходимые для сценария атрибуты, например IsKey = true для идентификатора документа. Чтобы узнать больше, см. SimpleFieldAttribute.cs в исходном коде.

    • SearchableField должен быть строкой и всегда доступен для поиска и извлечения. Другие атрибуты отключены по умолчанию, но их можно включить. Так как этот тип поля доступен для поиска, он поддерживает синонимы и полное дополнение свойств анализатора. Чтобы узнать больше, см. SearchableFieldAttribute.cs в исходном коде.

    Независимо от того, используется ли базовый API SearchField или одна из вспомогательных моделей, необходимо явно включить атрибуты фильтров, аспектов и сортировки. Например, IsFilterable, IsSortable и IsFacetable должны присваиваться явным образом, как в примере выше.

  3. Добавьте в проект второе пустое определение класса: 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; }
         }
     }
    
  4. Создайте еще два класса: Hotel.Methods.cs и Address.Methods.cs для переопределений ToString(). Эти классы используются для отображения результатов поиска в выходных данных консоли. Содержимое этих классов не указано в этой статье, но вы можете скопировать код из файлов в GitHub.

  5. В Program.cs создайте объект SearchClient, а затем вызовите метод CreateIndex, чтобы отразить индекс в службе поиска. Индекс также содержит SearchSuggester, чтобы применить автозаполнение для указанных полей.

     // Create hotels-quickstart index
     private static void CreateIndex(string indexName, SearchIndexClient adminClient)
     {
         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);
    
         adminClient.CreateOrUpdateIndex(definition);
     }
    

Загрузка документов

Поиск azure AI выполняет поиск по содержимому, хранящемуся в службе. На этом шаге вы отправите документы JSON, которые соответствуют формату созданного индекса отелей.

В поиске ИИ Azure документы поиска — это структуры данных, которые являются входными данными для индексирования и выходных данных из запросов. Полученные из внешнего источника данных входные документы могут содержать строки базы данных, большие двоичные объекты из хранилища BLOB-объектов или сохраненные на диске документы JSON. В нашем примере мы выбрали самый простой путь, внедрив прямо в код документы JSON с информацией о четырех отелях.

При отправке документов необходимо использовать объект IndexDocumentsBatch. Объект IndexDocumentsBatch содержит коллекцию Actions, каждая из которых содержит документ и свойство, указывающее службе "Поиск ИИ Azure", какие действия необходимо выполнить (отправка, слияние, удаление и слияниеOrUpload).

  1. В файле Program.cs создайте массив документов и действий индексирования, а затем передайте этот массив в IndexDocumentsBatch. Приведенные ниже документы соответствуют индексу hotels-quickstart, как определено классом hotel.

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

    Создав экземпляр объекта IndexDocumentsBatch, вы сможете отправить его в индекс, вызвав IndexDocuments из объекта SearchClient.

  2. Добавьте следующие строки в Main(). Загрузка документов выполняется с помощью SearchClient, однако для операции также понадобятся права администратора для службы, которая обычно связана с SearchIndexClient. Одним из способов настройки этой операции является получение SearchClient через SearchIndexClient (в этом примере — adminClient).

     SearchClient ingesterClient = adminClient.GetSearchClient(indexName);
    
     // Load documents
     Console.WriteLine("{0}", "Uploading documents...\n");
     UploadDocuments(ingesterClient);
    
  3. Так как это консольное приложение выполняет все команды последовательно, включите 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 представляет результаты запроса.

  1. В файле Program.cs создайте метод WriteDocuments, который выводит на консоль результаты поиска.

    // 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();
    }
    
  2. Создайте метод RunQueries для выполнения запросов и возврата результатов. Результаты имеют формат объектов Hotel. В этом примере показана сигнатура метода и первый запрос. В этом запросе представлен параметр Select, позволяющий составить результат, используя выбранные поля документа.

    // Run queries, use WriteDocuments to print output
    private static void RunQueries(SearchClient srchclient)
    {
        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 = srchclient.Search<Hotel>("*", options);
        WriteDocuments(response);
    
  3. Во втором запросе выполните поиск по термину, добавьте фильтр для выбора документов с оценкой больше 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 = srchclient.Search<Hotel>("hotels", options);
    WriteDocuments(response);
    
  4. Третий запрос демонстрирует свойство 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 = srchclient.Search<Hotel>("pool", options);
    WriteDocuments(response);
    
  5. Четвертый запрос демонстрирует аспекты, которые можно использовать для составления структуры фасетной навигации.

     // 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 = srchclient.Search<Hotel>("*", options);
     WriteDocuments(response);
    
  6. В пятом запросе возвращается конкретный документ. Поиск документа — это типичный ответ на событие OnClick в результирующем наборе.

     // Query 5
     Console.WriteLine("Query #5: Look up a specific document...\n");
    
     Response<Hotel> lookupResponse;
     lookupResponse = srchclient.GetDocument<Hotel>("3");
    
     Console.WriteLine(lookupResponse.Value.HotelId);
    
  7. Последний запрос представляет синтаксис средства автозавершения, моделирующего частичный ввод пользователем слова на "sa", при котором в свойстве sourceFields, связанном со средством подбора, определенным в индексе, распознается два возможных совпадения.

     // Query 6
     Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");
    
     var autoresponse = srchclient.Autocomplete("sa", "sg");
     WriteDocuments(autoresponse);
    
  8. Добавьте RunQueries в Main().

    // Call the RunQueries method to invoke a series of queries
    Console.WriteLine("Starting queries...\n");
    RunQueries(srchclient);
    
    // End the program
    Console.WriteLine("{0}", "Complete. Press any key to end this program...\n");
    Console.ReadKey();
    

В предыдущих запросах показано несколько способов сопоставления условий в запросе: полнотекстовый поиск, фильтры и автозавершение.

Полнотекстовый поиск и фильтрация выполняются с помощью метода SearchClient.Search. Строку поиска можно передать в параметре searchText, а выражение фильтра — свойстве Filter класса SearchOptions. Чтобы выполнить фильтрацию без поиска, передайте "*" в качестве значения параметра searchText в метод Search. Чтобы выполнить поиск без фильтрации, оставьте Filter свойство неустановленным или не передайте в SearchOptions экземпляре вообще.

Запуск программы

Нажмите клавишу F5, чтобы перестроить приложение и запустить полнофункциональную программу.

Выходные данные содержат сообщения из Console.WriteLine, а также сведения о запросе и результаты.

Очистка ресурсов

Если вы работаете в собственной подписке, в конце проекта следует решить, нужны ли вам созданные ресурсы. Ресурсы, которые продолжат работать, могут быть платными. Вы можете удалить ресурсы по отдельности либо удалить всю группу ресурсов.

Просматривать ресурсы и управлять ими можно на портале с помощью ссылок Все ресурсы или Группы ресурсов на панели навигации слева.

Если вы используете бесплатную службу, помните, что вы ограничены тремя индексами, индексаторами и источниками данных. Вы можете удалить отдельные элементы на портале, чтобы не превысить лимит.

Следующие шаги

В этом кратком руководстве описан набор задач для создания индекса, загрузки его с документами и выполнения запросов. Мы несколько упростили решение, чтобы код было проще читать и понимать. Теперь, когда вы знакомы с основными понятиями, ознакомьтесь с руководством, которое вызывает API поиска ИИ Azure в веб-приложении.