Практические заметки: Multi-Tenant RAG на Amazon S3 Vectors — Часть 3: Пайплайн
Пошаговое руководство по практическим заметкам: Multi-Tenant RAG на Amazon S3 Vectors — Часть 3: Пайплайн: контракты, проверки и слоты для вставки кода для команд, использующих эту схему.
В этом руководстве показано, как построить цепочку от сырья до рабочей системы для реализации Multi-Tenant RAG на Amazon S3 Vectors — Часть 3: Пайплайн. Основное внимание уделяется выполнимым шагам, явным проверкам и коду, который можно просто добавить в репозиторий без необходимости догадываться о его назначении. На этапе обзора необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии системы. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала помогает избежать неожиданных счетов при переходе от демо-среды к общедоступным средам.
Архитектура
При работе над этапом архитектуры сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Храните конфигурацию отдельно от кода приложения. Файлы с настройками окружения, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Измеряйте уровень воспроизводимости ответов на фиксированный набор вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.
Ingest: S3 (tenant prefix) ──▶ Lambda: extract + chunk ──▶ SQS ──▶ Lambda: embed (Bedrock Titan V2)
└──▶ PutVectors → index/org-<tenant>
Query: eID provider (OIDC) ──▶ API authorizer (tenant, role) ──▶ STS session policy (one index ARN)
──▶ embed question ──▶ QueryVectors(index/org-<tenant>, filter=classification)
──▶ LLM, grounded on returned chunk_text, citations = document_id + version
Audit: CloudTrail data events, resource type AWS::S3Vectors::Index, queried by resources.ARN
Создание индекса
При работе над этапом создания индекса сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Документируйте одновременно успешный и восстановительный сценарии работы. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Измеряйте точность восстановления информации на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.
import { S3VectorsClient, CreateIndexCommand } from "@aws-sdk/client-s3vectors";
const s3v = new S3VectorsClient({ region: "eu-west-1" });
await s3v.send(new CreateIndexCommand({
vectorBucketName: "kb-eu-west-1", // the bucket is regional → residency
indexName: "org-b", // the index is the tenant → isolation
dataType: "float32",
dimension: 1024, // Titan Text Embeddings V2, 1024-d
distanceMetric: "cosine",
metadataConfiguration: {
nonFilterableMetadataKeys: ["chunk_text"], // returned, never scanned, never filterable
},
}));
Получение данных: выбор ключей
При работе над этапом «Входные данные: выбор ключей» сначала запишите условия взаимодействия: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые модули большим скриптам. Если какой-то шаг не сработает, причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций. Оцените уровень воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска. При работе над этапом «Входные данные: выбор ключей» сначала запишите условия взаимодействия: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость токенов или запросов. Отслеживание затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.
import { PutVectorsCommand } from "@aws-sdk/client-s3vectors";
async function ingestDocument(tenant: string, doc: Document, chunks: string[]) {
const vectors = await Promise.all(chunks.map(async (text, i) => ({
key: `${doc.id}-k${String(i + 1).padStart(3, "0")}`, // chosen by us
data: { float32: await embed(text) },
metadata: {
document_id: doc.id, // filterable
version: doc.version, // filterable → appears on the citation
classification: doc.classification, // filterable → role filter at query time
chunk_text: text, // non-filterable → rides with the vector
},
}))); for (const batch of chunk(vectors, 500)) { // ≤ 500 per call
await s3v.send(new PutVectorsCommand({
vectorBucketName: "kb-eu-west-1", indexName: `org-${tenant}`, vectors: batch,
}));
}
await manifest.put(tenant, doc.id, doc.version, vectors.map(v => v.key)); // written down
}
import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
const bedrock = new BedrockRuntimeClient({ region: "eu-west-1" });
async function embed(text: string): Promise<number[]> {
const res = await bedrock.send(new InvokeModelCommand({
modelId: "amazon.titan-embed-text-v2:0",
contentType: "application/json",
body: JSON.stringify({ inputText: text, dimensions: 1024, normalize: true }),
}));
return JSON.parse(new TextDecoder().decode(res.body)).embedding;
}
Запрос: Учетные данные для обозначения одного индекса
Метод «Учетные данные для обозначения этапа» работает наилучшим образом, если рассматривать его как измеримую характеристику. Соберите один эталонный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
function indexArn(tenant: string) {
return `arn:aws:s3vectors:eu-west-1:${ACCOUNT}:bucket/kb-eu-west-1/index/org-${tenant}`;
}async function tenantCredentials(tenant: string) {
const res = await sts.send(new AssumeRoleCommand({
RoleArn: QUERY_ROLE_ARN, // broad role
RoleSessionName: `q-${tenant}`,
DurationSeconds: 900,
Policy: JSON.stringify({ // session policy: intersection, never a widening
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: ["s3vectors:QueryVectors", "s3vectors:GetVectors"],
Resource: indexArn(tenant), // exactly one ARN
}],
}),
}));
return res.Credentials!;
}
import { QueryVectorsCommand } from "@aws-sdk/client-s3vectors";
async function retrieve(tenant: string, role: Role, question: string) {
const client = new S3VectorsClient({ region: "eu-west-1", credentials: await tenantCredentials(tenant) });
const res = await client.send(new QueryVectorsCommand({
indexArn: indexArn(tenant),
queryVector: { float32: await embed(question) },
topK: 10,
filter: { classification: { $in: allowedClassifications(role) } }, // evaluated during search
returnMetadata: true,
returnDistance: true,
}));
return res.vectors ?? []; // each: key, distance, metadata incl. chunk_text
}
Умышленный баг
Этап The Deliberate Bug работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Документируйте одновременно успешный сценарий выполнения и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не этапом последующей доработки. Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества.
const client = new S3VectorsClient({ region: "eu-west-1", credentials: await tenantCredentials("b") });
await client.send(new QueryVectorsCommand({ indexArn: indexArn("a"), queryVector: { float32: q }, topK: 10 }));
// → AccessDeniedException: User ... is not authorized to perform: s3vectors:QueryVectors on resource: .../index/org-a
Удаление: удаление по ключу, проверка по ключу
Подход «Удаление по ключу» работает наилучшим образом, когда его рассматривают как измеримую среду. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Предпочитайте небольшие, тестируемые единицы вместо обширных скриптов. Когда какой-либо шаг срабатывает некорректно, причина сбоя должна указывать на конкретный ответственный элемент, а не на запутанную цепочку операций. Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества. Подход «Удаление по ключу» работает наилучшим образом, когда его рассматривают как измеримую среду. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте время выполнения операций, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.
import { DeleteVectorsCommand, GetVectorsCommand } from "@aws-sdk/client-s3vectors";
async function eraseDocument(tenant: string, docId: string, version: number) {
const keys = await manifest.get(tenant, docId, version);
for (const batch of chunk(keys, 500)) {
await s3v.send(new DeleteVectorsCommand({ vectorBucketName: "kb-eu-west-1", indexName: `org-${tenant}`, keys: batch }));
}
// verify — strongly consistent, so this is valid immediately
for (const batch of chunk(keys, 100)) {
const res = await s3v.send(new GetVectorsCommand({ vectorBucketName: "kb-eu-west-1", indexName: `org-${tenant}`, keys: batch }));
if ((res.vectors ?? []).length) throw new Error(`erasure incomplete: ${res.vectors!.length} keys remain`);
}
await audit.record({ tenant, docId, version, keyCount: keys.length, verifiedAt: new Date() });
}
Аудит: события данных CloudTrail по ARN
На этапе аудита событий данных CloudTrail необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не пытаясь угадать скрытое состояние. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их аудитировать, не читая весь код. Указывайте те участки текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.
// CDK
new cloudtrail.Trail(this, "Trail", { sendToCloudWatchLogs: false })
.addEventSelector(cloudtrail.DataResourceType.S3_VECTORS_INDEX, [
`arn:aws:s3vectors:eu-west-1:${account}:bucket/kb-eu-west-1/index/*`,
]);
// Check the CDK enum/name for the S3 Vectors index resource type; the underlying resource type is AWS::S3Vectors::Index.
{
"eventSource": "s3vectors.amazonaws.com",
"eventName": "QueryVectors",
"eventTime": "2026-09-09T10:41:07Z",
"userIdentity": { "type": "AssumedRole", "arn": "arn:aws:sts::123456789012:assumed-role/kb-query/q-b" },
"resources": [{ "type": "AWS::S3Vectors::Index",
"ARN": "arn:aws:s3vectors:eu-west-1:123456789012:bucket/kb-eu-west-1/index/org-b" }],
"errorCode": null
}
SELECT eventTime, eventName, userIdentity.arn, errorCode
FROM cloudtrail_events
WHERE eventSource = 's3vectors.amazonaws.com'
AND element_at(resources, 1).arn LIKE '%/index/org-a'
ORDER BY eventTime DESC;
Четыре дополнения, соответствующих модели
Для этапа «Четыре дополнения», который подходит для данной ситуации, необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Необходимо задокументировать как успешный, так и восстановительный пути выполнения. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. При следующем шаге, представляющем собой код или вызов инструмента, следует отдавать предпочтение структурированным выводам с проверкой по схеме перед свободным текстом.
Ключ KMS для каждого индекса.
Для каждого этапа с ключом A KMS необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Лучше использовать небольшие, тестируемые модули вместо обширных скриптов. При сбое шага причина должна быть связана с конкретной областью ответственности, а не с запутанной структурой процесса. Указывайте те части текста, которые легли в основу ответа. Без цитат операторы не смогут отличить вымысел от проблем с индексацией. Для каждого этапа с ключом A KMS необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Регистрируйте время выполнения, а также стоимость токенов или запросов вместе с функциональными результатами. Отображение стоимости заранее помогает избежать неожиданных счетов при переходе от демо-среды к общедоступным средам.
Экспорт.
При работе на этапе экспорта сначала запишите условия контракта: необходимые параметры входных данных, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает избегать ошибок при последующих изменениях кода. Храните конфигурацию отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Оцените точность воспроизведения ответов на фиксированном наборе вопросов перед настройкой подсказок. Изменение подсказок редко помогает улучшить качество поиска.
Путь в частной сети.
При работе над этапом пути в частной сети сначала запишите условия контракта: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Документируйте одновременно успешный сценарий работы и сценарий восстановления. Повторные попытки, проверки со стороны человека и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Измеряйте точность воспроизведения ответов на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.
Стоимость за арендатора.
При работе над этапом «Стоимость за арендатора» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Лучше использовать небольшие, тестируемые модули вместо обширных скриптов. Если какой-то шаг не сработает, причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций. Перед настройкой запросов измерьте уровень воспроизведения ответов на фиксированном наборе вопросов. Частая смена формулировок запросов редко помогает улучшить качество поиска. При работе над этапом «Стоимость за арендатора» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость токенов или запросов. Четкое отслеживание затрат с самого начала предотвращает неожиданные счета при переходе от демо-среды к общедоступным.
Чего не решает граница индекса
Этап «Границы индекса» работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь граф структуры. Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.
Роли внутри тенанта.
Механизмы работы внутри этапа арендатора работают наилучшим образом, если рассматривать их как измеримую структуру. Соберите один эталонный пример успешной работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Документируйте одновременно успешный сценарий выполнения и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не элементами последующей доработки. Разделяйте политику разбиения данных и политику их извлечения; изменение одной не должно принуждать к переписыванию другой при изменении показателей качества.
Утечка информации.
Этап борьбы с утечками в Existence работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Предпочитайте небольшие, тестируемые единицы вместо обширных скриптов. Когда какой-то шаг терпит неудачу, причина сбоя должна указывать на конкретный ответственный элемент, а не на запутанную цепочку операций. Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно вынуждать переписывать другую при изменении показателей качества. Этап борьбы с утечками в Existence работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.
Похожесть между языками.
На этапе сравнения между языками необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Указывайте те участки текста, на которых основан ответ. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.
Лексический поиск не используется.
На этапе без лексического поиска необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Необходимо задокументировать как успешный, так и восстановительный пути выполнения. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не последующими улучшениями. Указывайте те фрагменты текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.
Границы блоков.
На этапе определения границ блоков необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Лучше использовать небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое шага причина должна быть связана с конкретной функцией, а не с запутанной структурой обработки данных. Указывайте те части текста, которые легли в основу ответа. Без цитат операторы не смогут отличить вымысел от проблем с индексацией. На этапе определения границ блоков необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Записывайте время выполнения, а также стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости на раннем этапе предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.
Миграция моделей.
При работе над этапом миграции моделей сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Храните конфигурацию отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Храните в кэше стабильные инструкции системы и схемы инструментов. Пересылка одинаковых данных несколько раз — распространённая причина избыточных затрат.
Базы знаний Bedrock.
При работе над этапом Bedrock Knowledge Bases сначала запишите условия работы: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Документируйте одновременно успешный и восстановительный сценарии работы. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Измеряйте уровень воспроизводимости ответов на фиксированном наборе вопросов перед настройкой подсказок. Изменение подсказок редко помогает улучшить качество поиска.
Требования, ещё раз
Во время этапа повторного рассмотрения требований сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист помогает сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые модули большим скриптам. Если какой-то шаг не сработает, причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций. Оцените уровень воспроизводимости ответов на фиксированный набор вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска. Во время этапа повторного рассмотрения требований сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист помогает сохранять честность при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость в использовании токенов или запросов. Отслеживание затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.
Чек-лист операций
Этап проверочного списка операций работает наилучшим образом, когда рассматривается как измеримая основа. Соберите один идеальный пример выполнения, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ.
Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия создаваемым документам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задач.
Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества.
При наличии бюджета добавьте тест на базовую работоспособность, который проверяет критически важные этапы в рамках CI с использованием фикстчеров, а не реальных платных API.
Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам.
Необходимо разделять политику сегментации данных и политику их извлечения. Изменение одной из них не должно принуждать к переписыванию другой при изменении показателей качества.
Перед внедрением новой структуры следует заморозить существующие версии, сохранить эталонный вариант записи данных для критически важных этапов и уточнить шаги для возврата к предыдущему состоянию. В совместных средах необходимы ограничения на частоту запросов, проверки принадлежности ресурсов и четко определенный ответственный за обновление секретов. Лучше выбирать надежность, чем креативные одноразовые демонстрации.
Примечание для версии 0b50bb1ebd01: не храните ключи поставщика в репозитории, установите лимит токенов на каждую сессию и сохраняйте записи данных рядом с фиксами для оценки, чтобы последующие замены моделей оставались сопоставимыми.