Гайд · TNWS AI

Как фильтровать векторный поиск Azure AI Search по tenant без утечки данных

6 мин—

OData filter и vectorFilterMode preFilter для tenant, негативный тест изоляции и сравнение с postFilter.

Задача и применимость

Актуальный русскоязычный запрос: «как фильтровать vector search Azure AI Search по tenant preFilter». Официальная документация Microsoft проверена 19 сентября 2026 года. REST API 2026-04-01; tenantId — отдельное строковое filterable поле в схеме, служебные права задаются на сервере; для старых индексов доступность preFilter проверяется отдельно.

Задача здесь — выполнить и проверить конкретную операцию в Azure AI Search. Образцовые значения вроде ru-kb, p1 и policy-2026-01 принадлежат тестовому корпусу, а не обязательным именам в вашем сервисе. У вас должны быть полномочия на указанную операцию; для теста можно использовать административный ключ на доверенном сервере, но в пользовательском браузере нужен защищённый серверный посредник. Сначала снимите baseline по контрольному запросу и набору документов, затем меняйте только одну настройку за раз.

Что подтверждает документация

Фильтры применяются к filterable не-векторным полям. preFilter применяется во время обхода HNSW по shard и обычно максимизирует recall, тогда как postFilter фильтрует кандидатов после локального поиска и может давать false negatives при небольшом k. strictPostFilter помечен preview; для разделения пользователей не опирайтесь на него.

Это не обещание одинакового ранжирования для любого русского корпуса. Результат embedding зависит от deployment, подготовки текста, chunking, обновлений модели и состава индекса. Доступность предварительных функций и ограничения сверяйте с документацией и настройками собственного сервиса; цена и длительность операций здесь намеренно не указаны, потому что они не следуют из поискового ответа. Отделяйте успешный HTTP-запрос от оценки полезности найденного фрагмента.

Пошаговое выполнение

  1. Добавьте tenantId как filterable Edm.String при проектировании индекса и заполните его на каждом документе.
  2. Сервер извлекает tenant из подтверждённой сессии, а не из свободного параметра клиента.
  3. В Search - POST задайте filter по tenantId и vectorFilterMode=preFilter.
  4. Проверьте положительный пример, затем отправьте тот же запрос от второго tenant: чужого id не должно быть в результатах.
  5. При пустой выдаче отличите отсутствие разрешённого документа от потери recall и ошибок OData экранирования.

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

Готовый REST-шаблон

POST /indexes/ru-kb/docs/search?api-version=2026-04-01
{"filter":"tenantId eq 'company-a'","vectorFilterMode":"preFilter",
 "select":"id,tenantId,title,content","vectorQueries":[
  {"kind":"text","text":"политика возврата","fields":"contentVector","k":5}]}
# tenantId заполняет сервер после авторизации; для kind:text
# нужен vectorizer целевого поля.

Подставьте своё имя сервиса в https://<service>.search.windows.net, передайте Content-Type: application/json и авторизацию выбранным поддерживаемым способом. Демонстрационный массив из трёх чисел или строка <embedding массива> — только описание формы; они не являются реальным embedding для поля с 1536 измерениями. Перед отправкой проверьте JSON, размерность и отсутствие ключа в клиентском коде.

Входные данные и ожидаемый результат

Вход: документ a-1 с tenantId=company-a о возврате и документ b-1 с tenantId=company-b с почти идентичным содержанием. Клиент company-a задаёт вопрос о возврате. Ожидаются только записи company-a; b-1 не появляется ни в IDs, ни в цитатах, даже если его embedding математически ближе.

Ожидаемый ID — проверяемая гипотеза на подготовленном тестовом наборе. При другом корпусе и другом embedding нельзя честно обещать конкретный score и порядок результатов. Сохраняйте как входное содержимое, так и текст возвращённого фрагмента: найденный ID без исходного подтверждения не годится для публикации ответа от имени компании.

Разбор и границы применения

Изоляция — задача авторизации, а не настройка качества выдачи. Фильтр, который пользователь может удалить или заменить собственным tenant, бесполезен; сервер конструирует выражение из проверенных claims и журналирует применённый tenant. Если на части документов нет tenantId, они не должны случайно стать публичными: проведите аудит пропусков и negative tests. Упоминание preFilter не означает, что Search самостоятельно проверяет право доступа к каждому файлу или контейнеру. Разделение по tenant — только один слой, а доступ к источникам и выдаваемым полям должен оцениваться отдельно. Для старых индексов Microsoft документирует иную историческую настройку по умолчанию и возможную потребность создать индекс заново, чтобы использовать preFilter. Явно передавайте vectorFilterMode в тесте и проверяйте поддержку; не предполагаете её по возрасту поискового сервиса. При сравнении режимов заморозьте корпус, вектор, фильтр и k, затем сравните ids, Recall@k, p95 latency и число пустых выдач. postFilter может показаться быстрее, но для редкого tenant потеря документов недопустима. Сочетание hybrid запроса и фильтра также требует отдельной проверки всего итогового набора, включая текстовые совпадения.

Практический контроль именно этой задачи

План изоляционного теста должен быть злонамеренным. Создайте для company-a и company-b документы с буквально одинаковым title и почти одинаковым content, но разными уникальными id. Выполните запрос пользователя A с корректной сессией, затем вручную измените в клиентском запросе желаемый tenant на B: сервер обязан проигнорировать подмену и использовать проверенный claim A. Протестируйте документы с отсутствующим tenantId, пустой строкой и специальными символами в разрешённом значении. При формировании OData выражения экранируйте значение на сервере, не собирайте фильтр простой конкатенацией произвольного пользовательского ввода. Проверяйте отсутствие b-1 не только в основной выдаче, но и в captions, цитатах генератора и диагностических логах, доступных пользователю. При переходе от postFilter к preFilter оценивайте нагрузку на высокоселективных фильтрах: более высокий recall может стоить дополнительной задержки. Это осознанный компромисс, не повод жертвовать разграничением прав.

Сопоставление вариантов

РежимКогда применяет фильтрРиск для редкого tenant
preFilterВо время обхода HNSW на shardПредпочтителен для recall; может стоить latency
postFilterПосле локального vector поискаВозможны ложные пропуски при малом k
strictPostFilter (preview)После глобального top-kНаибольший риск ложных пропусков

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

Критерии проверки

  • Каждый возвращённый tenantId равен tenant текущей сессии.
  • Отрицательный тест не раскрывает b-1.
  • preFilter поддерживается индексом и возвращает контрольный a-1.
  • Тесты повторены на малом k и селективном tenant.

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

Типичные ошибки и что не делать

  • Доверять tenantId, присланному браузером.
  • Фильтровать само vector поле.
  • Считать postFilter эквивалентным preFilter по recall.
  • Объявлять preview strictPostFilter настройкой по умолчанию.

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

FAQ

Где должен быть tenantId?

В отдельном filterable поле индекса.

Что предпочесть при редком tenant?

Проверяйте preFilter: он обычно сохраняет больше релевантных кандидатов.

Заменяет ли filter авторизацию?

Нет, значение фильтра и доступ к источникам контролирует сервер.

Официальные источники

Частые вопросы

Где должен быть tenantId?

В отдельном filterable поле индекса.

Что предпочесть при редком tenant?

Проверяйте preFilter: он обычно сохраняет больше релевантных кандидатов.

Заменяет ли filter авторизацию?

Нет, значение фильтра и доступ к источникам контролирует сервер.

Читайте также

Комментарии

Пока тихо. Скажите первое слово