Регистрация

Эмбеддинги

Векторные представления текста для поиска, кластеризации и классификации. Тот же ключ, тот же base URL, формат ответа — как у OpenAI.

Запрос: POST /embeddings

Эндпоинт принимает одну строку или массив строк и возвращает по вектору на каждую. Стриминга здесь нет — ответ приходит целиком. Модель обязательно должна быть эмбеддинговой: обычная чат-модель вернёт 404 с типом model_not_found.

Параметр Тип Обяз. Описание
model string да Идентификатор модели с тегом «эмбеддинги» из каталога, вместе с вендором.
input string | string[] да Строка или массив строк. До 2 048 строк в одном запросе, каждая — не длиннее контекста модели.
encoding_format string нет float — массив чисел, значение по умолчанию. base64 — тот же вектор строкой, ответ примерно втрое короче.
dimensions integer нет Усечение вектора до нужной размерности. Работает только у моделей, которые это поддерживают.

Примеры

Самый короткий вариант — одна строка на curl. Ответ вернёт массив data из одного элемента.

bash · curl
curl https://api.tokendock.cloud/v1/embeddings \
  -H "Authorization: Bearer $TOKENDOCK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/text-embedding-3-large",
    "input": "Стоимость запроса считается по токенам входа и выхода."
  }'

Батч из нескольких строк — тот же запрос, но input становится массивом. Один запрос на сотню чанков дешевле по накладным расходам и укладывается в лимит RPM, где сотня отдельных запросов уже нет.

bash · curl, батч
curl https://api.tokendock.cloud/v1/embeddings \
  -H "Authorization: Bearer $TOKENDOCK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/text-embedding-3-large",
    "input": [
      "Пополнение баланса картой российского банка проходит мгновенно.",
      "Секрет ключа показывается один раз — дальше хранится только его хеш.",
      "Доступ к каталогу моделей работает из России без VPN."
    ]
  }'
python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.tokendock.cloud/v1",
    api_key=os.environ["TOKENDOCK_API_KEY"],
)

chunks = [
    "Пополнение баланса картой российского банка проходит мгновенно.",
    "Секрет ключа показывается один раз — дальше хранится только его хеш.",
    "Доступ к каталогу моделей работает из России без VPN.",
]

response = client.embeddings.create(
    model="openai/text-embedding-3-large",
    input=chunks,
    dimensions=1024,
)

# Порядок в data совпадает с порядком input, но надёжнее опираться на index
vectors = [item.embedding for item in sorted(response.data, key=lambda x: x.index)]

print(len(vectors), len(vectors[0]))   # 3 1024
print(response.usage.prompt_tokens)
javascript · node.js
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.tokendock.cloud/v1",
  apiKey: process.env.TOKENDOCK_API_KEY,
});

const chunks = [
  "Пополнение баланса картой российского банка проходит мгновенно.",
  "Секрет ключа показывается один раз — дальше хранится только его хеш.",
  "Доступ к каталогу моделей работает из России без VPN.",
];

const response = await client.embeddings.create({
  model: "mistral/mistral-embed",
  input: chunks,
});

const vectors = response.data
  .sort((a, b) => a.index - b.index)
  .map((item) => item.embedding);

console.log(vectors.length, vectors[0].length);
console.log(response.usage);

В SDK от OpenAI метод называется embeddings.create и работает без правок — меняется только base_url. То же справедливо для LangChain и LlamaIndex: там достаточно указать адрес TokenDock в настройках OpenAI-провайдера.

Ответ

Ответ на батч из трёх строк. Векторы в примере усечены до нескольких значений — у openai/text-embedding-3-large их 3 072, а с параметром dimensions ровно столько, сколько запрошено.

json · ответ
{
  "object": "list",
  "model": "openai/text-embedding-3-large",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [-0.01423, 0.03118, -0.00784, 0.02641, "…", -0.00915]
    },
    {
      "object": "embedding",
      "index": 1,
      "embedding": [0.00672, -0.02037, 0.04152, -0.00389, "…", 0.01226]
    },
    {
      "object": "embedding",
      "index": 2,
      "embedding": [-0.00318, 0.01894, 0.00507, -0.02760, "…", 0.00841]
    }
  ],
  "usage": {
    "prompt_tokens": 62,
    "total_tokens": 62
  }
}
  • index — позиция строки во входном массиве. Порядок элементов data обычно совпадает с порядком input, но сортировка по index надёжнее.
  • usage.prompt_tokens — сумма токенов всех строк батча. Выходных токенов у эмбеддингов нет, поэтому completion_tokens в ответе отсутствует.
  • Если хотя бы одна строка длиннее контекста модели, весь запрос отклоняется с 422. Частичного результата не бывает — режьте текст до отправки.

Какие модели доступны

В каталоге две эмбеддинговые модели. Цена указана за 1M входных токенов.

id модели Вендор Контекст Размерность Цена, ₽ / 1M dimensions
openai/text-embedding-3-large OpenAI 8 191 3 072 11 поддерживается
mistral/mistral-embed Mistral 8 192 1 024 8 не поддерживается
  • text-embedding-3-large — качество выше, вектор длиннее, размерность можно уменьшить параметром dimensions без переиндексации на другой модели.
  • Mistral Embed — дешевле и компактнее: 1 024 значения на вектор, меньше места в индексе и быстрее поиск.
  • Смена модели означает полную переиндексацию. Векторы разных моделей несопоставимы, даже если совпала размерность.
Смотреть каталог и цены

Тарификация

У эмбеддингов считаются только входные токены. Цена выхода в каталоге равна нулю — вектор не тарифицируется, сколько бы значений в нём ни было. Параметр dimensions на стоимость не влияет: он меняет размер ответа, а не объём работы модели.

  • Индексация корпуса на 4M токенов через openai/text-embedding-3-large44 ₽: 4 × 11 ₽ за миллион.
  • Тот же корпус на mistral/mistral-embed32 ₽.
  • Запрос, отклонённый до модели — 400, 422, 429 — не тарифицируется.
Как считается стоимость

Практика

Нормализация

Модели отдают векторы единичной длины, поэтому косинусное сходство совпадает со скалярным произведением — это экономит операцию на каждом сравнении. Но как только вы усекли вектор через dimensions, норма перестаёт быть единицей: нормализуйте результат сами, иначе ранжирование поедет.

python · нормализация
import numpy as np

vectors = np.array(vectors, dtype=np.float32)

# После усечения через dimensions норма ломается — вернём её к единице
vectors /= np.linalg.norm(vectors, axis=1, keepdims=True)

# На единичных векторах косинус — это обычное скалярное произведение
similarity = vectors @ vectors.T

Храните векторы в float32 — точности достаточно, а памяти вдвое меньше, чем у float64. Если индекс большой, encoding_format: "base64" заметно сокращает трафик на приёме.

Размер батча

  • Технический потолок — 2 048 строк на запрос, практический ориентир — от 96 до 256 строк.
  • Батч тратит токены сразу за все строки, поэтому в лимит TPM он упирается раньше, чем в RPM. Считайте бюджет по токенам, а не по количеству запросов — цифры в разделе лимиты и квоты.
  • Ошибка на батче отменяет весь батч. Для больших прогонов держите очередь с повтором по 429 и 5xx и уменьшайте пачку при повторе.
  • Индексацию корпуса выгоднее гнать в 2–4 параллельных потока с бэкоффом, чем в один поток без пауз — так лимиты расходуются ровнее.

Длинные тексты

Контекст эмбеддинговых моделей — около 8 000 токенов, и это не повод отправлять документ целиком. Один вектор на длинный текст размывает смысл: поиск начнёт находить документ «вообще про то же», но не тот фрагмент, который нужен.

  • Режьте текст на чанки по 200–500 токенов с перекрытием 10–15% — так граница чанка не разрывает мысль.
  • Режьте по структуре: абзацы, заголовки, элементы списка. Разрез по символам ломает смысл чаще, чем экономит место.
  • Храните рядом с вектором ссылку на документ и позицию чанка — выдача должна вести к исходному тексту.
  • Усреднять векторы чанков в один «вектор документа» можно для грубой фильтрации, но искать всё равно лучше по чанкам.

Эмбеддинги детерминированы: одна и та же строка на одной и той же модели даёт один и тот же вектор. Кэшируйте результат по хешу текста — на повторной индексации это экономит и время, и деньги.