Добавление фасетной навигации в приложение поиска

Фасетная навигация используется для самостоятельной фильтрации детализации по результатам запроса в приложении поиска, где приложение предлагает элементы управления формами для поиска в группах документов (например, категорий или брендов), а поиск ИИ Azure предоставляет структуры данных и фильтры для поддержки взаимодействия.

В этой статье описаны основные шаги по созданию фасетной структуры навигации в службе "Поиск ИИ Azure".

  • Установка атрибутов поля в индексе
  • Структура запроса и ответа
  • Добавление элементов управления навигации и фильтров на уровне презентации

Код в слое презентации делает тяжелый подъем в фасетной навигации. Демонстрации и примеры, перечисленные в конце этой статьи, предоставляют рабочий код, который показывает, как объединить все.

Фасетная навигация на странице поиска

Аспекты являются динамическими и возвращаются в запросе. Ответ поиска содержит все категории аспектов, используемые для навигации по документам в результатах. Запрос выполняется сначала, а затем аспекты извлекаются из текущих результатов и собираются в фасетную структуру навигации.

В поиске ИИ Azure аспекты являются одним слоем глубокой и не могут быть иерархическими. Если вы не знакомы с фасетными структурами навигации, в следующем примере показан один из них слева. Счетчики указывают количество совпадений для каждого аспекта. Один и тот же документ можно представить в нескольких аспектах.

Screenshot of faceted search results.

С помощью фасетов вы можете найти нужные данные. Вероятность нулевого результата практически исключена. Используя фасеты, разработчики могут определить наиболее полезные критерии поиска для навигации по индексу поиска.

Включение аспектов в индексе

Аспекты включены в определении индекса, если присвоить атрибуту facetable значение true.

Хотя это не является строго обязательным, необходимо также задать атрибут "фильтруемый" для создания необходимых фильтров, которые возвращают аспектную навигацию в приложении поиска.

В следующем примере примера индекса "hotels" показаны "фасетные" и "фильтруемые" поля с низкой карта inality, содержащие отдельные значения или короткие фразы: "Категория", "Теги", "Рейтинг".

{
  "name": "hotels",  
  "fields": [
    { "name": "hotelId", "type": "Edm.String", "key": true, "searchable": false, "sortable": false, "facetable": false },
    { "name": "Description", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false },
    { "name": "HotelName", "type": "Edm.String", "facetable": false },
    { "name": "Category", "type": "Edm.String", "filterable": true, "facetable": true },
    { "name": "Tags", "type": "Collection(Edm.String)", "filterable": true, "facetable": true },
    { "name": "Rating", "type": "Edm.Int32", "filterable": true, "facetable": true },
    { "name": "Location", "type": "Edm.GeographyPoint" }
  ]
}

Выбор полей

Аспекты можно вычислять по полям с одним значением, а также коллекциям. Поля, которые лучше всего работают в фасетной навигации, имеют следующие характеристики:

  • Низкая карта inality (небольшое количество уникальных значений, повторяющихся в документах в корпусе поиска)

  • Короткие описательные значения (один или два слова), которые будут хорошо отображаться в дереве навигации

Значения в поле, а не имя поля, создают аспекты в фасетной структуре навигации. Если аспект является строковым полем с именем Color, аспекты будут синим, зеленым и любым другим значением для этого поля.

Рекомендуется проверка поля для значений NULL, ошибок или несоответствий регистра, а также одно и множественное число версий одного и того же слова. По умолчанию фильтры и аспекты не проходят лексический анализ или проверка орфографии, что означает, что все значения поля "facetable" являются потенциальными аспектами, даже если слова отличаются одним символом. При необходимости можно назначить нормализатор поля "фильтруемым" и "фасетным" для сглаживания вариантов в регистре и символах.

Значения по умолчанию в REST и пакетах SDK Azure

Если вы используете один из пакетов SDK Azure, код должен явно задать атрибуты поля. В отличие от этого, REST API имеет значения по умолчанию для атрибутов полей на основе типа данных. Следующие типы данных являются "фильтруемыми" и "фасетными" по умолчанию:

  • Edm.String
  • Edm.DateTimeOffset
  • Edm.Boolean
  • Edm.Int32, , Edm.Int64Edm.Double
  • Коллекции любого из указанных выше типов, например Collection(Edm.String) или Collection(Edm.Double)

Нельзя использовать Edm.GeographyPoint или Collection(Edm.GeographyPoint) поля в фасетной навигации. Фасеты лучше всего работают в полях с низкой мощностью. Из-за разрешения географических координат редко в заданном наборе данных будут равны любые два набора координат. Таким образом, аспекты не поддерживаются для гео координат. Вам понадобится поле города или региона для поиска по местоположению с использованием фасетизации.

Совет

Для оптимизации производительности и хранения отключите фасетизацию для полей, которые никогда не должны использоваться в качестве аспектов. В частности, строковые поля для уникальных значений, таких как идентификатор или название продукта, должны иметь значение "facetable": false, чтобы предотвратить их случайное (и неэффективное) использование в фасетной навигации. Это особенно верно для REST API, который включает фильтры и аспекты по умолчанию.

Запрос и ответ аспектов

Аспекты указываются в запросе, а структура фасетной навигации возвращается в верхней части ответа. Структура запроса и ответа довольно простая. На самом деле, реальная работа за фасетной навигацией лежит на слое презентации, посвященном более позднему разделу.

Следующий пример REST — это неквалифицированный запрос (), который область для всего индекса ("search": "*"см. пример встроенных отелей). Аспекты обычно являются списком полей, но этот запрос отображает только один для более читаемого ответа ниже.

POST https://{{service_name}}.search.windows.net/indexes/hotels/docs/search?api-version={{api_version}}
{
    "search": "*",
    "queryType": "simple",
    "select": "",
    "searchFields": "",
    "filter": "",
    "facets": [ "Category"], 
    "orderby": "",
    "count": true
}

Полезно инициализировать страницу поиска с открытым запросом, чтобы полностью заполнить фасетную структуру навигации. Как только вы передаете термины запроса в запросе, фасетная структура навигации будет область только совпадения в результатах, а не весь индекс.

Ответ для приведенного выше примера включает в себя фасетную структуру навигации в верхней части. Структура состоит из значений "Категория" и количества отелей для каждого из них. За ним следует остальные результаты поиска, обрезанные здесь для краткости. Этот пример хорошо работает по нескольким причинам. Количество аспектов для этого поля попадает под ограничение (по умолчанию — 10), поэтому все из них отображаются, и каждый отель в индексе 50 отелей представлен ровно в одной из этих категорий.

{
    "@odata.context": "https://demo-search-svc.search.windows.net/indexes('hotels')/$metadata#docs(*)",
    "@odata.count": 50,
    "@search.facets": {
        "Category": [
            {
                "count": 13,
                "value": "Budget"
            },
            {
                "count": 12,
                "value": "Resort and Spa"
            },
            {
                "count": 9,
                "value": "Luxury"
            },
            {
                "count": 7,
                "value": "Boutique"
            },
            {
                "count": 5,
                "value": "Suite"
            },
            {
                "count": 4,
                "value": "Extended-Stay"
            }
        ]
    },
    "value": [
        {
            "@search.score": 1.0,
            "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.",
            "Category": "Boutique",
            "Tags": [
                "pool",
                "air conditioning",
                "concierge"
            ],
            "ParkingIncluded": false,
        }
    ]
}

Синтаксис аспектов

Параметр запроса аспектов имеет список полей с разделителями-запятыми и в зависимости от типа данных может быть параметризован для задания счетчиков, заказов сортировки и диапазонов: count:<integer>, sort:<>и interval:<integer>.values:<list> Дополнительные сведения о параметрах аспектов см. в разделе "Параметры запроса" в REST API.

POST https://{{service_name}}.search.windows.net/indexes/hotels/docs/search?api-version={{api_version}}
{
    "search": "*",
    "facets": [ "Category", "Tags,count:5", "Rating,values:1|2|3|4|5"],
    "count": true
}

Для каждого фасетного дерева навигации по умолчанию используется ограничение верхнего десяти аспектов. Это упрощает структуру переходов, позволяя управлять списком значений. Можно переопределить значение по умолчанию, назначив значение счетчику. Например, "Tags,count:5" уменьшите количество тегов в разделе "Теги" до пяти лучших.

Только для числовых и значений DateTime можно явно задать значения в поле аспектов (например, facet=Rating,values:1|2|3|4|5), чтобы разделить результаты на непрерывные диапазоны (либо диапазоны на основе числовых значений или периодов времени). Кроме того, можно добавить "interval", как и в facet=Rating,interval:1.

Каждый диапазон создается с использованием отправной точки 0 и конечной точки, равной значению из списка. Затем из всего диапазона удаляется предыдущий диапазон, что позволяет создавать дискретные интервалы.

Несоответствия в количествах аспектов

В определенных обстоятельствах можно найти, что счетчики аспектов не являются полностью точными из-за архитектуры сегментирования. Каждый индекс поиска распространяется по нескольким сегментам, и каждый сегмент сообщает верхние N аспекты по количеству документов, которые затем объединяются в один результат. Так как это просто верхние N-аспекты для каждого сегмента, можно пропустить или недосчитывать соответствующие документы в ответе аспектов.

Чтобы гарантировать точность, можно искусственно раздуть число:<число> до большого числа, чтобы принудительно создать полный отчет от каждого сегмента. Можно указать "count": "0" неограниченные аспекты. Кроме того, можно задать значение count, которое больше или равно количеству уникальных значений фасетного поля. Например, если вы сталкиваетесь с полем "size", которое содержит пять уникальных значений, можно задать "count:5" , чтобы убедиться, что все совпадения представлены в ответе аспектов.

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

Уровень представления данных

В коде приложения шаблон состоит в использовании параметров запроса фасета для возврата структуры фасетной навигации вместе с результатами фасета, а также выражения $filter, Выражение фильтра обрабатывает событие щелчка и дополнительно сужает результат поиска на основе выделения аспектов.

Сочетание аспектов и фильтров

Следующий фрагмент кода из JobsSearch.cs файла в демонстрации NYCJobs добавляет выбранный название бизнеса в фильтр, если выбрать значение из аспекта "Название бизнеса".

if (businessTitleFacet != "")
  filter = "business_title eq '" + businessTitleFacet + "'";

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

if (!String.IsNullOrEmpty(categoryFacet))
    filter = $"category eq '{categoryFacet}'";

HTML для фасетной навигации

В следующем примере, взятом из index.cshtml файла примера приложения NYCJobs, показана статическую структуру HTML для отображения фасетной навигации на странице результатов поиска. Список фасетов создается или повторно создается динамически при отправке условия запроса, выборе или очистке фасета.

<div class="widget sidebar-widget jobs-filter-widget">
  <h5 class="widget-title">Filter Results</h5>
    <p id="filterReset"></p>
    <div class="widget-content">

      <h6 id="businessTitleFacetTitle">Business Title</h6>
      <ul class="filter-list" id="business_title_facets">
      </ul>

      <h6>Location</h6>
      <ul class="filter-list" id="posting_type_facets">
      </ul>

      <h6>Posting Type</h6>
      <ul class="filter-list" id="posting_type_facets"></ul>

      <h6>Minimum Salary</h6>
      <ul class="filter-list" id="salary_range_facets">
      </ul>

  </div>
</div>

Динамическое построение HTML

Следующий фрагмент кода из демонстрации index.cshtml (также из демонстрационной версии NYCJobs) динамически создает HTML-код для отображения первого аспекта бизнес-заголовка. Аналогичные функции динамически создают HTML для других фасетов. Каждый фасет содержит метку и счетчик, отображающий число элементов, найденных для соответствующего результата фасета.

function UpdateBusinessTitleFacets(data) {
  var facetResultsHTML = '';
  for (var i = 0; i < data.length; i++) {
    facetResultsHTML += '<li><a href="javascript:void(0)" onclick="ChooseBusinessTitleFacet(\'' + data[i].Value + '\');">' + data[i].Value + ' (' + data[i].Count + ')</span></a></li>';
  }

  $("#business_title_facets").html(facetResultsHTML);
}

Советы для работы с аспектами

Этот раздел представляет собой коллекцию советов и обходных решений, которые могут оказаться полезными.

Асинхронное сохранение структуры фасетной навигации отфильтрованных результатов

Одной из проблем фасетной навигации в поиске ИИ Azure является то, что аспекты существуют только для текущих результатов. На практике обычно сохраняется статический набор фасетов, чтобы пользователь мог перемещаться в обратном направлении, проходя по поисковому содержимому для поиска альтернативных путей.

Хотя это распространенный вариант использования, это не то, что фасетная структура навигации в настоящее время предоставляет вне поля. Разработчики, которым требуются статические аспекты, обычно обходят ограничение, выполняя два отфильтрованных запроса: один в пределах результатов, а другой для создания статического списка аспектов для навигации.

Очистить аспекты

При разработке страницы результатов поиска не забудьте включить механизм очистки фасетов. Устанавливая флажки, можно легко понять, как очистить фильтры. Для других структур может понадобиться шаблон навигации или другой нестандартный элемент. В примере C# для сброса страницы можно отправить пустой поиск. В отличие от этого, пример приложения NYCJobs предоставляет возможность [X] щелчка после выбранного аспекта, чтобы очистить аспект, который является более сильной визуальной очередью для пользователя.

Обрезание результатов аспекта с помощью дополнительных фильтров

Фасеты позволяют отобразить в результатах поиска документы, соответствующие определенному фасету. В следующем примере результаты поиска фразы cloud computing (облачные вычисления) включают 254 элемента с типом содержимого internal specification (внутренняя спецификация). Элементы не обязательно являются взаимоисключающими. Если элемент соответствует критериям обоих фильтров, он учитывается в каждом из них. Это дублирование возможно при фасетном поиске в полях Collection(Edm.String), которые часто используются для обозначения документов тегами.

Search term: "cloud computing"
Content type
   Internal specification (254)
   Video (10)

Как правило, если вам кажется, что при использовании фасета вы получаете слишком много результатов, мы рекомендуем добавить фильтры, чтобы пользователи могли сузить область поиска.

Интерфейс поиска только для аспектов

Если приложение использует фасетную навигацию исключительно (то есть нет поля поиска), можно пометить поле как searchable=false, filterable=truefacetable=true чтобы создать более компактный индекс. Индекс не будет включать инвертированные индексы и не будет анализа текста или маркеризации. Фильтры выполняются на точных совпадениях на уровне символов.

Проверка входных данных во время запроса

При динамическом создании списка фасетов на основе данных, введенных ненадежными пользователями, следует проверить допустимость имен полей фасетов или исключить имена при создании URL-адресов с помощью Uri.EscapeDataString() в .NET или его эквивалента в другой платформе.

Демонстрации и примеры

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

Добавление поиска в веб-приложения (React)

Руководства и примеры в C#, Python и JavaScript включают фасетную навигацию, а также фильтры, предложения и автозавершение. В этих примерах используется React для слоя презентации.

Пример кода и демонстрация NYCJobs (Ajax)

Пример NYCJobs — это приложение ASP.NET MVC, использующее Ajax на уровне презентации. Он доступен в виде демонстрационного приложения и в качестве исходного кода в репозитории Azure-Samples на GitHub.