Мультиарендный чат по документам на Amazon Bedrock Managed Knowledge Base строится вокруг двух потоков: загрузки файлов и retrieval по ним. Managed Knowledge Base сокращает объем самостоятельно управляемой retrieval-инфраструктуры, однако backend, аутентификация, авторизация, метаданные, статусы обработки и пользовательский интерфейс остаются ответственностью приложения.
Граница безопасности проходит по серверному контексту пользователя. Backend получает доверенный user_id из проверенной сессии или токена, связывает его с метаданными документов и добавляет фильтр при каждом retrieval-запросе. Значение user_id из тела запроса, имени файла или состояния интерфейса нельзя использовать как основание для доступа.
После загрузки файл сначала попадает в объектное хранилище и прикладную модель документов. Затем запускается асинхронная обработка источника и поискового представления. Поэтому успешный прием файла и состояние searchable разделяют несколько этапов: документ можно считать готовым для вопросов после подтверждения завершения соответствующей операции, а не сразу после ответа upload-endpoint.
Как работает мультиарендный чат по документам на Amazon Bedrock
Главная идея: один чат, изолированные наборы документов
Мультиарендность не требует отдельного чата или отдельной прикладной системы для каждого пользователя. Один интерфейс может обслуживать множество арендаторов, если каждый документ получает однозначную принадлежность, а каждый запрос проходит с ограниченной областью поиска.
Представим двух пользователей: user-101 загрузил договоры, а user-202 загрузил внутренние инструкции. Оба задают вопрос «Какие сроки указаны в документах?». Поисковый запрос должен использовать один и тот же текст, но разные серверные фильтры. Для первого пользователя допустимы фрагменты только с метаданными user_id = user-101, для второго, только с user_id = user-202.
Инвариант системы выглядит так: результат retrieval всегда подчинен доверенному контексту пользователя. LLM получает уже ограниченный набор фрагментов и не должна самостоятельно решать, кому принадлежат документы.
- Аутентификация устанавливает, кто отправил запрос.
- Backend сопоставляет пользователя с внутренним
user_idилиtenant_id. - Документ связывается с этим идентификатором до индексирования.
- Retrieval применяет metadata filter для каждого вопроса и follow-up.
- Агент формирует ответ только по разрешенному контексту.
Что Managed Knowledge Base берет на себя, а что не берет
Managed Knowledge Base закрывает управляемую часть работы с источниками и retrieval-представлением документов. Сервис помогает убрать из прикладной системы часть задач, связанных с подготовкой базы знаний и поиском по обработанному содержимому. Точная схема источников, индексации, фильтров и поддерживаемых режимов зависит от выбранной конфигурации и актуальной документации AWS.
Приложение продолжает отвечать за полный жизненный цикл пользовательского действия:
| Зона ответственности | Что контролирует приложение |
|---|---|
| Пользователь | Аутентификация, сессия, права доступа и связь с внутренней учетной записью |
| Файл | Размер, тип, имя, целостность, безопасное хранение и повторная отправка |
| Принадлежность | Связь document_id с user_id или tenant_id |
| Ingestion | Запуск операции, отслеживание состояния, таймауты и ограниченные повторы |
| Retrieval | Построение серверного фильтра, передача запроса и обработка пустого результата |
| UX | Отображение принятия, обработки, готовности и ошибки |
| Аудит | Фиксация загрузок, вопросов, изменений прав и операций удаления |
Общая настройка Managed Knowledge Base и варианты агентного поиска разобраны в практическом руководстве по Amazon Bedrock Managed Knowledge Base. Для мультиарендного чата к этой схеме добавляется собственный слой контроля доступа.
Архитектура RAG на Amazon Bedrock: компоненты и границы доверия
Целевую систему удобно разделить на семь логических компонентов: клиентское приложение, слой аутентификации, backend/API, объектное хранилище, прикладную базу документов, Amazon Bedrock Managed Knowledge Base и агентный или генеративный слой.
| Компонент | Задача | Доверенный контекст |
|---|---|---|
| Клиент | Выбор файла, отправка вопроса, отображение статуса и ответа | Недоверенный |
| Аутентификация | Проверка токена или сессии | Источник identity |
| Backend | Авторизация, фильтрация, управление операциями и аудит | Главная прикладная граница |
| Object storage | Хранение исходного файла | Доступ через серверные политики |
| База документов | Связи документа, владельца, статуса и операции | Источник прикладной правды |
| Managed Knowledge Base | Подготовка поискового представления и retrieval | Получает ограниченный запрос |
| Агент или LLM | Планирование взаимодействия и формирование ответа | Работает с разрешенным контекстом |
Клиент может передать имя файла, выбранный режим чата или идентификатор для отображения в интерфейсе. Такие поля помогают UI, но не определяют право доступа. Решение принимает backend после проверки identity.
Поток загрузки: от файла до источника для retrieval
Загрузка проходит последовательно:
- Клиент отправляет файл и прикладные параметры.
- Backend проверяет сессию или токен.
- Сервер получает канонический
user_idиз проверенного контекста. - Приложение проверяет тип, размер, имя и состояние файла.
- Объект сохраняется в хранилище, а запись документа получает владельца и идентификатор операции.
- Приложение запускает обработку источника или ставит задачу в очередь.
- Система отслеживает состояние до момента, когда документ можно использовать в retrieval.
Успешная загрузка означает, что приложение приняло объект и создало связанную запись. Она не подтверждает, что текст уже разбит на фрагменты, проиндексирован и виден поисковому контуру.
Поток retrieval: от вопроса до ответа агента
Запрос пользователя проходит другой путь:
- Клиент передает текст вопроса и идентификатор диалога.
- Backend проверяет identity и получает серверный
user_id. - Сервер выбирает область документов, доступную этому пользователю или его организации.
- Retrieval выполняется с metadata filter, построенным backend.
- Система получает релевантные фрагменты и проверяет, что они относятся к разрешенной области.
- Агент или генеративный слой использует эти фрагменты для ответа.
- В журнал попадают идентификатор пользователя, операции и технический результат без раскрытия лишних данных клиенту.
Для сложных вопросов, которые требуют нескольких поисковых шагов, полезно отделять выбор retrieval-режима от контроля доступа. Агентный поиск может планировать подзадачи, но каждый подзапрос должен сохранять тот же серверный фильтр. Особенности многошагового поиска разобраны в статье про AgenticRetrieveStream и сложные запросы к неструктурированным данным.
Почему агент не заменяет авторизацию
Системная инструкция не превращает LLM в механизм контроля доступа. Модель может ошибиться, неверно интерпретировать вопрос или попытаться вызвать инструмент с неожиданными параметрами. Prompt не блокирует прямой вызов retrieval endpoint и не исправляет ошибку в метаданных.
Правильное разделение выглядит так:
- backend определяет разрешения;
- retrieval получает ограниченный запрос;
- агент выбирает способ работы с уже разрешенным контекстом;
- генеративный слой не получает чужие фрагменты для последующей фильтрации.
Архитектура самописного агентного слоя, включая оркестрацию, инструменты и обработку ошибок, разобрана в материале о создании AI-агента с нуля. Для document chat к агенту добавляется обязательное ограничение области поиска.
Как организовать загрузку документов от разных пользователей
Сначала аутентификация, потом прием файла
Проверка пользователя должна происходить до создания записи документа и выдачи полномочий на загрузку. Backend проверяет подпись токена или состояние сессии, срок действия, принадлежность учетной записи и право добавлять документы.
После этого сервер сам получает user_id. Источником может служить идентификатор subject в проверенном токене, внутренняя таблица пользователей или результат серверного сопоставления внешней учетной записи с локальной моделью.
Минимальный порядок действий можно выразить псевдокодом:
def upload_document(request):
principal = authenticate(request)
if principal is None:
return error('unauthorized')
user_id = resolve_internal_user_id(principal)
validate_file(request.file)
document_id = create_document(user_id=user_id, status='accepted')
store_object(document_id, request.file)
enqueue_ingestion(document_id)
return document_status(document_id)
Поле request.user_id намеренно отсутствует. Клиент не должен выбирать владельца документа. Если пользовательская модель содержит организации, сервер отдельно проверяет membership и право загрузки в конкретный tenant_id.
Как связать объект, метаданные и владельца
Для каждого документа нужна стабильная прикладная запись. Она связывает физический объект, владельца и поисковое представление. Минимальный набор полей выглядит так:
| Поле | Назначение |
|---|---|
document_id | Уникальная ссылка на документ внутри приложения |
user_id | Владелец или субъект, для которого разрешен поиск |
tenant_id | Организация или рабочее пространство, если доступ групповой |
object_key | Путь объекта в хранилище |
original_name | Имя файла для UI и аудита |
content_type | Тип содержимого после серверной проверки |
ingestion_status | Текущее состояние обработки |
operation_id | Связь с конкретной операцией загрузки или обновления |
created_at | Время создания записи |
Поле, используемое для фильтра retrieval, должно иметь одинаковое имя, тип и смысл во всех документах. Если часть объектов получает строковый user_id, а часть, числовой идентификатор, фильтр может давать пустые или непредсказуемые результаты.
При корпоративной модели прав одного user_id может быть мало. Например, документ доступен группе из пяти сотрудников, но запрещен остальным пользователям организации. В такой схеме потребуется определить, фильтруется ли документ по tenant_id, списку групп, отдельным ACL-метаданным или комбинации условий. Этот выбор нужно зафиксировать до наполнения базы, потому что исправление метаданных после массовой загрузки потребует повторной обработки.
Что возвращать клиенту после загрузки
Upload-endpoint должен возвращать состояние операции, а не создавать впечатление мгновенной готовности. Внутри приложения удобно использовать логические этапы:
| Состояние | Смысл | Можно задавать вопросы |
|---|---|---|
accepted | Файл принят и получил document_id | Нет |
processing | Источник передан на обработку | Обычно нет |
ready | Сервис подтвердил завершение нужной операции | Да |
error | Обработка завершилась ошибкой или исчерпала повторы | Нет |
Это внутренние состояния приложения. Точные статусы и переходы нужно сопоставить с API и конфигурацией AWS, которые используются в конкретном проекте. Клиенту достаточно понятного сообщения: «Файл принят», «Документ обрабатывается», «Документ готов к вопросам» или «Не удалось обработать файл».
Асинхронное индексирование: когда документ становится searchable
Почему успешная загрузка еще не означает доступность в чате
Между сохранением файла и появлением фрагментов в retrieval находятся несколько операций: чтение объекта, извлечение текста, подготовка содержимого, построение поискового представления и обновление доступного состояния. Эти операции могут выполняться отдельно от HTTP-запроса пользователя.
Если UI показывает «загрузка завершена» сразу после сохранения объекта, пользователь легко решит, что вопрос по файлу уже должен вернуть результат. При пустом retrieval приложение ошибочно покажет сбой или передаст запрос в LLM без контекста. Второй вариант особенно опасен: модель может ответить общими словами, хотя документ еще не готов.
Корректная формула проще: файл принят, когда сохранение подтверждено; документ searchable, когда завершение обработки подтверждено выбранным поисковым контуром.
Как построить ожидание готовности
Есть два распространенных подхода:
- Backend периодически проверяет состояние операции и обновляет запись документа.
- Приложение принимает событие о смене состояния, если такая схема доступна для выбранной конфигурации AWS.
В обоих вариантах нужны одинаковые элементы надежности:
- Отдельная запись операции с привязкой к
document_id. - Ограниченный интервал повторной проверки.
- Таймаут, после которого операция получает понятное состояние ошибки.
- Разделение временного сбоя и окончательного отказа.
- Идемпотентный повтор без создания второго документа.
- Ручной запуск повторной обработки из интерфейса или административного API.
Не считайте документ готовым по факту принятия запроса на ingestion. Нужен сигнал завершения, который приложение связывает с конкретным документом и конкретной версией файла.
Гонки между загрузкой и первым вопросом
Пользователь может открыть чат и задать вопрос сразу после выбора файла. Система должна заранее определить поведение в этот момент.
- Заблокировать вопросы по документу до состояния
ready. - Разрешить вопрос, но вернуть явный ответ о продолжающейся обработке.
- Показать только документы, которые уже доступны для retrieval.
- Не подменять отсутствие фрагментов общим ответом без предупреждения.
Бесконечное ожидание ухудшает UX и усложняет диагностику. Лучше показать промежуточное состояние, идентификатор документа и возможность повторить запрос после завершения обработки.
Для массовой загрузки полезно отображать общий прогресс отдельно от состояния каждого файла. Один сбой не должен скрывать готовность остальных документов, а успешная обработка одного объекта не должна переводить всю пакетную операцию в состояние полной готовности.
Retrieval с server-side фильтрацией по user_id
Откуда сервер берет user_id
Доверенным источником служит проверенная сессия, JWT или другой механизм аутентификации, который backend умеет валидировать. После проверки сервер сопоставляет внешний identity с внутренним пользователем и его правами.
Клиентское поле может содержать любой идентификатор. Пользователь способен изменить его через инструменты разработчика, повторить HTTP-запрос с другим значением или отправить запрос напрямую, минуя интерфейс. Поэтому такой параметр можно использовать для отображения, но сервер должен игнорировать его при построении области доступа.
def resolve_access_context(request):
principal = authenticate(request)
if principal is None:
raise UnauthorizedError()
user = find_user_by_identity(principal.identity)
return {
'user_id': user.id,
'tenant_id': user.tenant_id,
'groups': user.groups,
}
Если приложение поддерживает личные и общие документы, функция авторизации должна возвращать полный контекст доступа. Подстановка одного user_id вместо проверки групп может открыть файлы рабочей области всем ее участникам.
Фильтр должен применяться к каждому запросу
Фильтрация нужна для нового вопроса, follow-up, повторного запроса, регенерации ответа и любого агентного действия, которое инициирует retrieval. Нельзя рассчитывать на то, что предыдущий запрос уже отфильтровал контекст или что интерфейс скрыл чужие документы.
Серверный путь должен выглядеть единообразно:
- Проверить identity.
- Получить актуальный контекст пользователя.
- Построить metadata filter из серверных данных.
- Проверить, что выбранный режим retrieval принимает этот фильтр.
- Передать вопрос и фильтр в поисковый слой.
- Передать агенту только результат из разрешенной области.
Псевдокод запроса:
def answer_question(request):
access = resolve_access_context(request)
query = validate_query(request.query)
metadata_filter = build_filter(
user_id=access['user_id'],
tenant_id=access['tenant_id']
)
chunks = retrieve(query=query, filter=metadata_filter)
return generate_answer(query=query, context=chunks)
Названия параметров здесь условные. Формат фильтра нужно сверить с используемым API и проверить на реальных метаданных. Смысл остается постоянным: сервер формирует ограничение до получения контекста.
Защита от подмены и обхода фильтра
Негативные сценарии должны входить в обязательный набор тестов. Положительный тест подтверждает, что пользователь видит свой документ. Негативный проверяет, что изменение параметров не открывает чужие данные.
| Атака или ошибка | Что может произойти | Контроль |
|---|---|---|
Подмена user_id | Вопрос выполняется по чужой области | Игнорировать значение клиента и брать identity из проверенной сессии |
| Прямой вызов retrieval endpoint | Пользователь обходит UI и отправляет собственные параметры | Закрыть прямой доступ, проверять аутентификацию и фильтр на сервере |
Запрос по чужому document_id | Раскрываются имя, статус или содержимое документа | Проверять принадлежность документа до любой операции |
| Пропущенные метаданные | Документ выпадает из фильтра или становится трудно проверяемым | Отклонять объект без обязательных метаданных и вести аудит |
| Общий кэш ответов | Одинаковый вопрос возвращает чужой контекст | Включать tenant context в ключ и проверять права до чтения кэша |
| Групповой доступ без проверки membership | Пользователь получает документы организации без нужной роли | Проверять группу, роль и состояние членства |
Проверка должна включать смену параметров, повторное использование старого токена, удаление прав между двумя запросами и запрос к документу, который принадлежит другому пользователю. UI-тестов недостаточно: запросы нужно отправлять напрямую к backend.
Какие части системы остаются на стороне приложения
Backend как точка контроля доступа
Backend связывает аутентифицированного пользователя с документами, операциями и retrieval-фильтрами. Через него проходят загрузка, список файлов, вопросы, удаление, замена и повторная обработка.
Клиенту не следует выдавать полномочия на самостоятельный выбор tenant_id, списка документов или готового фильтра. Даже если UI передает эти параметры, сервер обязан пересобрать их из проверенного контекста.
В прикладной базе полезно хранить аудит минимум для следующих событий:
- создание документа;
- изменение или замена файла;
- запуск ingestion;
- смена статуса операции;
- retrieval-запрос и его область доступа;
- удаление документа;
- изменение группового доступа.
Состояния, ошибки и повторные попытки
Одна строка status = error редко помогает разобраться в проблеме. Разделяйте состояние документа, состояние операции и техническую причину сбоя. Например, документ может оставаться доступным в старой версии, пока новая версия ожидает повторной обработки.
| Состояние | Решение приложения |
|---|---|
| Временная ошибка | Поставить ограниченный повтор с задержкой и сохранить причину |
| Ошибка валидации | Попросить пользователя заменить файл или исправить параметры |
| Истек таймаут | Остановить автоматические повторы и создать задачу для диагностики |
| Дубликат операции | Вернуть существующий результат по ключу идемпотентности |
| Успешная обработка | Перевести документ в состояние, доступное для retrieval |
Ключ идемпотентности можно связать с пользователем, логическим документом и хэшем файла. При повторной отправке сеть может доставить тот же запрос дважды, поэтому backend должен уметь вернуть уже созданную операцию вместо создания второй записи.
Удаление, замена и аудит документов
Удаление требует согласовать три состояния: исходный объект, прикладную запись и поисковое представление. Удаленный файл не должен оставаться доступным через retrieval из-за того, что физический объект уже исчез, а индекс еще не обновился.
Для замены файла полезно использовать версию документа. Новая версия получает собственную операцию обработки, а старая сохраняется доступной или блокируется по заранее выбранному правилу. Смешивать фрагменты двух версий в одном ответе без явного контроля не следует.
После отзыва доступа backend должен запретить новый retrieval сразу на прикладном уровне. Удаление или обновление поискового представления может завершиться позже, поэтому серверный фильтр и проверка записи документа должны учитывать актуальное состояние прав.
Масштабирование и ограничения multi-tenant document chat
Массовые загрузки и ограничение конкурентности
Пакет из нескольких тысяч файлов нельзя обрабатывать тем же способом, что одиночную загрузку из UI. Для массового импорта нужна очередь задач, ограничение параллелизма и отдельный мониторинг. Иначе всплеск операций одного арендатора создаст задержки для остальных.
Очередь должна хранить как минимум document_id, версию файла, тип операции, число попыток и время следующего запуска. Повторная задача не должна создавать новый объект или новую логическую запись без проверки идемпотентности.
- Ограничивайте число одновременных операций.
- Разделяйте одиночные загрузки и пакетный импорт.
- Показывайте состояние каждого документа отдельно.
- Храните причину последней ошибки.
- Отделяйте очередь повторов от очереди новых файлов.
- Проверяйте фактические квоты и ограничения AWS перед расчетом производительности.
Неравномерная нагрузка между арендаторами
Средняя latency по всей системе может скрывать проблему одного крупного арендатора. Метрики нужно группировать по tenant_id или другому разрешенному техническому идентификатору.
| Метрика | Зачем нужна |
|---|---|
| Количество документов | Показывает рост объема по каждому арендатору |
| Размер очереди | Помогает увидеть накопление операций |
| Длительность ingestion | Показывает задержки обработки файлов |
| Доля ошибок | Отделяет единичный сбой от системной проблемы |
| Частота retrieval | Помогает оценить нагрузку на поисковый и генеративный слой |
| Количество повторов | Выявляет проблемы с идемпотентностью или временными сбоями |
Для защиты соседних арендаторов можно ограничивать число одновременных задач на пользователя или организацию. Конкретный предел выбирается по квотам сервиса, размеру файлов и требованиям к задержке, поэтому его нельзя назначать по универсальному числу.
Кэширование и повторные запросы без утечки данных
Одинаковый текст вопроса не означает одинаковый ответ. Два пользователя могут задать вопрос «Какая дата окончания договора?», имея разные документы. Ключ кэша должен учитывать область доступа.
Пример логического ключа:
tenant_id:user_id:filter_version:document_scope:normalized_query
Если права пользователя изменились, старый результат нужно считать потенциально устаревшим. Для этого применяют версию фильтра, версию набора документов или явное удаление связанных записей кэша.
Нельзя кэшировать retrieval-результат только по тексту вопроса. Такая схема смешивает контексты пользователей и создает риск раскрытия содержимого даже при корректной авторизации основного запроса.
Когда управляемого retrieval уже недостаточно
Managed Knowledge Base подходит для сценария, где приложение передает документы в управляемый контур и получает поиск по ним с контролируемыми метаданными. Дополнительное проектирование или другой поисковый слой может потребоваться при следующих условиях:
- права зависят от сложных связей между пользователями, группами, ролями и отдельными фрагментами;
- нужно применять нестандартную логику ранжирования;
- требуется полный контроль над индексом и собственным форматом обработки;
- поисковый контур должен учитывать сложные транзакционные правила;
- нужны особые требования к задержке, переносу данных или локальному размещению;
- документы проходят нестандартный конвейер распознавания, очистки и обогащения.
Выбор зависит от модели прав, объема данных, стоимости ошибок и требований к latency. Управляемый сервис уменьшает инфраструктурную нагрузку, но не отменяет проверку архитектурных ограничений на реальном наборе документов.
Практический чек-лист перед запуском
Минимальный набор проверок безопасности
- Identity:
user_idберется из проверенной сессии или токена. - Подмена: изменение
user_idв теле запроса не меняет область поиска. - Прямой retrieval: вызов endpoint без UI не позволяет убрать или изменить фильтр.
- Чужой документ: запрос по чужому
document_idвозвращает отказ или пустой результат. - Метаданные: документ без обязательного владельца не становится доступным для поиска.
- Группы: пользователь видит только документы разрешенной организации или группы.
- Кэш: ключ учитывает пользователя, арендатора и версию области доступа.
- Токен: просроченная или повторно использованная сессия обрабатывается по правилам системы.
Минимальный набор проверок жизненного цикла
- Новый документ не появляется в ответах до подтверждения готовности.
- После успешной обработки retrieval находит ожидаемый фрагмент.
- Временная ошибка запускает ограниченный повтор.
- Повторная отправка того же файла не создает дубликат.
- Ошибка обработки видна пользователю и записывается в журнал.
- Удаленный документ перестает участвовать в новых ответах.
- Замена файла не смешивает версии без явного правила.
- Массовая загрузка сохраняет отдельный статус по каждому объекту.
- Отзыв прав блокирует доступ на backend, даже если поисковое представление еще обновляется.
Итоговая модель ответственности
Amazon Bedrock Managed Knowledge Base сокращает объем кода вокруг управляемого retrieval. Безопасный мультиарендный чат строится приложением вокруг трех дисциплин: доверенной аутентификации, server-side фильтрации по user_id и явного управления асинхронным жизненным циклом индексирования.
Перед запуском проверьте четыре связи: пользователь связан с разрешенной областью, документ связан с владельцем, ingestion связан с конкретной версией файла, retrieval связан с серверным фильтром. После этого добавьте контроль очереди, идемпотентные повторы, аудит, изолированный кэш и негативные тесты. Точные квоты, статусы и поддерживаемые режимы AWS подтвердите по документации выбранной конфигурации.