В прошлом примере я рассказал о том, как можно использовать YDB в качестве векторной базы знаний. Сегодня расскажу про то, как использовать Yandex Embedder.
Но сначала несколько слов о том, почему Embedder - это очень важно.
Итак, у нас есть задача: есть некоторая внутренняя система (у моем случае - YouTrack), есть ИИ (не важно какой). Я хочу получать ответы от ИИ с использованием данных из внутренней системы. Классическое решение - это выгрузка данных из внутренней системы в RAG и использование этих данных для контекста в запросе к ИИ. В качестве RAG используется векторная база данных (например YDB). Важным моментом является то, что в контекст запроса к LLM передается не вся база - а только некоторое количество наиболее близких документов ("близость" как раз определяется сравнением векторов). То есть, работает так:
мы пишем запрос
по этому запросу находим релевантные документы в RAG
отправляем запрос в LLM передавая найденные документы
Так вот. То, что вам ответит ИИ зависит от того, что вы найдете в RAG. А это в свою очередь зависит от того, насколько "хорошо" будут сгенерированы векторы. За генерацию векторов как раз и отвечает Embedder.
В моем прошлом примере класс сохранения данных в YDB использовал простой embedder из библиотеки '@xenova/transformers'
и результат поиска, если честно, был так себе. Поэтому следующей задачей стало использование уже более "правильного" Embedder-а. В моем случае, так как я в данном прототипе ориентирован на максимально использование сервисов Yandex Cloud то и выбор пал на Yandex Embedder. Доступные модели можно посмотреть в AI Studio в каталоге моделей.

Нам нужна та, которая называется Yandex Text embedding (query) 1
Ниже приведен код класса для работы с Yandex Embedder:
// yandex-embedder.js
export class YandexEmbedder {
constructor(options = {}) {
this.apiKey = options.apiKey;
this.baseURL = options.baseURL;
this.modelUri = options.modelUri;
this.requestsPerSecond = options.requestsPerSecond || 8;
this.delayBetweenRequests = 1000 / this.requestsPerSecond;
this.lastRequestTime = 0;
console.log('🔧 Yandex Embedder configured:');
console.log(' - Model Uri:', this.modelUri);
console.log(' - Base URL:', this.baseURL);
console.log(' - delayBetweenRequests:', this.delayBetweenRequests + 'ms');
}
async generateEmbedding(text) {
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequestTime;
// console.log(Date.now() + " timeSinceLastRequest: " + timeSinceLastRequest);
if (timeSinceLastRequest < this.delayBetweenRequests) {
const waitTime = this.delayBetweenRequests - timeSinceLastRequest;
await new Promise(resolve => setTimeout(resolve, waitTime));
}
// console.log(Date.now() + " After timer:");
this.lastRequestTime = Date.now();
return await this.makeRequest(text);
}
async makeRequest(text) {
const controller = new AbortController();
try {
// console.log(`🔍 Generating embedding for text: "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
const response = await fetch(this.baseURL, {
method: 'POST',
headers: {
'Authorization': `Api-Key ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
modelUri: this.modelUri,
text: text
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Yandex API error: ${response.status} - ${errorText}`);
}
const data = await response.json();
if (!data.embedding) {
throw new Error('No embedding in Yandex API response');
}
// console.log(`✅ Embedding generated, dimension: ${data.embedding.length}`);
return data.embedding;
} catch (error) {
console.error('❌ Error generating Yandex embedding:', error.message);
throw error;
}
}
// Batch генерация эмбеддингов
async generateEmbeddings(texts) {
console.log(`🔍 Generating embeddings for ${texts.length} texts...`);
const embeddings = [];
for (let i = 0; i < texts.length; i++) {
try {
const embedding = await this.generateEmbedding(texts[i]);
embeddings.push(embedding);
if ((i + 1) % 10 === 0 || i === texts.length - 1) {
console.log(`📊 Progress: ${i + 1}/${texts.length}`);
}
} catch (error) {
console.error(`❌ Failed to generate embedding for text ${i + 1}:`, error.message);
embeddings.push(null);
}
}
console.log(`✅ Generated ${embeddings.filter(e => e !== null).length}/${texts.length} embeddings`);
return embeddings;
}
}
При инициализации классу необходимо передать несколько параметров:
apiKey - ключ созданный для сервисного аккаунта от имени которого вы будете обращаться к сервису. Генерируется, например, в консоли Yandex Cloud
baseURL - URL сервиса - чаще всего это https://llm.api.cloud.yandex.net/foundationModels/v1/textEmbedding
modelUri - указатель на используемую модель. Чаще всего будет emb://${YourYandexCloudFolderID}/text-search-query/latest - но тоже могут быть нюансы (теоретически вы можете развернуть свою версию модели)
requestsPerSecond - количество запросов в сек, которые можно отправить к сервису.
Вот об этом чуть подробней. Сервис Яндекса обрабатывает не более 10-ти запросов в секунду. Так что запросы надо выстраивать в очередь. Реализация очереди может быть самой разной. Так как в моем случае проект в стадии "прототипа" - то я просто поставил таймер между запросами, но в production решении конечно надо придумывать что-то более надежное.
Могу сказать что с использованием Yandex Embedder-а качество ответов, которые стала выдавать мне программа стало прям на порядок лучше.
Надеюсь, этот пример будет кому-то полезен и сэкономит время. Только учтите - это именно пример на уровне прототипа.