RAG
Как индексировать документы, искать и пересортировывать фрагменты и формировать ответ с источниками.
Чтобы собрать поиск с ответом по внутренним документам, приложение должно выполнить три вида запросов через AI Gateway: получить векторы, найти подходящие фрагменты, при необходимости пересортировать их и передать выбранный контекст модели генерации. RAG — генерация с поиском по своим данным — помогает обосновывать ответы источниками и снижать риск выдуманных сведений.
AI Gateway предоставляет пути к моделям, но не хранит ваш индекс, не загружает документы и не строит весь процесс автоматически. Разбиение текстов, векторное хранилище, поиск, права доступа, сбор контекста и ссылки на источники остаются в вашем приложении.
Как работает RAG
Рабочая схема состоит из четырёх этапов:
- Разбить документы на осмысленные фрагменты и получить для них векторы.
- Получить вектор вопроса и найти ближайшие фрагменты в своём хранилище.
- При необходимости отдельно пересортировать найденные фрагменты по соответствию вопросу.
- Передать лучшие фрагменты модели генерации и попросить отвечать только по этому контексту.
Для каждого вызова выберите в разделе Модели модель с нужным опубликованным путём и скопируйте точный request_model_id. Идентификаторы модели векторизации, модели пересортировки и модели генерации могут отличаться. Не подставляйте название из старого примера: каталог и доступность меняются.
Шаг 1: индексировать документы
Сначала сохраните для каждого фрагмента как минимум:
- собственный идентификатор;
- исходный текст;
- ссылку или идентификатор документа;
- версию документа;
- вектор;
- точный идентификатор модели векторизации.
Путь векторизации — POST https://route.smartaipack.ru/v1/embeddings. Минимальное тело содержит model и input:
curl https://route.smartaipack.ru/v1/embeddings \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<EMBEDDING_REQUEST_MODEL_ID>",
"input": [
"Фрагмент документа номер один.",
"Фрагмент документа номер два."
]
}'Для подтверждённого совместимого поставщика input может быть строкой или массивом строк. Размер запроса и допустимое число элементов зависят от конкретной модели и поставщика, поэтому обрабатывайте документы порциями и проверяйте ограничения выбранной модели. Успешный совместимый ответ содержит массив data, а вектор каждого элемента находится в data[].embedding.
Для документов и будущих вопросов используйте одну и ту же модель векторизации. При смене модели постройте индекс заново: векторы из разных пространств нельзя корректно сравнивать как один набор.
Подробнее формат запроса разобран в статье «Векторные представления через AI Gateway».
Шаг 2: найти подходящие фрагменты
Когда приходит вопрос, отправьте его на тот же путь /v1/embeddings с тем же request_model_id. Затем приложение сравнивает вектор вопроса с сохранёнными векторами и выбирает ближайшие фрагменты.
Для небольшого учебного набора достаточно косинусного сходства в памяти. В рабочей системе индекс и поиск должны находиться в выбранном вами хранилище. AI Gateway не предоставляет встроенную векторную базу и не принимает на себя хранение исходных документов.
Перед поиском применяйте права доступа и фильтры по организации, продукту, языку или версии документа. Иначе математически близкий фрагмент может оказаться недоступным пользователю или устаревшим.
Шаг 3: пересортировать для точности
Векторный поиск быстро отбирает кандидатов, но их порядок не всегда совпадает с полезностью для конкретного вопроса. Дополнительная пересортировка может уточнить верхнюю часть списка.
Путь — POST https://route.smartaipack.ru/v1/rerank. Проект подтверждает такое минимальное тело:
curl https://route.smartaipack.ru/v1/rerank \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<RERANK_REQUEST_MODEL_ID>",
"query": "Как отозвать API-ключ?",
"documents": [
"Ключ можно отозвать в личном кабинете.",
"Расход по моделям отображается в аналитике."
]
}'Здесь documents — массив строк. Формат успешного ответа может зависеть от модели и поставщика. До написания рабочего разбора ответа выполните обезличенный проверочный запрос, сохраните его ответ как образец для выбранной модели и адаптируйте разборщик именно к этой форме. Не стройте интеграцию на предположении о названиях полей или структуре элементов.
Пересортировка необязательна. Она добавляет ещё один вызов, задержку и стоимость. Сначала измерьте качество векторного поиска на своих вопросах и только затем решайте, оправдан ли дополнительный этап.
Шаг 4: сгенерировать ответ с контекстом
Финальный запрос приложение отправляет на POST https://route.smartaipack.ru/v1/chat/completions. Минимальное тело содержит точный идентификатор модели и массив messages:
curl https://route.smartaipack.ru/v1/chat/completions \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<CHAT_REQUEST_MODEL_ID>",
"messages": [
{
"role": "system",
"content": "Отвечай только по переданному контексту. Если данных недостаточно, так и скажи. Указывай ID использованных источников."
},
{
"role": "user",
"content": "Контекст:\n[doc-17] Ключ можно отозвать в личном кабинете.\n\nВопрос: Как отозвать API-ключ?"
}
]
}'Контекст вставляет ваше приложение, а не шлюз. Передавайте вместе с текстом устойчивые ID или внутренние ссылки на источники. Инструкция модели должна запрещать добавлять сведения вне контекста и явно разрешать ответ «данных недостаточно». Это не устраняет ошибки полностью, но делает ответ проверяемее и снижает риск необоснованных утверждений.
Полный пример
Ниже — самодостаточный учебный пример на стандартной библиотеке Python. Он получает векторы документов и вопроса, считает косинусное сходство в памяти, выбирает фрагменты и отправляет их на генерацию. Для рабочего приложения замените список в памяти на собственное хранилище и добавьте проверку прав доступа.
Перед запуском задайте AI_GATEWAY_API_KEY, EMBEDDING_REQUEST_MODEL_ID и CHAT_REQUEST_MODEL_ID. Каждый идентификатор скопируйте из строки соответствующего пути в Моделях.
import json
import math
import os
import urllib.request
BASE_URL = "https://route.smartaipack.ru/v1"
API_KEY = os.environ["AI_GATEWAY_API_KEY"]
EMBEDDING_MODEL_ID = os.environ["EMBEDDING_REQUEST_MODEL_ID"]
CHAT_MODEL_ID = os.environ["CHAT_REQUEST_MODEL_ID"]
documents = [
{
"id": "doc-17",
"source": "/support",
"text": "Ключ можно отозвать в личном кабинете.",
},
{
"id": "doc-23",
"source": "/dashboard/analytics",
"text": "Расход по моделям отображается в аналитике.",
},
{
"id": "doc-41",
"source": "/pricing",
"text": "Актуальные цены опубликованы на странице тарифов.",
},
]
def post(path, body):
request = urllib.request.Request(
BASE_URL + path,
data=json.dumps(body).encode("utf-8"),
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=60) as response:
return json.load(response)
def cosine(left, right):
numerator = sum(a * b for a, b in zip(left, right))
left_norm = math.sqrt(sum(value * value for value in left))
right_norm = math.sqrt(sum(value * value for value in right))
if left_norm == 0 or right_norm == 0:
return 0.0
return numerator / (left_norm * right_norm)
document_result = post(
"/embeddings",
{
"model": EMBEDDING_MODEL_ID,
"input": [document["text"] for document in documents],
},
)
document_vectors = [item["embedding"] for item in document_result["data"]]
question = "Где посмотреть расход по моделям?"
query_result = post(
"/embeddings",
{"model": EMBEDDING_MODEL_ID, "input": question},
)
query_vector = query_result["data"][0]["embedding"]
ranked = sorted(
zip(documents, document_vectors),
key=lambda pair: cosine(query_vector, pair[1]),
reverse=True,
)
selected = [document for document, _vector in ranked[:2]]
context = "\n\n".join(
f'[{item["id"]}] {item["text"]} Источник: {item["source"]}'
for item in selected
)
chat_result = post(
"/chat/completions",
{
"model": CHAT_MODEL_ID,
"messages": [
{
"role": "system",
"content": (
"Отвечай только по контексту. Если данных недостаточно, "
"сообщи об этом. В конце перечисли ID источников."
),
},
{
"role": "user",
"content": f"Контекст:\n{context}\n\nВопрос: {question}",
},
],
},
)
print(chat_result["choices"][0]["message"]["content"])Чтобы добавить пересортировку, передайте тексты из ranked в /v1/rerank с отдельным RERANK_REQUEST_MODEL_ID. Сначала сохраните обезличенный проверочный ответ выбранной модели, затем напишите небольшой преобразователь, который возвращает документы в новом порядке. Сам пример намеренно не угадывает структуру ответа этого пути.
Когда нужна пересортировка
Рассмотрите дополнительный этап, если:
- векторный поиск часто поднимает наверх похожие по теме, но бесполезные фрагменты;
- корпус и число кандидатов выросли, а точность верхних результатов снизилась;
- цена неточного ответа выше дополнительной задержки;
- проверочный набор показывает измеримое улучшение после пересортировки.
Оставьте только векторный поиск, если качество уже устраивает, важна минимальная задержка или вы проверяете первую версию решения. Универсального числа кандидатов нет: подберите его на собственном наборе вопросов и ожидаемых источников.
Стратегии разбиения
Качество поиска во многом определяется границами фрагментов. Практичные варианты:
- по заголовкам и разделам — сохраняет структуру инструкции;
- по абзацам и предложениям — подходит для коротких справочных материалов;
- по смысловым границам — полезно для документов с неравномерными разделами;
- с перекрытием соседних частей — помогает не потерять условие на границе, но увеличивает объём индекса и число дублей.
Не выбирайте один фиксированный размер для всех данных. Сравните несколько схем на реальных вопросах: нашёлся ли нужный источник, достаточно ли в фрагменте контекста и не вытесняют ли дубли более полезные результаты.
Рекомендации
- Соберите проверочный набор из вопросов, ожидаемых документов и допустимых ответов до настройки порогов.
- Храните ID источника, версию текста и модель векторизации рядом с каждым вектором.
- Переиндексируйте изменившиеся документы, а при смене модели векторизации — весь индекс.
- Сначала фильтруйте документы по правам доступа, затем выполняйте смысловой поиск.
- Не отправляйте в модель секреты, персональные данные и документы, к которым у пользователя нет доступа.
- Ограничивайте объём контекста и убирайте дублирующиеся фрагменты.
- Разрешайте модели сообщить о нехватке данных и сохраняйте ID источников в итоговом ответе.
- Сравнивайте качество, задержку и стоимость варианта с пересортировкой и без неё.
- Проверяйте отдельные вызовы в Использовании, а итоговые расходы — в Аналитике.
Доступные модели
Не закрепляйте текущие названия моделей в коде или документации приложения. Для каждого этапа откройте Модели и выберите строку с нужным опубликованным путём:
| Этап | Путь | Какое значение передать в model |
|---|---|---|
| Векторизация документов и вопросов | /v1/embeddings | Точный request_model_id этой строки; один и тот же для индекса и вопросов |
| Пересортировка | /v1/rerank | Отдельный точный request_model_id строки пересортировки |
| Генерация ответа | /v1/chat/completions | Отдельный точный request_model_id строки генерации |
Перед рабочей нагрузкой отправьте короткий обезличенный запрос на каждый выбранный путь. Текущие цены смотрите на странице цен, а ограничения — в руководстве по лимитам.
Связанные материалы
- Векторные представления через AI Gateway — формат векторизации и учебный смысловой поиск.
- Справочник API — адреса, заголовки и поддерживаемые пути.
- Ошибки API — обработка кодов и повторов.
- Лимиты API — как ограничивать нагрузку и повторять временные ошибки.
- OpenAI SDK со AI Gateway — подключение совместимого клиента.
- Как выбирать модели — работа с каталогом и точными идентификаторами.
- Поддержка — помощь с запросом без передачи полного API-ключа.