Embedding API не генерирует текст, а отображает его в последовательность чисел. На этих числах работают семантический поиск, RAG, кластеризация, дедупликация и рекомендации. Само подключение несложное; сложности возникают в размерности, пакетной обработке, нормализации и индексации — ошибка в этих деталях незаметно ухудшает поиск.
Ниже на примере OpenAI-совместимого интерфейса разобраны запрос и ответ, вызовы на Python и curl, стратегия пакетов, хранение векторов, сборка RAG, типичные ошибки и проверка при миграции.
Примечание: OpenAI, PostgreSQL и другие упомянутые бренды — товарные знаки их правообладателей. Статья носит технический характер и не связана с этими компаниями; интерфейсы, модели и цены — по официальной документации и страницам сайта.
1. Интерфейс и аутентификация
OpenAI-совместимый embedding-интерфейс выглядит так:
POST https://ai.zhongshangqihang.com/v1/embeddings
Authorization: Bearer sk-ваш-ключ
Content-Type: application/json
Base URL указывается только до /v1, не добавляйте /embeddings самостоятельно перед передачей в SDK. SDK добавит путь по методу, а двойное склеивание даст 404.
В теле запроса достаточно двух полей:
{
"model": "text-embedding-3-small",
"input": ["кто оплачивает доставку при возврате", "кто платит за логистику возврата"]
}
input может быть строкой или массивом строк. Массив возвращает несколько векторов в том же порядке и подходит для пакетной обработки. Пустые строки обычно отклоняются, их нужно отфильтровать на этапе очистки.
2. Поля запроса и ответа
| Поле | Где | Описание |
|---|---|---|
model |
Запрос | Обязательно; имя модели — по документации платформы и тарифам |
input |
Запрос | Строка или массив строк; одна запись не должна превышать лимит модели |
dimensions |
Запрос | Поддерживается не всеми моделями; сокращает размерность выхода |
encoding_format |
Запрос | Обычно float, также возможен base64 для уменьшения объёма передачи |
data[].embedding |
Ответ | Массив вектора, порядок соответствует входу |
data[].index |
Ответ | Индекс входа в массиве |
usage |
Ответ | prompt_tokens и total_tokens для сверки |
Поле index в ответе важно. При повторе пакетного запроса или объединении параллельных вызовов не предполагайте, что порядок ответа всегда совпадает с входом; выравнивание по index надёжнее.
3. Вызов на Python и похожесть
После установки SDK минимальный пример выглядит так:
import os
from math import sqrt
from openai import OpenAI
client = OpenAI(
api_key=os.environ["ZSQH_API_KEY"],
base_url="https://ai.zhongshangqihang.com/v1",
)
resp = client.embeddings.create(
model="text-embedding-3-small",
input=["кто оплачивает доставку при возврате", "кто платит за логистику возврата"],
)
a, b = resp.data[0].embedding, resp.data[1].embedding
cos = sum(x * y for x, y in zip(a, b)) / (
sqrt(sum(x * x for x in a)) * sqrt(sum(y * y for y in b))
)
print(len(a), round(cos, 4), resp.usage.total_tokens)
Этот код одновременно проверяет три вещи: ключ работает, размерность соответствует ожиданию, похожесть двух близких текстов разумна. В рабочем проекте используйте NumPy или функцию расстояния из векторной базы, а не чистый Python на больших объёмах.
4. Размерность, нормализация и согласованность модели
Размерности распространённых моделей OpenAI embedding:
text-embedding-3-small: 1536 измерений.text-embedding-3-large: 3072 измерения.text-embedding-ada-002: 1536 измерений.
Параметр dimensions позволяет сократить выход семейства text-embedding-3, например с 3072 до 1024, чтобы сэкономить место и трафик. Сокращение теряет часть информации; стоит ли это делать, проверяйте на своём оценочном наборе, а не только по числу измерений.
Документация OpenAI утверждает, что её embedding нормализованы до длины 1, поэтому косинусную похожесть можно считать через скалярное произведение. Но если вы используете собственную модель, стороннюю модель или вручную усекли размерность, нужна повторная L2-нормализация. Надёжнее всего использовать одну и ту же логику нормализации при записи и при запросе и явно описать её в коде.
Есть жёсткое ограничение: все векторы в векторной базе должны быть получены одной моделью, с одной размерностью и одной нормализацией. После смены модели старые векторы нельзя смешивать с новыми, иначе похожесть теряет смысл.
5. Как делить пакетные запросы
Пакеты сокращают накладные расходы HTTP, но чем больше запрос, тем не лучше. Текущая документация OpenAI указывает границы: одна запись не более примерно 8K токенов, сумма всех входов одного запроса не более 300 000 токенов; точные лимиты — по документации поставщика.
На практике пакеты делят по бюджету токенов, а не по фиксированному числу строк:
- Очистите текст, удалите пробелы, дубли и слишком длинные абзацы.
- Оцените токены каждого фрагмента токенизатором, длинные тексты сначала нарежьте.
- Группируйте по общему бюджету токенов, например по несколько десятков тысяч на пакет, оставляя запас на повторы.
- Для 429 и 5xx используйте экспоненциальную задержку; ошибки параметров 4xx не повторяйте слепо.
- Записывайте для каждого пакета модель, размерность, токены и длительность — для сверки и воспроизведения.
Повторы должны быть идемпотентными. В качестве ключа кэша можно использовать «хеш текста + имя модели + размерность», чтобы не оплачивать один и тот же текст повторно. После сбоя пакетной задачи сначала определите, какие index уже обработаны, и дополните только недостающие.
6. Сохранение в векторной базе
На примере pgvector в PostgreSQL структура таблицы может быть такой:
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
content text NOT NULL,
source text,
embedding vector(1536)
);
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);
Запрос по косинусному расстоянию:
SELECT id, content
FROM chunks
ORDER BY embedding <=> $1
LIMIT 5;
Помимо вектора, нужно хранить как минимум: исходный текст, источник, арендатора или область прав, позицию фрагмента, имя модели и размерность. Без этих метаданных ответ не сможет сослаться на источник и не получится фильтрация по правам. В продакшене также нужны тип индекса, параметры поиска, резервное копирование и инкрементальное обновление — не откладывайте это до роста данных.
7. Порядок сборки RAG
Рабочий процесс RAG обычно выглядит так:
- Разбор и очистка документов, удаление колонтитулов и повторов.
- Нарезка по заголовкам или абзацам с небольшим перекрытием между фрагментами.
- Вызов embedding для каждого фрагмента и пакетная запись в векторную базу.
- При вопросе пользователя векторизация вопроса той же моделью.
- Поиск Top-K по косинусной похожести, затем фильтрация по правам и источнику.
- При необходимости — ключевой поиск для гибридного отбора или модель реранкинга.
- Передача найденных фрагментов и вопроса чат-модели с требованием ссылаться на источники.
Единого ответа по размеру фрагмента нет. Для китайских документов частая отправная точка — 300–800 иероглифов, затем корректировка по качеству ответов. Слишком крупный фрагмент снижает точность поиска, слишком мелкий теряет контекст. Вместо споров о параметрах лучше сделать небольшой оценочный набор с фиксированными вопросами и эталонными ответами и прогонять его при каждом изменении цепочки.
8. Частые ошибки и диагностика
401(ошибка аутентификации): неверный или удалённый ключ, отсутствует Bearer, пустая переменная окружения.404 Not Found: в Base URL нет/v1или путь склеен дважды.model not found: неверное имя модели или у аккаунта нет прав на неё.400из-за длины входа: одна запись превышает лимит модели, сначала нарежьте.400из-за пустого входа: в массиве есть пустая строка, отфильтруйте на очистке.429: превышены частота или параллелизм, снизьте параллелизм и используйте экспоненциальную задержку.- Несовпадение размерности: запись и запрос используют разные модели или разные
dimensions. - Похожесть всегда 0 или аномально близка: проверьте нормализацию, правильность поля и отсутствие нулевых векторов в базе.
При диагностике сначала выводите имя модели, число входов, длину одной записи и размерность ответа. Большинство проблем — не «слабая модель», а несогласованность какого-то этапа запроса или хранения.
9. Миграция и проверка перед запуском
При переходе с официального API или другого шлюза на OpenAI-совместимый обычно меняются два места: base_url и api_key. Но у embedding есть дополнительная стоимость: при смене модели или размерности исторические векторы нужно перестраивать.
Перед запуском проверьте как минимум:
- поддерживает ли новая платформа используемые имя модели и размерность;
- нужна ли полная повторная векторизация базы, приемлемы ли окно перестройки и расход;
- переключаются ли сторона запроса и сторона записи одновременно, чтобы не смешать старые и новые векторы;
- есть ли схема постепенного включения, чтобы сравнить качество поиска на части трафика;
- покрывает ли мониторинг долю ошибок, задержку, расход токенов и долю пустых результатов;
- есть ли путь отката, сохранены ли старый ключ и старый индекс на время после переключения.
Если меняется только Base URL, а модель и размерность остаются прежними, миграция намного проще; если меняется и модель, планируйте её как проект перестройки данных. Конкретные модели и цены — на странице тарифов.
Частые вопросы
Чем embedding-интерфейс отличается от Chat Completions?
Embedding возвращает вектор фиксированной длины для поиска, кластеризация и расчёта похожести; Chat Completions возвращает сгенерированный текст. Аутентификация обычно одинаковая, но тело запроса и структура ответа полностью различаются.
Сколько текстов можно передать за раз?
Интерфейс принимает массив строк, но реальное ограничение — общий бюджет токенов и длина одной записи. Текущая документация OpenAI указывает лимит 300 000 токенов на запрос и около 8K токенов на запись. Практический пакет лучше делить по бюджету токенов, не работая на пределе.
Нужно ли нормализовать векторы самостоятельно?
Если платформа явно указывает, что векторы нормализованы, косинусную похожесть можно считать через скалярное произведение. Для собственной модели, ручного усечения размерности или неясного источника выполняйте L2-нормализацию одинаково при записи и запросе.
Можно ли использовать старые векторы после смены embedding-модели?
Смешивать напрямую нельзя. Пространства разных моделей несопоставимы, и совпадение размерности не означает семантического соответствия. После смены модели нужно перестроить индекс и записать в метаданные имя модели и размерность.
Нужно ли сегментировать китайский текст перед отправкой?
Обычно нет. Современные embedding-модели обрабатывают исходный текст напрямую, а сегментация может потерять контекст. Нужны очистка, нарезка, дедупликация и контроль длины.
Что дальше
Чтобы проверить один запрос, создайте ключ с низким лимитом на странице регистрации и замените текст в примере curl или Python на свой. Для выбора рабочего варианта смотрите тарифы и сравните качество поиска на небольшом наборе реальных бизнес-текстов. Цены и модели меняются — ориентируйтесь на текущее отображение на сайте.
Связанные статьи:
