Простой GPT-ассистент в Telegram на базе Яндекса и Node.js

79404bd10c6907839902e7a5bf0da313

Всем привет! Это моя первая публикация на Хабре, поэтому буду благодарен за любую обратную связь, которая поможет мне писать ещё лучше.

В статье вас ждёт «сборка» очень простой связки из распознавания и синтеза речи, а также запросов в модель YandexGPT на Node.js. Наш телеграм бот будет получать голосовое сообщение, а затем распознавать его, скармливать в модель GPT и синтезировать полученный ответ в голосовое сообщение.

Хочется начать с небольшого предисловия. В ходе написания этого простейшего решения я потратил кучу времени на попытку интегрироваться с популярным OpenAI ChatGPT, но мои нервы вышли из чата (обход блокировки, HTTPS прокси и т.п.), поэтому я перешёл к Яндексу. Он встречает нас дружелюбной консолью, понятной документацией и грантом на тестирование. В целом, если гранта по каким-либо причинам нет, то мне на все тесты хватило 20 рублей.

Telegram фреймворк

Для работы с телеграм я взял самый лучший (на мой взгляд) фреймворк — GrammyJS. Немного про работу с телеграм: самый простой путь — это long polling, когда вы периодически опрашиваете сервер телеграмма о новых событиях, а дальше их обрабатываете — также отправляя HTTP-запросы. В целом, со всеми этими задачами прекрасно справляется Grammy.

const bot = new Bot(BOT_TOKEN); // где BOT_TOKEN - это токен бота, который можно получить в t.me/BotFather

// привязываем наш обработчик на событие message:voice
bot.on('message:voice', async ctx => {
  // тут мы будем обрабатывать наше голосовое
  // вся информация о сообщении и методы для ответа содержатся в ctx
});

// запускаем бота
bot.start({
  // функция onStart будет вызвана, когда всё успешно стартанёт
  onStart: botInfo => console.log(`Bot started under ${botInfo.username}`),
  // какие события мы хотим обрабатывать
  allowed_updates: ['message'],
  // проигнорировать все события, когда бот "лежал"
  drop_pending_updates: true,
});

Здесь хочется дополнить, что Grammy даёт очень гибкую систему привязки для обработки событий, о которой вы можете подробнее узнать на их офф.сайте. Если кратко, то вы можете указать, что вам нужны именно сообщения, содержащие голосовое сообщение, картинку или другой контент.

Для ответа голосовым Grammy предоставляет прекрасный метод внутри контекста: ctx.replyWithVoice. Он принимает в себя объект класса InputFile, который можно собрать из Buffer'a, потока или Uint8Array. Дальше библиотека сама разберётся с загрузкой файла на сервер телеграмма.

Например, это может выглядеть вот так:

await ctx.replyWithVoice(new InputFile(buffer));

Кстати, скачать голосовое из телеграмма тоже крайне просто:

// вызываем метод API Telegram, чтобы получить путь к файлу
const { file_path: filePath } = await ctx.getFile();
// формируем URL
const fileUrl = `https://api.telegram.org/file/bot${token}/${filePath}`;

// скачиваем файл через Fetch API, появившийся в Node v18
const response = await fetch(url);
const buffer = Buffer.from(await response.arrayBuffer());

Распознавание и синтез голоса

Для этой задачи мне посчастливилось найти прекрасный NPM пакет, который берёт на себя все задачи по отправке запросов и получению ответов от API Yandex Cloud. Для простоты прототипа я не стал связываться с потоковым распознаванием, да оно и тут и не нужно — голосовое сообщение можно передать целиком.

Первым делом нам нужно настроить доступы, я использовал сервисный аккаунт — это проще всего — https://cloud.yandex.ru/ru/docs/speechkit/quickstart/

А дальше воспользуемся пакетом, чтобы распознать голосовое и синтезировать его обратно:

import { speech2text, text2speech } from 'yandex-speech-promise';

// распознание, buffer1 - это наше скачанное голосовое
const text = await speech2text(buffer1, {
  auth: `Api-Key ${YANDEX_API_KEY}`,
});

// здесь мы можем работать с текстом, передавать его GPT и т.д.

// а это уже синтез
const buffer2 = await text2speech(text, {
  auth: `Api-Key ${YANDEX_API_KEY}`,
  emotion: 'good', // neutral, evil
  lang: 'ru-RU',
  voice: 'oksana',
});

Запросы к GPT модели

Яндекс предоставляет несколько моделей и даже даёт возможность дообучить модель под себя. Также Яндекс предоставляет два режима на выбор: чат и одноразовый промпт.

Мы действуем в рамках минимального прототипа, поэтому пойдём по самому простому пути — сделаем запросы к стандартной модели YandexGPT в режиме промптов. Для этого найти библиотеку мне не удалось, поэтому ниже будет «полотенце» кода с запросом.

async function askAi(text: string, role: string): Promise {
  const response = await fetch('https://llm.api.cloud.yandex.net/foundationModels/v1/completion', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Api-Key ${YANDEX_API_KEY}`,
      'x-folder-id': YANDEX_FOLDER_ID!,
    },
    body: JSON.stringify({
      modelUri: `gpt://${YANDEX_FOLDER_ID}/yandexgpt-lite`,
      completionOptions: {
        stream: false,
        temperature: 0.1,
        maxTokens: '1000',
      },
      messages: [
        {
          role: 'system',
          text: role,
        },
        {
          role: 'user',
          text,
        },
      ],
    }),
  });

  const json = await response.json();
  return json.result.alternatives[0].message.text;
}

Здесь я прокомментирую лишь пару ключевых моментов. Во-первых, массив messages, в котором мы видим два объекта с разными полями role. Согласно документации, с помощью role: 'system' мы можем «настроить» нашу модель, например, вот так: Ты профессиональный секретарь. Ты прекрасно разбираешься в составлении документов и можешь генерировать их текст по запросу. А role: 'user' остаётся для пользовательского запроса в рамках настройки.

Во-вторых, можно обратить внимание на настройки temperature и maxTokens — они позволяют нам настроить степень креативности (непредсказуемости) модели и ограничение в токенах на ответ, чтобы не скрутить весь бюджет сразу.

Собираем всё вместе

В целом, весь код лежит на Github: https://github.com/ByMsx/voice-ai-tg-helper/, а тут я лишь расскажу, как его запустить.

Для начала вам понадобится установленный Node.js 18-й версии или Docker, а дальше просто создаём .env файл, в котором указываем настройки:

# Yandex service account credentials
YANDEX_API_KEY=
YANDEX_FOLDER_ID=
# Роль ИИ
AI_ROLE=Ты профессиональный секретарь.
# Telegram Bot token
BOT_TOKEN=

А дальше просто раскатываем проект (вводим эти команды в терминал или командную строку).

cd folder; # где folder - ваша папка с проектом
npm i -g yarn # если не стоит yarn
yarn # автоматически устанавливаем все зависимости
yarn start:dev # для запуска у себя на компьютере в режиме разработки

Переходим в созданного через BotFather бота, нажимаем старт и записываем голосовое!

Вместо послесловия

Благодарю вас за внимание! Буду рад любой обратной связи. Дальше планирую изучать нейросети, а не только использовать готовые и делиться этим в статьях.

© Habrahabr.ru