Гайд · TNWS AI
Как фильтровать векторный поиск Azure AI Search по tenant без утечки данных
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-запрос от оценки полезности найденного фрагмента.
Пошаговое выполнение
- Добавьте tenantId как filterable Edm.String при проектировании индекса и заполните его на каждом документе.
- Сервер извлекает tenant из подтверждённой сессии, а не из свободного параметра клиента.
- В Search - POST задайте filter по tenantId и vectorFilterMode=preFilter.
- Проверьте положительный пример, затем отправьте тот же запрос от второго tenant: чужого id не должно быть в результатах.
- При пустой выдаче отличите отсутствие разрешённого документа от потери 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 авторизацию?
Нет, значение фильтра и доступ к источникам контролирует сервер.
Читайте также
Как искать текстом через vectorizer Azure AI Search без передачи массива чисел
Настройка vectorizer и REST kind:text для русских вопросов: связка profile, модели и проверка результатов.
Как измерить recall HNSW в Azure AI Search через exhaustive KNN
Точный baseline exhaustive:true, Recall@k, стабильный набор запросов и диагностика trade-off качества и задержки.
Как настроить semantic ranker Azure AI Search для RAG с k=50
Вторичное семантическое ранжирование, конфигурация текстовых полей, k=50 и проверка citations без генеративных обещаний.
Комментарии
Пока тихо. Скажите первое слово