Гайд · TNWS AI

Как ограничить поиск документами одного клиента в Haystack

Haystack 3.1Python#Haystack#RAG#Python
5 мин—

Фильтр meta.tenant в retriever, тест на утечку чужого документа и границы серверной авторизации.

Применимость и задача

Поисковый запрос: «как фильтровать RAG документы по tenant Haystack». Материал сверён с официальной документацией Haystack 3.1 19 сентября 2026 года. Примеры рассчитаны на Python и пакет haystack-ai; для векторного примера дополнительно нужен пакет sentence-transformers-haystack. Демонстрационное InMemoryDocumentStore не рекомендовано разработчиком для production. Интерфейс командной строки ниже — Python API, а не названия пунктов графического меню.

Фильтры Haystack строятся из сравнений field/operator/value и логических условий AND, OR, NOT; поддержка операторов зависит от Document Store. Для InMemoryBM25Retriever аргумент filters доступен при вызове run. Поле метаданных обозначается путём вида meta.tenant. Этот фильтр ограничивает выдачу, но не заменяет проверку прав в сервисе.

Нумерованный порядок работы

  1. Пометьте документы серверным tenant ID при индексации и проверьте, что поле задано у каждой записи.
  2. Получите tenant ID из проверенной серверной сессии, а не из вопроса пользователя или текста промпта.
  3. Составьте фильтр по meta.tenant и передайте его прямо в вызов retriever.
  4. Создайте для двух клиентов документы с одинаковым содержимым и разными ID, затем повторите запрос в обеих сессиях.
  5. Проверяйте фактические ID returned documents перед передачей их генератору и перед отображением цитат.

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

from haystack import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever

store = InMemoryDocumentStore()
store.write_documents([
    Document(id="A-1", content="Возврат 14 дней", meta={"tenant": "A"}),
    Document(id="B-1", content="Возврат 30 дней", meta={"tenant": "B"}),
])
tenant_from_server_session = "A"
filter_a = {"field": "meta.tenant", "operator": "==",
            "value": tenant_from_server_session}
docs = InMemoryBM25Retriever(store).run(
    query="возврат", filters=filter_a, top_k=5)["documents"]
assert all(d.meta["tenant"] == "A" for d in docs)
print([d.id for d in docs])

Пример предназначен для контролируемого теста на своих данных. Установите зависимости в отдельном окружении и зафиксируйте версии; при использовании локальной embedding-модели отдельно проверьте её доступность и разрешённость для вашего проекта. Код был проверен на синтаксис Python, но его вызовы Haystack в данном материале не выдаются за выполненный runtime-тест.

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

Вход: у клиента A правило 14 дней, у клиента B текст о возврате 30 дней. При запросе «возврат» в сессии A ожидается только A-1, без B-1 даже если у B выше поисковый score. После переключения проверенной серверной сессии на B ожидается только B-1. Если документ без tenant попал в выдачу, тест доступа провален.

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

Разбор ограничений и ошибок

Фильтрация и авторизация — разные уровни защиты. Даже идеально записанный словарь фильтра не спасёт, если клиент может сам выбрать tenant ID и сервер поверит параметру. Идентификатор арендатора следует извлекать из проверенного токена пользователя, сверять с правами и использовать как обязательное условие запроса. Запретите запрос без tenant, вместо того чтобы «временно» возвращать результаты всех. При сложном условии combine AND с уровнем доступа, статусом публикации и временем действия документа; семантика фильтра внешнего хранилища может отличаться, поэтому не переносите тест с InMemory на другую интеграцию без повторной проверки. Сохраняйте безопасность при предварительном кэше и повторном ранжировании: набор кандидатов обязан быть уже отфильтрован, а кэш-ключ должен учитывать tenant. Логи с чужим содержимым тоже утечка. Пользовательская инструкция «игнорируй фильтр» не должна влиять на фильтр, созданный сервером.

Проверочный сценарий на реальном корпусе

Негативный тест с двумя одинаковыми текстами сильнее обычного запроса, потому что он исключает объяснение «чужой документ просто оказался нерелевантным». Добавьте запрос из B, где строка буквально совпадает с вопросом A; B-1 всё равно не должен появиться у A. Проверьте пустую выдачу для арендатора C: правильный ответ — ничего не найдено, а не fallback на глобальный корпус. Отдельно проверьте цитаты и скачивание исходника: UI не должен дать открыть документ по прямому ID из другого tenant. Для расширенных фильтров фиксируйте тесты на in, вложенный AND и типы полей, поскольку интеграции различаются по поддержке. Сервисная проверка ID источника после retrieval добавляет важную границу, но не оправдывает отправку чужого текста в LLM до проверки.

Критерии приёмки

  • Выдача A содержит только A-1.
  • Выдача B содержит только B-1.
  • Неавторизованный tenant отклонён до вызова retriever.

Не принимайте демонстрационный вывод за статистику качества. Для рабочего приложения повторите эти проверки на обезличенном наборе документов, измерьте результат после изменения версии корпуса и запретите доступ к источникам вне полномочий пользователя. Корректный ответ HTTP вашего сервиса не означает корректный ответ RAG-системы; последнее устанавливается сопоставлением факта с разрешённым источником.

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

  • Не брать tenant ID из пользовательского промпта.
  • Не использовать глобальный fallback при пустом результате.
  • Не считать metadata filter единственной авторизацией.

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

FAQ

Как указать поле?

Через путь meta.tenant в фильтре.

Все ли stores поддерживают одинаковые операторы?

Нет, смотрите документацию конкретной интеграции.

Может ли prompt снять фильтр?

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

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

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

Как указать поле?

Через путь meta.tenant в фильтре.

Все ли stores поддерживают одинаковые операторы?

Нет, смотрите документацию конкретной интеграции.

Может ли prompt снять фильтр?

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

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

Комментарии

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