Привет! Давно хотел поиграть с GigaChat от Сбера и наконец дошли руки. Решил не ограничиваться скучной перепиской, а сделать полноценного голосового ассистента. Чтобы можно было просто говорить с ним, как с Алисой или Siri, и получать озвученный ответ.

В итоге собрал веб-приложение, где фронт ловит голос, отправляет его в текст (через распознавание речи), я гоняю этот текст через GigaChat, а ответ озвучиваю с помощью SaluteSpeech.

В этом посте расскажу, с какими граблями пришлось столкнуться: как достучаться до API, куда сохранять токены, чтобы не вылетать по таймауту, как отлавливать ошибки и сделать так, чтобы интерфейс не выглядел как консоль 90-х.

Почему я выбрал именно GigaChat?

Сейчас выбор языковых моделей огромен: ChatGPT, Claude, YandexGPT и другие. Почему я полез в экосистему Сбера? Причины смешные и прагматичные:

  1. Русский язык "из коробки". GigaChat изначально заточен под наш менталитет и контекст. Ему не нужно объяснять, кто такой "мужик с Уралмаша" или почему "шаурма" и "шаверма" — это вечный холивар.

  2. Бесплатный тариф. На момент старта у них был щедрый тестовый период с нормальными лимитами. Для пет-проекта, который не приносит деньги, это жирный плюс.

  3. Не только тексты. Модель умеет в код, таблицы и картинки (хотя картинки я в этом проекте не трогал, но сам факт).

  4. Единая экосистема. Рядом есть SaluteSpeech с приятными голосами. Не надо платить за отдельные сервисы синтеза речи или стучаться в сторонние API — всё под одним кабинетом разработчика.

Как я строил архитектуру (чтоб не рассыпалась)

Фронт и бэк я решил не разносить по разным репозиториям, а взять Next.js (App Router). Это удобно: делаешь API-роуты прямо в папке app/api, и они уже работают как серверные функции. Безопасность соблюдена (ключи API наружу не торчат), и CORS'ы не болят.

Что получилось внутри:

  • Эндпоинт /api/giga: Сюда стучится клиент с сообщением. Моя задача — проверить, что прислали не пустоту, выбить свежий токен для GigaChat, сходить с ним к нейросетке и вернуть ответ обратно на фронт.

  • Эндпоинт /api/salute: Работает как прокси для озвучки. Принимает текст, лезет за токеном SaluteSpeech, получает аудиофайл и отдаёт его потоком на клиент.

  • Клиент: Обычный React с TypeScript и Tailwind. Здесь живут компоненты чата, кнопка микрофона, кастомные хуки (useGigaChatuseSaluteSpeech) и прочая магия интерфейса.

Подготовка: как получить заветные ключи

Тут всё стандартно, но без инструкции вы можете пропустить важный шаг.

  1. Идём на developers.sber.ru, регистрируемся и создаём проект.

  2. В проекте активируем GigaChat и SaluteSpeech.

  3. Забираем оттуда Client ID и Client Secret для каждого сервиса.

Эти данные нельзя светить в коде. Я сложил их в .env.local:

GIGACHAT_CLIENT_ID=your_client_id
GIGACHAT_CLIENT_SECRET=your_client_secret

Танцы с бубном вокруг токенов (OAuth 2.0)

GigaChat работает по протоколу OAuth 2.0. По-простому: чтобы задать вопрос, нужно сначала показать "паспорт" (Client ID/Secret) и получить взамен временный пропуск — Access Token. Токен живёт где-то 30 минут.

Самая частая ошибка новичков — дёргать сервер авторизации перед каждым сообщением. Так делать не надо: во-первых, это тормозит, во-вторых, можно нарваться на лимиты.

Я сделал функцию getAccessToken, которая кэширует токен и отдаёт старый, пока он не протух. Вот как это выглядит (упрощённо):

// Глобально храним токен в памяти (в проде лучше использовать Redis, но для старта сойдёт)
let cachedToken: { value: string; expiresAt: number } | null = null;

async function getAccessToken() {
  // Если токен ещё жив (с запасом в минуту), отдаём его
  if (cachedToken && cachedToken.expiresAt > Date.now() + 60000) {
    return cachedToken.value;
  }

  // Иначе идём за новым
  const authString = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
  const rqUid = crypto.randomUUID(); // Уникальный ID запроса (обязательно!)

  const response = await fetch('https://ngw.devices.sberbank.ru:9443/api/v2/oauth', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Basic ${authString}`,
      'RqUID': rqUid,
    },
    body: new URLSearchParams({ scope: 'GIGACHAT_API_PERS' }),
  });

  const data = await response.json();
  cachedToken = {
    value: data.access_token,
    expiresAt: Date.now() + data.expires_in * 1000,
  };
  return cachedToken.value;
}

Важный нюанс: Заголовок RqUID обязателен. Это просто случайный UUID. Без него Сбер ругается.

Отправляем запрос к модели

Сам эндпоинт GigaChat (/api/v1/chat/completions) сделан очень похожим на OpenAI. Если вы работали с ChatGPT API, разберётесь за минуту.

Я шлю на сервер массив messages с историей диалога, указываю модель (обычно GigaChat:latest), выкручиваю temperature (0.7 для баланса между креативом и фактами) и жду ответа.

const response = await fetch('https://gigachat.devices.sberbank.ru/api/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${accessToken}`,
  },
  body: JSON.stringify({
    model: 'GigaChat:latest',
    messages: [{ role: 'user', content: 'Привет, как дела?' }],
    temperature: 0.7,
    max_tokens: 1000,
  }),
});

В ответе, помимо самого текста, приходит объект usage с количеством потраченных токенов. Я вывожу это пользователю мелкими циферками, чтобы было понятно, сколько "веса" съел вопрос.

Сюрприз от Vercel (или проблема SSL)

Когда я задеплоил проект на Vercel, столкнулся с неожиданной ошибкой: запросы к API Сбера падали с ошибками сертификатов. Оказалось, в некоторых средах (и локально тоже) сервер Сбера отдаёт самоподписанный сертификат, который Node.js не хочет принимать.

Решение — создать кастомный HTTPS-агент, который временно отключает проверку (только для разработки! В бою так делать нельзя).

import https from 'https';

const agent = new https.Agent({
  rejectUnauthorized: process.env.NODE_ENV === 'production', // true только в проде
});

// И использовать этот agent в fetch
const response = fetch(url, { agent, ...otherOptions });

Это было самое неприятное, что заняло полчаса гугления. Остальное заработало почти сразу.

Делаем голос: распознавание и синтез

Чтобы превратить текстового бота в настоящего ассистента, я добавил голос.

  1. Распознавание речи (Speech-to-Text). Здесь не стал мудрить и использовал встроенный Web Speech API браузера. Он есть в Chrome и Edge, умеет в русский язык. Достаточно попросить у пользователя доступ к микрофону и нажать кнопку.

const recognition = new webkitSpeechRecognition();
recognition.lang = 'ru-RU';
recognition.onresult = (event) => {
  setInputValue(event.results[0][0].transcript);
};
  1. Это бесплатно и работает без сервера.

  2. Синтез речи (Text-to-Speech). Стандартный SpeechSynthesisUtterance звучит как робот из 90-х. Поэтому я полез в SaluteSpeech. Их голоса (например, "Мила") звучат очень приятно, почти как человек.

Логика на сервере для SaluteSpeech похожа на GigaChat: получаем токен, шлём текст, забираем аудиопоток. На клиенте я просто воспроизвожу этот поток через Audio или Web Audio API.

Интерфейс, который не стыдно показать

На клиенте я не стал городить огород. React-хуки, функциональные компоненты. Вот что получилось:

  • Боковая панель со списком всех диалогов. Можно искать, удалять, экспортировать в JSON.

  • Адаптивность: на телефоне тоже удобно, кнопка микрофона под пальцем.

  • Настройки: ползунок температуры (креативности) и чекбокс "стриминг" (чтобы ответ прилетал по словам, а не целиком).

  • Обратная связь: под каждым ответом кнопки лайк/дизлайк. Хочу потом собрать статистику, на каких вопросах модель тупит.

Кстати, про валидацию. Я ограничил длину сообщения 4000 символов, а температуру — от 0 до 2. Если пользователь введёт 100500, покажу ему понятную ошибку, а не сломаю API.

Обработка ошибок: чтобы не падало молча

Внешние API имею�� привычку падать или тормозить. Чтобы пользователь не видел белый экран, я добавил:

  • Таймаут 30 секунд на каждый запрос. Если GigaChat долго думает — отменяем запрос и пишем "Сервер не отвечает".

  • Перехват статусов: 401 (токен протух) -> чистим кэш и пробуем ещё раз. 429 (лимиты) -> показываем "Слишком много запросов, отдохните минуту".

  • Логирование: каждый запрос имеет свой requestId, по которому можно отследить в логах Vercel, где упало.

  • UI-уведомления: всплывающие тосты с понятным текстом, а не "Error: 500".

Деплой и итоги

Задеплоил на Vercel одной кнопкой (гит-репозиторий подключил, переменные окружения вбил в панель). Заработало сразу, если не считать истории с SSL.

Что в сухом остатке:

  • Рабочий AI-чат на русском, который можно юзать с телефона.

  • Голосовой ввод работает в браузере без установки приложений.

  • Сессии сохраняются в localStorage, так что после перезагрузки диалог никуда не девается.

  • Можно экспортировать диалоги — пригодится, если хотите показать смешные ответы друзьям.

Итог и ссылки

Честно говоря, я ожидал, что с российской экосистемой будет больше проблем. Но документация у Сбера внятная, API предсказуемые, SaluteSpeech вообще порадовал качеством. Если вы думаете, как добавить AI в свой проект — присмотритесь к GigaChat.

Весь код, как и обещал, на GitHub: https://github.com/Kurganov1993/giga
Задавайте
вопросы в комментариях (или тут), постараюсь ответить всем!