Мультиязычный RAG на AWS Bedrock: Архитектура Ring для экономии 21% | AiManual
AiManual Logo Ai / Manual.
30 Мар 2026 Гайд

Архитектура Ring: как масштабировать мультиязычную RAG-поддержку на Bedrock Knowledge Bases с экономией 21%

Пошаговый гайд по Ring-архитектуре для глобального RAG-чата на AWS Bedrock Knowledge Bases. Фильтрация по метаданным, пайплайны Step Functions и реальный кейс э

Ваша глобальная поддержка стоит как маленький флот. И это глупо

Вы развернули RAG-чатбота для поддержки клиентов. Он работает на Amazon Bedrock Knowledge Bases, отвечает на вопросы по документации. Пока только на английском. Потом приходит задача: "Нужна поддержка на 12 языках в 8 регионах".

Типичная реакция? Создать 12 отдельных Knowledge Bases. Или один гигантский, где все смешано. Первый вариант съедает бюджет быстрее, чем отдел закупок успевает подписать апрувы. Второй превращает релевантность поиска в лотерею. Ответ на вопрос про возврат товара в Германии будет содержать ссылки на политику доставки в Японии. Клиенты в ярости, инженеры в стрессе, CFO смотрит на счет за AWS как на личное оскорбление.

Проблема не в технологии. Проблема в архитектуре. Копирование инфраструктуры для каждого языка или региона - тупик. Смешивание всего в одну кучу - хаос. Нужен третий путь.

Кейс Ring: Перед внедрением Ring-архитектуры их пилотный мультиязычный чатбот на базе единой Knowledge Base показывал точность (nDCG@3) около 0.62 для неанглийских запросов. После рефакторинга - 0.89. Месячные затраты на инференс и хранение упали с прогнозируемых ~$8500 до ~$6700. Эти 21% - не магия, а правильная фильтрация.

Ring-архитектура: один центр, много вращающихся инстансов

Забудьте про "один на всех" и "каждому свое". Архитектура Ring работает как планетарий. В центре - единое хранилище документов (S3 Bucket) и мастер-пайплайн обработки. Вокруг него вращаются легковесные, регионально-специфичные инстансы Knowledge Bases. Каждый инстанс знает свой язык, свои метаданные и свою политику фильтрации.

Суть в сегментации не на уровне данных, а на уровне метаданных и запросов. Все документы загружаются в единый конвейер. Но при индексации каждый чанк помечается четкими метаданными: locale: de_DE, region: eu-central-1, doc_type: return_policy. Сама Knowledge Base остается одна (физически). А вот каждый endpoint для чата (например, один для немецкой поддержки, другой для японской) - это отдельный, тонко сконфигурированный клиент. Его главная работа - жестко фильтровать по этим метаданным перед поиском.

Почему это выгодно? Потому что стоимость Amazon Bedrock Knowledge Bases складывается из:

  • Хранение индекса (~$0.023 за GB/месяц)
  • Инференс (поиск и генерация) (~$0.0015 за 1K токенов для Claude 3.5 Sonnet на момент 30.03.2026)

Создавая отдельную KB для каждого языка, вы платите за хранение множества почти одинаковых индексов. Данные-то одни и те же, просто на разных языках! В Ring-архитектуре индекс один. Фильтрация по метаданным - это почти бесплатная операция на стороне запроса. Экономия в 21% у Ring - это в основном экономия на дублирующем хранении и более эффективном поиске (система не перебирает ненужные чанки).

Скелет системы: что куда засунуть

Давайте разберем компоненты, без воды.

Компонент Назначение Почему здесь?
Central Document Bucket (S3) Единое хранилище исходных PDF, DOCX, TXT для всех языков. Один источник истины. Управление версиями документации происходит в одном месте.
Ingestion & Sync Pipeline (AWS Step Functions) Автоматическая обработка новых/обновленных файлов: извлечение текста, разбивка, обогащение метаданными, загрузка в KB. Step Functions идеальны для долгих, многоэтапных пайплайнов с обработкой ошибок. Легко добавить этап перевода (например, через Amazon Translate).
Unified Knowledge Base (Bedrock KB) Единственный векторный индекс. Использует обновленную на 2026 год встроенную эмбеддинг-модель (например, cohere.embed-english-v3 или amazon.titan-embed-text-v2:0). Не надо плодить сущности. Все данные здесь, отфильтрованные по метаданным.
Regional/Locale Endpoints (Lambda + API Gateway) Отдельные HTTP-эндпоинты для каждого языка/региона. Их код знает, какой locale и region проставлять в фильтр. Изоляция. Падение эндпоинта для французского языка не затронет японский. Можно независимо масштабировать.
Metadata Filter (в коде Lambda) Критически важный кусок логики. Превращает запрос пользователя "Как оформить возврат?" в поисковый запрос к KB с фильтром: {"locale": "de_DE", "region": "eu-central-1"}. Это сердце Ring-архитектуры. Именно фильтр вырезает из общего индекса только нужные чанки.

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

1 Папа, откуда берутся метаданные?

Ошибка номер один - пытаться назначать метаданные вручную. Вы с ума сойдете, если у вас тысячи документов. Метаданные должны генерироваться автоматически в пайплайне обработки.

Ваш Step Functions пайплайн должен выглядеть так:

// Упрощенная схема State Machine
{
  "StartAt": "DetectFileType",
  "States": {
    "DetectFileType": { ... },
    "ExtractText": { ... },
    "ChunkDocument": { ... },
    "EnrichWithMetadata": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:...",
      "Parameters": {
        "chunks": [ ... ],
        "sourceFileKey": "s3://bucket/de/returns.pdf",
        "inferredLocale": "de_DE", // Определили из пути S3 или контента
        "inferredRegion": "eu-central-1",
        "docType": "policy" // Определили по классификатору
      },
      "Next": "StoreInKB"
    },
    "StoreInKB": {
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:bedrockagent:ingestKnowledgeBaseDocuments",
      "Parameters": {
        "knowledgeBaseId": "YOUR_UNIFIED_KB_ID",
        "dataSourceId": "YOUR_DATA_SOURCE_ID",
        "documents": [{
          "content": { "bytes": "..." },
          "metadata": { // ВОТ ОНИ!
            "locale": "de_DE",
            "region": "eu-central-1",
            "doc_type": "policy",
            "source_file": "returns_de_v2.pdf"
          }
        }]
      }
    }
  }
}

Ключевой этап - EnrichWithMetadata. Локаль и регион часто можно вывести из пути файла в S3 (/de/returns.pdf -> locale=de_DE). Тип документа (инструкция, политика, FAQ) определяете с помощью простого классификатора (ключевые слова или даже вызов Bedrock's Converse API с промптом "classify this text").

💡
Не используйте общие теги вроде "lang". Используйте стандартные коды локалей (de_DE, ja_JP, en_US). Это позволит в будущем легко отделить, например, немецкий в Германии от немецкого в Австрии (de_AT), если понадобится.

2 Фильтрация: превращаем запрос в снайперский выстрел

Теперь самое интересное. Пользователь заходит на немецкий сайт, пишет в чат: "Wie kann ich eine Rücksendung veranlassen?". Ваш Lambda-эндпоинт для Германии должен:

  1. Принять запрос.
  2. Жестко дополнить его фильтром по метаданным.
  3. Отправить уже отфильтрованный запрос в единую Knowledge Base.

Вот как выглядит критически важный фрагмент кода на Python (используя актуальный на 2026 год boto3):

import boto3
from botocore.config import Config

bedrock_agent = boto3.client('bedrock-agent-runtime', config=Config(region_name='us-east-1')) # Регион, где создана KB

def retrieve_and_generate(query, user_locale='de_DE', user_region='eu-central-1'):
    """
    Извлекаем контекст и генерируем ответ ТОЛЬКО на основе документов
    для указанной локали и региона.
    """
    
    # 1. Строим фильтр на основе контекста вызова (это знает эндпоинт)
    filter_condition = {
        "andAll": [
            {"equals": {"key": "locale", "value": user_locale}},
            {"equals": {"key": "region", "value": user_region}}
        ]
    }
    
    # 2. Поиск с фильтром
    retrieval_response = bedrock_agent.retrieve(
        knowledgeBaseId=UNIFIED_KB_ID,
        retrievalQuery={
            "text": query  # Оригинальный запрос пользователя
        },
        retrievalConfiguration={
            "vectorSearchConfiguration": {
                "filter": filter_condition  # ВАЖНО: фильтр применяется ДО поиска по схожести
            }
        },
        # Используем новую модель для поиска, актуальную на 2026
        modelId="amazon.titan-embed-text-v2:0", 
        numberOfResults=5
    )
    
    # 3. Генерация ответа на основе ОТФИЛЬТРОВАННЫХ результатов
    generation_response = bedrock_agent.generate(
        knowledgeBaseId=UNIFIED_KB_ID,
        modelId="anthropic.claude-3-5-sonnet-20241022", # Актуальная версия Claude на 30.03.2026
        retrievalConfiguration={
            "vectorSearchConfiguration": {
                "filter": filter_condition  # Фильтр ДОЛЖЕН быть повторен и здесь!
            }
        },
        input={
            "text": f"""Ответь на вопрос на языке {user_locale}, используя только предоставленный контекст.\n\nКонтекст:\n{formatted_context}\n\nВопрос: {query}"""
        }
    )
    
    return generation_response['output']['text']

Предупреждение: Многие забывают передать filter в этап generate. Вызов retrieve и generate в Bedrock - это отдельные операции. Если в generate не указать фильтр, модель может проигнорировать ваш тщательно отобранный контекст и "галлюцинировать" на основе всего индекса. Всегда дублируйте фильтр.

Этот подход убивает двух зайцев. Качество ответов растет, потому что модель не путается в нерелевантных чанках. Стоимость падает, потому что система не тратит вычислительные ресурсы на обработку мусора. Вам не нужен сложный иерархический RAG, чтобы добиться точности. Нужна лишь жесткая, корректная фильтрация.

Где спрятаны грабли: нюансы, которые потопят проект

  • Смешанные документы: Что делать с документом, который актуален для всех регионов (например, глобальный кодекс поведения)? Индексируйте его несколько раз с разными метаданными (locale: all не сработает). Или создайте отдельный, "глобальный" эндпоинт без фильтра по региону, но с фильтром по типу документа.
  • Динамическое определение локали: Не доверяйте IP-адресу пользователя для определения языка. Используйте явный выбор языка на сайте или в приложении. IP ведет к ошибкам (немецкий пользователь в отпуске в Италии получит итальянские ответы).
  • Обновление индекса: При обновлении документа для одной локали, старые чанки нужно удалить. Используйте source_file в метаданных и операцию удаления по фильтру (source_file='returns_de_v1.pdf') перед загрузкой новой версии. В противном случае в индексе будут и старая, и новая информация, что запутает модель.
  • Квоты Bedrock: Единая KB означает, что все регионы бьют в один endpoint. Убедитесь, что запросы к bedrock-agent-runtime не упираются в лимиты Request Rate. Используйте резервирование пропускной способности или географически распределенные эндпоинты Lambda, но с указанием на KB в одном регионе (приемлемая задержка для поиска обычно ниже 500 мс).

Вопросы, которые не дают спать (и ответы на них)

А если нужен гибридный поиск (семантический + ключевые слова)?

Bedrock Knowledge Bases с конца 2025 года поддерживает гибридный поиск из коробки. В конфигурации vectorSearchConfiguration вы можете указать "hybridSearch": {"enabled": true}. Фильтрация по метаданным работает и для гибридного поиска. Это еще больше повысит точность для специфичных терминов. Просто убедитесь, что ваши метаданные также проиндексированы для полнотекстового поиска.

Как измерять успех и где взять эти 21% экономии?

Считайте два сценария. Наивный: 12 языков = 12 KB. Стоимость хранения индекса * 12. Ring: 1 KB, но индекс в ~1.5 раза больше (из-за разных переводов одного документа). Экономия на хранении очевидна. Добавьте сюда экономию на инференсе: точный поиск требует обработки меньшего числа чанков, иногда можно уменьшить numberOfResults. Мониторьте метрики в CloudWatch: RetrievedResults (сколько чанков нашел поиск) и Latency. После внедрения фильтров первый показатель должен упасть, второй - остаться стабильным или уменьшиться. Реальная экономия зависит от объема данных и трафика. 21% - реалистичный показатель для компании уровня Ring.

А не проще использовать один большой промпт с инструкцией "отвечай на немецком"?

Нет. Это наивный подход, который сломается на сложных вопросах. Модель может начать переводить ответы с английских чанков, что приведет к смысловым ошибкам. Или, что хуже, начнет "сочинять" ответ, если не найдет точного соответствия на нужном языке. Фильтрация на уровне поиска - единственный production-ready способ.

Что делать с устными диалектами или очень специфичной терминологией?

Это выходит за рамки базовой фильтрации. Вам понадобится более продвинутая обработка контента. Но Ring-архитектура - отличная основа. Вы можете добавить в пайплайн дополнительный этап обогащения: например, с помощью кастомной ML-модели добавлять к чанкам термины из глоссария (metadata: {"contains_technical_jargon": true}). Затем ваш фильтр сможет отсекать или, наоборот, включать такие чанки в зависимости от уровня подготовки пользователя.

Итог: не умножай сущности, умножай метаданные

Глобальная RAG-поддержка - это не про копирование инфраструктуры. Это про умную сегментацию. Архитектура Ring доказывает, что один хорошо организованный индекс с продуманной системой фильтрации beats армию разрозненных баз.

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

И да, эти 21% - не потолок. После оптимизации пайплайнов, перехода на более новые и эффективные эмбеддинг-модели (следите за обновлениями Bedrock в 2026) и настройки кэширования частых запросов, можно выжать и 30%. Но это уже тема для другой статьи. Сначала постройте кольцо.

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