Multi-tenancy на Amazon Bedrock AgentCore: пошаговое руководство с кодом | AiManual
AiManual Logo Ai / Manual.
23 Июн 2026 Инструмент

Как реализовать multi-tenancy с Amazon Bedrock AgentCore: пошаговое руководство с примерами кода

Полное руководство по реализации мультитенантности для AI-агентов на Amazon Bedrock AgentCore. Изоляция, cost tracking, код и архитектурные паттерны.

Реклама
cliv2

Почему мультитенантность в Bedrock AgentCore — это головная боль (и как её лечить)

Вы построили крутого AI-агента на Amazon Bedrock AgentCore. Он отвечает на вопросы, тащит данные из вашей базы, вызывает внешние API. Теперь эту магию хотят купить сразу три крупных заказчика. Каждый — со своими данными, своей логикой и своими счетами. И тут начинается ад: как сделать так, чтобы агент клиента А не видел данные клиента Б, чтобы затраты клиента В не списывались на клиента Г, и чтобы каждый мог менять промпты не ломая соседа? Добро пожаловать в мир мультитенантности.

Из коробки Amazon Bedrock AgentCore не имеет встроенной поддержки tenant. Агент — это ресурс, живущий в вашем аккаунте AWS. Он может быть один для всех, но тогда изоляция ложится на вашу логику в лямбда-функциях и action groups. Или вы плодите копии агентов — по экземпляру на tenant. И тот, и другой путь — с подводными камнями. В этой статье я разложу по полочкам, как НЕ надо делать, а потом покажу рабочие паттерны с кодом. Поехали.

Дисклеймер: всё ниже — исключительно практический опыт, выстраданный на production-нагрузках. Никакой воды.

Паттерны изоляции: под какой соус резать арендаторов

В теории SaaS существует три основных паттерна: Silo (отдельный экземпляр), Pool (общий экземпляр), Bridge (гибрид). В контексте Bedrock AgentCore они выглядят так:

  • Silo: на каждого tenant — свой AgentCore, свои action groups, своя база знаний. Максимальная изоляция, но кровавое управление и цена. Если у вас сотня клиентов, деплоить сотню агентов... только через CI/CD, как я описывал в гайде по деплою через GitHub Actions.
  • Pool: один AgentCore на всех, в каждом вызове передаёте tenant_id, а action groups уже разрезают доступ по этому ID. Проще, но изоляция — на вас. Если в лямбде ошибка — все видят чужие данные.
  • Bridge: общий AgentCore, но у каждого tenant своя база знаний (Knowledge Base) и подкапотные лямбды. Агент один, а хранилища — раздельные. Золотая середина.

Лично я — за Bridge. Он даёт гибкость Silo (можно выдать одному tenant Anthropic Claude Opus 4.5, другому — дешёвый Amazon Nova) без дублирования всего оркестрации. Но выбор зависит от вашего SLA и бюджета. Если tenant платят по-разному — Bridge позволяет нарезать tier'ы.

Архитектура «Как надо»: один агент, разные миры

Давайте спроектируем систему, где один AgentCore обслуживает сотню клиентов. Ключевая идея: action group (Lambda) получает tenant_id из сессии и решает, к каким данным дать доступ. Сам агент — просто роутер. Он не знает про tenant.

📌 Важно: tenant_id должен быть подписанным и проверяться в Lambda, чтобы клиент не мог подставить чужой ID. Используйте JWT или Cognito.

Зачем нам здесь CI/CD? Затем, что каждый раз менять одну лямбду для разных tenant — риск. Лучше иметь шаблон Agent (как FAST), который при деплое тянет конфигурацию tenant из Parameter Store или DynamoDB. Так вы меняете только конфиг, а не код.

1 Создание AgentCore с tenant-контекстом

Сам агент — обычный. Но в SessionAttributes мы передаём tenant_id на старте. Как это выглядит в boto3:

import boto3, json

client = boto3.client('bedrock-agent-runtime')

response = client.invoke_agent(
    agentId='YOUR_AGENT_ID',
    agentAliasId='YOUR_ALIAS_ID',
    sessionId=f"tenant-{tenant_id}-{user_session}",
    inputText=user_message,
    sessionState={
        'sessionAttributes': {
            'tenant_id': tenant_id
        },
        'promptSessionAttributes': {
            'current_tier': get_tenant_tier(tenant_id)
        }
    }
)

Обратите внимание: sessionId я формирую с префиксом tenant. Это помогает в логировании и cost tracking (см. ниже). Но сам контекст tenant пробрасывается через sessionAttributes.

2 Action group: Lambda, которая знает про tenant

В action group мы поднимаем Lambda. Её первая задача — достать tenant_id из event['sessionState']['sessionAttributes']. Если его нет — бой! Опять же, проверяем валидность токена (я использую AWS Secrets Manager, как в гайде по Secrets Manager).

def lambda_handler(event, context):
    tenant_id = event.get('sessionState', {}).get('sessionAttributes', {}).get('tenant_id')
    if not tenant_id:
        raise Exception('tenant_id required')

    # Получаем динамическую конфигурацию для tenant
    config_table = boto3.resource('dynamodb').Table('TenantConfig')
    tenant_config = config_table.get_item(Key={'tenant_id': tenant_id})['Item']

    # Теперь работаем с данными только этого tenant
    db = boto3.client('rds-data')
    # ... запросы с учётом схемы tenant

Здесь видна сила конфигурационной таблицы: вы храните ключи, endpoint'ы, model_id для этого tenant. Хотите дать одному клиенту модель Claude Haiku за 0.25$ за миллион токенов, а другому — Nova Pro за 0.80$? Просто меняете значение в DynamoDB!

3 Разграничение знаний: по одной базе на tenant

Если у вас Knowledge Base (RAG), то я рекомендую Bridge: создаём отдельный Data Source для каждого tenant, но все они подцеплены к одному Knowledge Base. Как? При создании Data Source указываете S3 prefix для tenant:

{
  "knowledgeBaseId": "common-kb",
  "dataSourceConfiguration": {
    "type": "S3",
    "s3Configuration": {
      "bucketArn": "arn:aws:s3:::my-docs",
      "inclusionPrefixes": ["tenants/{tenant_id}/"]
    }
  }
}

Когда агент вызывает RetrieveAndGenerate, вы передаёте тот самый tenant_id в sessionAttributes, а в action group для Knowledge Base зашиваете фильтр по этому prefix. Так каждый tenant видит только свои документы. Безопасно и легко масштабируется.

Cost tracking: как понять, кто сколько съел токенов

Самый частый запрос от CTO: «Почему счёт растёт?» Без мультитенантности вы не ответите. С нашим подходом — легко. Есть два способа:

  1. Cost Allocation Tags. При создании AgentCore через CloudFormation навешиваете на него тег tenant_id. Но это только на уровне ресурса — не покроет вызовы.
  2. Custom logging в CloudWatch. В каждой Lambda пишем метрику: tenant_id, invocation_id, затраченные токены, стоимость. Можно рассчитать по формуле model pricing (на момент июня 2026 цены на Claude уже упали ещё на 15%, но всё равно считайте).

Вот фрагмент для записи метрик:

import boto3
cloudwatch = boto3.client('cloudwatch')

def log_tenant_cost(tenant_id, model_id, input_tokens, output_tokens):
    cost_map = {
        'anthropic.claude-3-haiku': (0.00025, 0.00125),
        'amazon.nova-pro': (0.00080, 0.00240)
    }
    input_cost = input_tokens * cost_map[model_id][0] / 1000
    output_cost = output_tokens * cost_map[model_id][1] / 1000
    total_cost = input_cost + output_cost
    
    cloudwatch.put_metric_data(
        Namespace='AgentCosts',
        MetricData=[{
            'MetricName': 'TotalCost',
            'Dimensions': [{'Name': 'TenantId', 'Value': tenant_id}],
            'Value': total_cost,
            'Unit': 'Count'
        }]
    )

Дальше строим дашборду в Grafana или QuickSight — и видим, кто у нас «прожорливый клиент». Отличный повод перевести его на другой тариф.

💡
Не хотите писать свою систему аналитики? Используйте AWS Cost Explorer с тегами — навесили тег на agent alias и считайте расходы по каждому tenant.

Пример ошибки: как я чуть не слил данные всех tenant

Когда я впервые делал pool-паттерн, я допустил классическую ошибку: хранил tenant_id только в sessionId. Думал, это безопасно. Но клиент А переслал свой sessionId клиенту Б, и тот через веб-интерфейс получал данные от A. Потому что я не проверял tenant_id в action group! Кошмар.

Решение: tenant_id должен быть подписанным (JWT с вашим секретом) и проверяться в Lambda. И никакого доверия к sessionId.

Другая грабля — общая DynamoDB-таблица без partition key по tenant. Если забыли WHERE tenant_id = XXX в запросе, то один клиент может получить доступ ко всем данным. Заведите правило: каждая таблица с tenant_id как PK или GSI. И всегда используйте выражение ConditionExpression.

Интеграция с внешними сервисами: Slack, платежи и секреты

В мультитенантной архитектуре часто нужно, чтобы каждый tenant мог подключить свой Slack-канал или свои платёжные реквизиты. Тут пригождается наш гайд по интеграции со Slack — просто заведите для каждого tenant отдельную Slack-апп, а токены храните в Secrets Manager с меткой tenant_id.

И если вы хотите, чтобы агент оплачивал счета от имени tenant, используйте платёжный шлюз. Amazon запустил платежи через Bedrock AgentCore с Coinbase и Stripe — эти SDK поддерживают передачу tenant_id в metadata, так что вы сможете билить каждого арендатора отдельно.

CI/CD для мультитенантного мира: автоматизация без боли

Когда tenant растут (а они будут), вам нужно управлять деплоем агентов. Silo-паттерн требует механизма для создания новых инстансов. Bridge-паттерн — только обновления общей лямбды. Я рекомендую в каждом микросервисе (Lambda) использовать конфигурацию per-tenant из SSM Parameter Store или AppConfig. Тогда изменение одного параметра = изменение поведения для одного tenant, без полного деплоя.

Посмотрите на наш CI/CD пайплайн — он позволяет выкатывать изменения на staging, который видит только один tenant (ваш тестовый), а потом катить на всех. И не забудьте про Web Search on Amazon Bedrock AgentCore: если один tenant хочет веб-поиск, а другой нет, вы можете включать MCP-сервер выборочно через конфиг.

География и латенси: когда ваш агент живёт в Окленде, а клиент — в Токио

Если ваши tenant распределены по миру, используйте географическую маршрутизацию. Создайте по одному AgentCore в нескольких регионах и через Route53 направляйте tenant в ближайший. А key-value store для tenant_config храните глобально (DynamoDB Global Tables).

Итоговый совет: не пытайтесь сделать одну «серебряную пулю» для всех. Multi-tenancy — это trade-off между изоляцией, стоимостью и сложностью. На старте берите Pool + строгие action group check'и. Как только копнёте глубже — переходите на Bridge с отдельными Knowledge Bases и конфигами. А когда клиентов станет 100+ — подумайте о Silo, но только с автоматизированным деплоем.

Помните: лучшее — враг хорошего. Ваши арендаторы хотят, чтобы агент работал, а не чтобы его архитектура была идеальной. Дайте им это.

Подписаться на канал