RAG-пайплайн на своих данных: минимальная схема

RAG не обязан начинаться с векторной базы. Разбираем минимальную схему: когда достаточно длинного контекста, а когда нужен полноценный retrieval.

5 мин · · Обновлено · разработка · rag

RAG обычно объясняют через связку «эмбеддинги → векторная база → топ-к чанков → генерация», и половина статей сразу начинается с выбора векторной БД. На практике для небольшого и среднего корпуса документов эта связка часто избыточна, а шлюз zukko эмбеддинги не отдаёт — здесь только генерация текста и веб-поиск. Разберём, что из этого реально собирается и когда векторный поиск всё-таки нужен.

Когда векторная база не нужна вообще

У моделей Claude и GPT в каталоге контекст 1,000,000 токенов (у glm-5.3 и glm-5.3-flash — 128K). Это примерно 700-800 тысяч слов — целая база знаний среднего размера компании, набор внутренней документации или сотня договоров помещаются в один запрос без всякого retrieval. Если корпус укладывается в такой объём, минимальная схема RAG — это не пайплайн, а один системный промпт:

bash
curl https://api.zukko.pro/v1/messages \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 1500,
    "system": "'"$(cat knowledge_base.md | jq -Rs .)"'",
    "messages": [{"role": "user", "content": "Как оформить возврат оборудования по договору аренды?"}]
  }'

Перед тем как городить векторную базу, стоит посчитать реальный объём данных через бесплатный подсчёт токенов — часто оказывается, что корпус заметно меньше лимита контекста:

bash
curl https://api.zukko.pro/v1/messages/count_tokens \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-opus-5-5", "messages": [{"role": "user", "content": "'"$(cat knowledge_base.md | jq -Rs .)"'"}]}'

Учтите цену такой схемы: вся база знаний оплачивается как входные токены в каждом запросе. У вендоров повторное чтение из кэша промптов стоит ~10% цены входа, а в Zukko кэшированные токены идут по обычной цене — для сценариев с большим повторяющимся контекстом реальная разница в цене меньше, чем при сравнении прайсов «в лоб».

Когда retrieval всё-таки нужен

Если корпус растёт быстрее контекста — сотни тысяч документов, логи, тикеты за годы — засовывать всё в промпт перестаёт работать: растёт цена запроса и модель хуже фокусируется на релевантном фрагменте среди шума. Здесь нужен классический retrieval: эмбеддинги считаются любым отдельным сервисом эмбеддингов (шлюз zukko их не предоставляет — это чисто генеративный API), результаты хранятся в любой векторной базе на ваш выбор, а сам шлюз используется только на этапе генерации ответа по найденным чанкам:

bash
import requests

# retrieval - свой векторный поиск, вне zukko
relevant_chunks = vector_search(query, top_k=8)
context = "\n\n---\n\n".join(chunk.text for chunk in relevant_chunks)

resp = requests.post(
    "https://api.zukko.pro/v1/messages",
    headers={
        "Authorization": "Bearer fetch_ваш_ключ",
        "anthropic-version": "2023-06-01",
    },
    json={
        "model": "claude-sonnet-5",
        "max_tokens": 1000,
        "system": f"Отвечай только на основе этих фрагментов:\n\n{context}",
        "messages": [{"role": "user", "content": query}],
    },
)

Разделение чёткое: retrieval — ваша инфраструктура, генерация — вызов /v1/messages или /v1/chat/completions.

Гибридная схема: tool calling вместо жёсткого пайплайна

Вместо того чтобы всегда сначала искать, а потом генерировать, можно отдать модели решение о том, нужен ли retrieval вообще, через /v1/responses с описанием инструмента поиска:

bash
curl https://api.zukko.pro/v1/responses \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-sol",
    "input": "Сколько у нас открытых договоров с автопродлением на 2027 год?",
    "tools": [{
      "type": "function",
      "name": "search_documents",
      "description": "Ищет релевантные фрагменты во внутренней базе документов",
      "parameters": {"type": "object", "properties": {"query": {"type": "string"}}}
    }]
  }'

Модель сама решает, вызывать ли search_documents (ваша функция retrieval на стороне приложения) или отвечать напрямую, если вопрос не требует обращения к базе. /v1/responses принимает и строку, и массив сообщений, и список инструментов, что удобно для такого агентного шаблона.

Веб-поиск как источник для внешнего контекста

Если часть RAG-системы должна отвечать на вопросы про внешний мир, а не только про внутренние документы — например, «сравни наши условия с рыночными» — для моделей без встроенного поиска есть отдельный вызов:

bash
curl https://api.zukko.pro/v1/web-search \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{"query": "средние ставки аренды коммерческой недвижимости 2026"}'

Результат подмешивается в контекст следующего запроса тем же способом, что и внутренние чанки — с точки зрения генерации разницы между «внутренний retrieval» и «веб-поиск» нет, оба просто дают текст в system или user-сообщение.

Кэш для повторяющихся вопросов

В RAG для поддержки или базы знаний одни и те же вопросы задают многократно. Если ответ должен быть детерминированным при одинаковом наборе найденных чанков, есть смысл ставить заголовок X-Zukko-Cache: true (temperature: 0 тоже включает кэш, но только у моделей, которые принимают этот параметр; Claude-модели — claude-sonnet-5, claude-opus-5-5 — могут отклонить temperature, отличную от 1, с ошибкой 400, поэтому им его не передавайте) — повторный одинаковый запрос от того же пользователя вернётся из кэша мгновенно и бесплатно (X-Zukko-Cache: hit в ответе), кэш для чата живёт до 7 суток. Важно: кэш срабатывает только при полном совпадении тела запроса — и формулировки вопроса, и набора чанков, и параметров; «почти одинаковые» вопросы в него не попадают, а сами попадания не пишутся в /v1/usage. Экономия появляется там, где популярный вопрос приходит в той же формулировке и retrieval возвращает тот же набор чанков день за днём.

Оценка стоимости и выбор модели по этапу

Для этапа генерации ответа по найденным чанкам обычно пробуют claude-sonnet-5 или gpt-5.6-sol — задача не требует расширенного рассуждения, если чанки уже релевантны; для простых выборок цитат можно проверить и более дешёвые gpt-5.6-luna или glm-5.3 ($0.10/$0.10). claude-opus-5-5 имеет смысл пробовать там, где ответ требует сопоставления нескольких документов и явного вывода (например, «есть ли противоречие между пунктом 4.2 договора А и пунктом 7.1 договора Б») — по цене в Zukko он не отличается от Sonnet 5 и Sol ($0.30/$0.30), так что проверьте на своих задачах, какая модель даёт результат лучше.

Ошибки, характерные для RAG-пайплайна

404 model_not_found в тестах часто всплывает, если в конфиге агента остался старый id модели после миграции (а модель, выключенная в каталоге, отвечает 503 model_disabled) — стоит сверяться со свежим списком через GET /v1/models, он публичный и не требует ключа. 422 idempotency_conflict встречается, если retry-логика пайплайна переиспользует один Idempotency-Key для запросов с разным набором найденных чанков — ключ должен зависеть не только от вопроса пользователя, но и от версии контекста, который в него подставили. Обрыв ответа на середине генерации почти всегда означает нехватку max_tokens, а не проблему с retrieval — если модель должна процитировать несколько длинных фрагментов, стоит закладывать больше токенов на выход, чем для короткого ответа на прямой вопрос.

Итог: для небольшого корпуса RAG — это системный промпт и большой контекст, без единого дополнительного сервиса. Для растущего корпуса — свой retrieval плюс генерация через API, где выбор модели зависит от того, нужен ли просто пересказ найденного или сопоставление нескольких источников.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.