Гайд · TNWS AI

Как фильтровать векторный поиск Vespa по tenant без утечки в RAG

4 мин—

YQL-фильтр вместе с nearestNeighbor, серверный tenant, pre-filtering, отрицательный ACL-тест и проверка полного top-k.

Какую задачу решаем и где применимо

Русскоязычный поисковый запрос: «как фильтровать vector search Vespa по tenant». Материал проверен по официальной документации Vespa, актуальной на 19 сентября 2026 года. Для Vespa с полем tenant_id типа int с attribute и fast-search и документным вектором embedding. Пример использует целочисленный tenant для ясного условия tenant_id = 42; в реальной системе значение должно поступать от доверенной backend-сессии, а не из аргумента пользователя.

В Vespa nearestNeighbor сочетается с фильтрами языка YQL. По умолчанию поиск с фильтрами использует pre-filtering при ranking.matching.postFilterThreshold=1.0; отдельные параметры профиля могут изменить стратегию, поэтому не предполагайте её по одной форме YQL. Отфильтровывать доступ только после передачи результатов в LLM небезопасно: содержимое уже могло попасть в prompt или логи.

До начала эксперимента подготовьте маленький набор документов с известными source_id и один отрицательный пример. Сохраните версию schema, embedding-модели, набор входных вопросов и ожидаемые IDs: так результат можно воспроизвести после обновления системы, а не оценивать по одному удачному ответу. Все значения для маленького tensor в шаблоне — иллюстрация контрактов, не показатели качества модели.

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

  1. Добавьте в схему tenant_id как фильтруемый attribute и проверьте его наличие в каждом документе.
  2. На сервере сопоставьте аутентифицированный принципал разрешённому tenant ID.
  3. Соедините nearestNeighbor и tenant_id = 42 через and внутри where YQL.
  4. В тестовом профиле зафиксируйте pre-filtering и сравните его с контролируемым post-filtering, сохранив latency и IDs.
  5. Поместите наиболее близкий чужой документ в тест; удостоверьтесь, что он ни разу не попадает в контекст.

Эти шаги предполагают доступ к собственному тестовому приложению Vespa. Для Vespa Cloud и самостоятельной установки способы деплоя отличаются; не угадывайте имена кнопок консоли. Используйте показанный интерфейс schema, YQL или CLI, сверяя конкретное окружение и permissions с документацией. При деплое сохраняйте предыдущее определение, чтобы сопоставить результаты и откатить неудачное изменение без смешения двух причин.

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

vespa query   'yql=select source_id,text,tenant_id from article where {totalTargetHits:20}nearestNeighbor(embedding,q) and tenant_id = 42'   'input.query(q)=[0.10,0.20,0.30]'   'ranking=semantic' 'hits=5'   'ranking.matching.postFilterThreshold=1.0'

Шаблон адаптируйте к своей размерности и именам документов. Если он содержит ручной трёхмерный вектор, он полезен для smoke-теста схемы и синтаксиса, но не заменяет настоящий embedding вопроса. Секреты провайдера, токены Vespa и данные закрытых клиентов не вставляйте ни в публичный prompt, ни в статью, ни в логи запроса.

Конкретный вход и ожидаемый выход

Вход: семь текстов tenant 42 и самый близкий вектор чужого tenant 17 с закрытым договором. Ожидается, что top-5 содержит только tenant_id=42, а закрытый документ не появляется ни в логах генератора, ни в запросе LLM; если у tenant менее пяти документов, короткий ответ допустим.

Обязательный фильтр доступа — не удобная SEO-настройка, а часть границы безопасности. Если tenant_id отсутствует, отклоняйте запрос или документ согласно политике, но не расширяйте выдачу на весь корпус. Инженерный нюанс: слишком редкий tenant меняет оптимальную стратегию поиска, и Vespa может перейти от ANN к exact в зависимости от порогов. Замерьте это на распределении размеров арендаторов, а не только на усреднённой нагрузке. На одном и том же gold set отдельно проверяйте отсутствие чужих IDs, полноту своих результатов и p95. Запросы без identity и с поддельным tenant ID должны проваливаться до обращения к Vespa. Prompt не исправит ошибку авторизации: найденный текст следует считать недоверенными данными.

Проверка результата

  • Tenant берётся только из проверенной backend identity.
  • Чужой ближайший документ не появляется даже при пустом результате своего tenant.
  • Отсутствующий tenant не переводит запрос в режим поиска по всему индексу.
  • Стратегия фильтра и p95 измерены на малых и больших tenant.

Успех означает прохождение всех перечисленных пунктов на положительных и отрицательных кейсах. Снимите фактические IDs, число hits и latency до и после изменения; если есть LLM-ответ, проверьте цитаты вручную. Пустой результат из-за ошибки типов, времени ожидания, прав доступа или отсутствующей записи нельзя выдавать за низкую релевантность. Удобно хранить отдельный отчёт по каждым case_id, иначе невозможно обнаружить регрессию на редких формулировках.

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

  • Брать tenant_id из пользовательского тела запроса.
  • Фильтровать доступ после передачи кандидатов в LLM.
  • Повторять пустой поиск без tenant-фильтра.

Не исправляйте нерелевантную выдачу произвольным повышением top-k: сперва отделите ошибку модели, схемы, фильтра и ранжирования. Не отключайте проверку tenant ради красивого теста. Не делайте вывод о производительности на крошечном демонстрационном корпусе; проверяйте рабочий объём, конкурентную нагрузку и p95, а также расход памяти/скорость обновлений там, где они существенно меняются.

FAQ

Где задаётся фильтр?

В YQL where совместно с nearestNeighbor.

Можно ли полагаться только на prompt?

Нет, prompt не заменяет серверную авторизацию.

Что делать с пустым результатом?

Возвращать отсутствие разрешённых данных, не снимать ACL.

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

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

Где задаётся фильтр?

В YQL `where` совместно с nearestNeighbor.

Можно ли полагаться только на prompt?

Нет, prompt не заменяет серверную авторизацию.

Что делать с пустым результатом?

Возвращать отсутствие разрешённых данных, не снимать ACL.

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

Комментарии

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