Руководства

RAG

Эмбеддинги, rerank и чат одним ключом. Модель отвечает по вашей базе, а не только по своим весам.

RAG (Retrieval-Augmented Generation) подставляет в контекст модели фрагменты вашей базы знаний. Ответ опирается на найденные документы: меньше выдумок, данные можно обновлять без дообучения.

Три эндпоинта AITUNNEL закрывают весь пайплайн:

  1. Эмбеддинги POST /embeddings: векторы документов и запроса
  2. Ранжирование POST /rerank: точный скоринг кандидатов
  3. Чат — POST /chat/completions: ответ по найденному контексту

Как устроен пайплайн

  1. Индекс

    Режете документы на чанки и считаете эмбеддинги.

  2. Поиск

    Эмбеддинг запроса, ближайшие чанки по косинусу.

  3. Rerank

    По желанию: кросс-энкодер оставляет лучшие 3–5. Можно пропустить.

  4. Генерация

    Эти фрагменты уходят в чат как контекст.

Эмбеддинг запросавектор из вопроса
Векторный поисктоп 10–20 похожих чанков
Rerankточная оценка, топ 3–5
Ответ с контекстомчат отвечает по найденному

Шаг 1. Индекс

Режьте документы на чанки и отправляйте их пакетом в /embeddings. Для прототипа векторы можно держать в памяти; в проде — в векторной базе.

curl https://api.aitunnel.ru/v1/embeddings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -d '{
    "model": "text-embedding-3-small",
    "input": [
      "AITUNNEL — единый OpenAI-совместимый API к GPT, Claude, Gemini и другим.",
      "RAG — Retrieval-Augmented Generation: модель отвечает по найденным фрагментам вашей базы.",
      "Эмбеддинги превращают текст в вектор."
    ]
  }'

Векторная база

В проде храните эмбеддинги в Qdrant, pgvector, Weaviate, Pinecone и подобных. Пример выше — только иллюстрация.

Много однотипных чанков без картинок можно прогнать через Batch запросы, если у модели в каталоге есть batch.

Шаг 2. Поиск

Эмбеддинг запроса и косинус с каждым чанком. Сравнивайте косинусом, не евклидовым расстоянием.

import numpy as np

def cosine(a, b):
    a, b = np.array(a), np.array(b)
    return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))

def retrieve(query, documents, top_n=5):
    query_vec = client.embeddings.create(
        model="text-embedding-3-small",
        input=query,
    ).data[0].embedding
    scored = [
        {"text": doc["text"], "score": cosine(query_vec, doc["embedding"])}
        for doc in documents
    ]
    scored.sort(key=lambda item: item["score"], reverse=True)
    return scored[:top_n]

query = "Как RAG улучшает ответы модели?"
results = retrieve(query, documents, top_n=5)
for i, item in enumerate(results, 1):
    print(f"{i}. {item['score']:.4f}  {item['text']}")

Та же модель

Тот же model, что при индексации. Смешивать модели нельзя: пространства векторов несовместимы.

Шаг 3. Rerank

Косинус по эмбеддингам быстрый и приблизительный. Rerank смотрит на пару «запрос + документ» и ставит более точную оценку. Имеет смысл, когда кандидатов много (например 20) и нужны лучшие 3.

В OpenAI SDK метода rerank нет — обычный HTTP. Подробнее — ранжирование.

curl https://api.aitunnel.ru/v1/rerank \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -d '{
    "model": "rerank-4-pro",
    "query": "Как RAG улучшает ответы модели?",
    "documents": [
      "RAG — Retrieval-Augmented Generation: модель отвечает по найденным фрагментам вашей базы, а не только по своим весам.",
      "Эмбеддинги превращают текст в вектор. Похожие по смыслу фрагменты оказываются рядом.",
      "Rerank сравнивает запрос с каждым документом напрямую и ставит более точные оценки, чем косинус эмбеддингов.",
      "AITUNNEL — единый OpenAI-совместимый API к GPT, Claude, Gemini и другим. Один ключ, оплата в рублях.",
      "Векторные базы вроде Qdrant, pgvector и Weaviate хранят эмбеддинги и ищут ближайших соседей."
    ],
    "top_n": 3
  }'

Картинки в rerank

Часть моделей принимает не только строки, но и объекты { "text", "image" }. image — URL или data-URI. Так можно ранжировать схемы и скриншоты, у которых смысл не в подписи. В каталоге AITUNNEL сейчас текстовые rerank-модели; мультимодальные вызываются слагом OpenRouter, если у модели во входе есть image.

Строки и объекты можно смешивать в одном documents.

Шаг 4. Ответ по контексту

Топ после rerank (или сразу после поиска) — в системный или пользовательский промпт. Просите цитировать источники и молчать, если контекста мало.

def generate(query, context_docs):
    context = "\n\n".join(
        f"[{i}] {item['document']['text']}"
        for i, item in enumerate(context_docs, 1)
    )
    answer = client.chat.completions.create(
        model="gpt-5-nano",
        messages=[
            {
                "role": "system",
                "content": (
                    "Отвечай только по контексту. Ссылайся на источники как [n]. "
                    "Если в контексте нет ответа — так и скажи."
                ),
            },
            {
                "role": "user",
                "content": f"Контекст:\n{context}\n\nВопрос: {query}",
            },
        ],
    )
    return answer.choices[0].message.content

print(generate(query, ranked))

Повторяющийся системный префикс («отвечай только по контексту, цитируй [n]») стоит держать стабильным — его подхватит кеш промпта.

Полный пример

Все четыре шага подряд. Эмбеддинги и чат — через OpenAI SDK, rerank — HTTP.

import numpy as np
import requests
from openai import OpenAI

client = OpenAI(
    api_key="sk-aitunnel-xxx",
    base_url="https://api.aitunnel.ru/v1/",
)
EMBED_MODEL = "text-embedding-3-small"
RERANK_MODEL = "rerank-4-pro"
CHAT_MODEL = "gpt-5-nano"

def cosine(a, b):
    a, b = np.array(a), np.array(b)
    return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))

def embed(texts):
    return [
        item.embedding
        for item in client.embeddings.create(model=EMBED_MODEL, input=texts).data
    ]

def retrieve(query_vec, doc_vecs, top_n=5):
    scored = [(i, cosine(query_vec, vec)) for i, vec in enumerate(doc_vecs)]
    scored.sort(key=lambda item: item[1], reverse=True)
    return scored[:top_n]

def rerank(query, documents, top_n=3):
    response = requests.post(
        "https://api.aitunnel.ru/v1/rerank",
        headers={"Authorization": "Bearer sk-aitunnel-xxx"},
        json={
            "model": RERANK_MODEL,
            "query": query,
            "documents": documents,
            "top_n": top_n,
        },
    )
    response.raise_for_status()
    return response.json()["results"]

def generate(query, context_docs):
    context = "\n\n".join(f"[{i}] {text}" for i, text in enumerate(context_docs, 1))
    answer = client.chat.completions.create(
        model=CHAT_MODEL,
        messages=[
            {
                "role": "system",
                "content": (
                    "Отвечай только по контексту. Ссылайся на источники как [n]. "
                    "Если в контексте нет ответа — так и скажи."
                ),
            },
            {
                "role": "user",
                "content": f"Контекст:\n{context}\n\nВопрос: {query}",
            },
        ],
    )
    return answer.choices[0].message.content

chunks = [
          "AITUNNEL — единый OpenAI-совместимый API к GPT, Claude, Gemini и другим. Один ключ, оплата в рублях.",
          "RAG — Retrieval-Augmented Generation: модель отвечает по найденным фрагментам вашей базы, а не только по своим весам.",
          "Эмбеддинги превращают текст в вектор. Похожие по смыслу фрагменты оказываются рядом.",
          "Rerank сравнивает запрос с каждым документом напрямую и ставит более точные оценки, чем косинус эмбеддингов.",
          "Векторные базы вроде Qdrant, pgvector и Weaviate хранят эмбеддинги и ищут ближайших соседей.",
          "Кеш промпта удешевляет повтор одного и того же префикса — например системные правила цитирования.",
          "Имя модели без префикса провайдера берёт цену из каталога AITUNNEL. Слаг provider/model уходит в OpenRouter."
        ]

# 1. Индекс
doc_vecs = embed(chunks)

# 2. Поиск
query = "Как RAG улучшает ответы модели?"
matches = retrieve(embed([query])[0], doc_vecs, top_n=5)
retrieved = [chunks[i] for i, _ in matches]

# 3. Rerank
ranked = rerank(query, retrieved, top_n=3)
context = [item["document"]["text"] for item in ranked]

# 4. Ответ
print(generate(query, context))

Когда нужен rerank

Rerank — отдельный запрос. Имеет смысл, когда:

  • база большая, косинус возвращает шум
  • важна точность (клиентский FAQ, юридические или медицинские тексты)
  • вы берёте 10–20 кандидатов и оставляете 3–5

Можно обойтись без него, когда:

  • база маленькая и косинус уже попадает
  • важна минимальная задержка
  • вы только собираете прототип

Нарезка документов

От нарезки зависит качество поиска не меньше, чем от модели:

  • По абзацам и заголовкам — сохраняет смысл, хорошо для структурированных текстов
  • Фиксированный размер с перекрытием — 200–500 токенов и ~50 токенов overlap, чтобы мысль не обрывалась на границе
  • По смысловым границам — предложения, секции, разрывы глав

Размер чанка

Мелкие чанки (200–300 токенов) точнее попадают в запрос, но теряют окружение. Крупные (500–1000) держат контекст, но размывают сигнал. Подбирайте на своих данных.

Практика

  • Одна и та же модель эмбеддингов для индекса и запроса.
  • Пакет в input, не по одному тексту.
  • Кешируйте векторы — повтор того же текста даёт тот же вектор.
  • Сначала широко, потом rerank: 10–20 кандидатов косинусом, 3–5 после rerank.
  • Метаданные в промпт: заголовок, раздел, URL рядом с текстом — так проще цитировать.
  • Порог релевантности: после rerank отбрасывайте слабые документы, чтобы не путать модель.
  • Имена моделей без префикса провайдера: text-embedding-3-small, rerank-4-pro, gpt-5-nano. Слаг provider/model OpenRouter.

Модели

Актуальные списки без ключа:

cURL
curl https://api.aitunnel.ru/public/aitunnel/models/embeddings
curl https://api.aitunnel.ru/public/aitunnel/models/rerank
curl https://api.aitunnel.ru/public/aitunnel/models/chat

На сайте: каталог. Фильтры по выходной модальности «Эмбеддинг» и «Ранжирование».

Смотрите также