Привет! Давно хотел поиграть с GigaChat от Сбера и наконец дошли руки. Решил не ограничиваться скучной перепиской, а сделать полноценного голосового ассистента. Чтобы можно было просто говорить с ним, как с Алисой или Siri, и получать озвученный ответ.
В итоге собрал веб-приложение, где фронт ловит голос, отправляет его в текст (через распознавание речи), я гоняю этот текст через GigaChat, а ответ озвучиваю с помощью SaluteSpeech.
В этом посте расскажу, с какими граблями пришлось столкнуться: как достучаться до API, куда сохранять токены, чтобы не вылетать по таймауту, как отлавливать ошибки и сделать так, чтобы интерфейс не выглядел как консоль 90-х.
Почему я выбрал именно GigaChat?
Сейчас выбор языковых моделей огромен: ChatGPT, Claude, YandexGPT и другие. Почему я полез в экосистему Сбера? Причины смешные и прагматичные:
Русский язык "из коробки". GigaChat изначально заточен под наш менталитет и контекст. Ему не нужно объяснять, кто такой "мужик с Уралмаша" или почему "шаурма" и "шаверма" — это вечный холивар.
Бесплатный тариф. На момент старта у них был щедрый тестовый период с нормальными лимитами. Для пет-проекта, который не приносит деньги, это жирный плюс.
Не только тексты. Модель умеет в код, таблицы и картинки (хотя картинки я в этом проекте не трогал, но сам факт).
Единая экосистема. Рядом есть SaluteSpeech с приятными голосами. Не надо платить за отдельные сервисы синтеза речи или стучаться в сторонние API — всё под одним кабинетом разработчика.
Как я строил архитектуру (чтоб не рассыпалась)
Фронт и бэк я решил не разносить по разным репозиториям, а взять Next.js (App Router). Это удобно: делаешь API-роуты прямо в папке app/api, и они уже работают как серверные функции. Безопасность соблюдена (ключи API наружу не торчат), и CORS'ы не болят.
Что получилось внутри:
Эндпоинт
/api/giga: Сюда стучится клиент с сообщением. Моя задача — проверить, что прислали не пустоту, выбить свежий токен для GigaChat, сходить с ним к нейросетке и вернуть ответ обратно на фронт.Эндпоинт
/api/salute: Работает как прокси для озвучки. Принимает текст, лезет за токеном SaluteSpeech, получает аудиофайл и отдаёт его потоком на клиент.Клиент: Обычный React с TypeScript и Tailwind. Здесь живут компоненты чата, кнопка микрофона, кастомные хуки (
useGigaChat,useSaluteSpeech) и прочая магия интерфейса.
Подготовка: как получить заветные ключи
Тут всё стандартно, но без инструкции вы можете пропустить важный шаг.
Идём на developers.sber.ru, регистрируемся и создаём проект.
В проекте активируем GigaChat и SaluteSpeech.
Забираем оттуда 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 });
Это было самое неприятное, что заняло полчаса гугления. Остальное заработало почти сразу.
Делаем голос: распознавание и синтез
Чтобы превратить текстового бота в настоящего ассистента, я добавил голос.
Распознавание речи (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); };
Это бесплатно и работает без сервера.
Синтез речи (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
Задавайте вопросы в комментариях (или тут), постараюсь ответить всем!
