Как создать голосовой фитнес ассистент
В предыдущем уроке вы познакомились с голосовыми AI-агентами и интерфейсом Realtime API. Теперь вас ждёт практика.❗️Практическая часть курса предполагает использование облачных ресурсов, которые вы оплачиваете самостоятельно. Курс разработан так, чтобы расходы были минимальными, — часть из них покроет стартовый грант.Чтобы избежать лишних трат:— останавливайте ВМ в перерывах между практиками (это не снизит потребление до нуля, но заметно сократит расходы);— настройте бюджет и уведомления в разделе
Биллинг;— удаляйте ресурсы после завершения курса;— необязательно выполнять все практики — сосредоточьтесь на релевантных.💻 Вы разберёте ключевые части приложения и пошагово настроите голосового агента. Полный код находится в GitHub-репозитории. Вы можете скачать проект, запустить его и адаптировать под свои задачи.Чтобы выполнить практику, подготовьте пререквизиты:
- аккаунт в Yandex Cloud
- API-ключ
YANDEX_CLOUD_API_KEY - ID каталога
YANDEX_CLOUD_FOLDER_ID - сервисный аккаунт с ролью
ai.models.user - Python 3.10+
- Зависимости
pip install yandex-ai-studio-sdk aiohttpYandex AI Studio SDK содержит утилитные классы для работы с аудио, которых достаточно для быстрого прототипирования голосового ассистента без реализации низкоуровневой логики захвата и воспроизведения звука.
Что вы соберёте
С точки зрения реализации вы создадите приложение с такими функциями:
- распознавание речи в реальном времени;
- генерация ответа с помощью модели;
- голосовое озвучивание ответа;
- вызов функций
calculate_calories,recommend_supplementsиend_dialog; - использование встроенных инструментов
file_searchиweb_search; - корректная обработка перебивания.
С точки зрения продукта — это голосовой фитнес-ассистент, который умеет:
- рассчитывать калории
calculate_calories; - рекомендовать спортивные добавки
recommend_supplements; - отвечать на вопросы по базе знаний
file_search; - искать актуальную информацию
web_search; - реагировать на перебивания пользователя;
- завершать диалог, если пользователь прощается
end_dialog.
Шаг 1. Подготовка клиентской части
1️⃣ Импортируйте вспомогательные аудиоклассы из установленной Yandex AI Studio SDK:
from yandex_ai_studio_sdk._experimental.audio.microphone import AsyncMicrophone
from yandex_ai_studio_sdk._experimental.audio.out import AsyncAudioOut
Эти классы берут на себя всю низкоуровневую работу с аудиоустройствами. Они обеспечивают корректный захват PCM-аудио, управление буфером и стабильное воспроизведение синтезированной речи.2️⃣ Подготовьте параметры подключения: сформируйте данные для установки WebSocket-соединения с Realtime API:
WSS_URL = (
f"wss://ai.api.cloud.yandex.net/v1/realtime/"
f"?model=gpt://{YANDEX_CLOUD_FOLDER_ID}/speech-realtime-250923"
)
Здесь:
wss://ai.api.cloud.yandex.net/v1/realtime/— эндпоинт Realtime API, реализует OpenAI-совместимый интерфейс взаимодействия;- параметр
model=в URL сообщает серверу, какую модель использовать в этой сессии; - формат
gpt://<YANDEX_CLOUD_FOLDER_ID>/<model_name>— стандартный способ обращения к моделям в AI Studio.
💻 Когда мы пишем этот урока, актуальна модель speech-realtime-250923. Она обеспечивает распознавание и синтез речи, генерацию ответа.3️⃣ Добавьте авторизационный заголовок. При подключении WebSocket-соединение должно пройти проверку по API-ключу:
HEADERS = {"Authorization": f"Api-Key {YANDEX_CLOUD_API_KEY}"}
YANDEX_CLOUD_API_KEY — ваш API-ключ, который позволяет обращаться к модели внутри указанного каталога YANDEX_CLOUD_FOLDER_ID.
Шаг 2. Настройка сессии Realtime API
На этом этапе вы зададите правила поведения для модели, доступные инструменты, формат аудио и другие параметры. Все настройки передаются в Realtime API с помощью метода session.update.1️⃣ Задайте инструкции для ассистента:
"instructions": (
"""Ты — умный фитнес-ассистент. Помогаешь людям с тренировками, питанием и спортивными добавками. Отвечаешь кратко, по делу и дружелюбно.
...
Твои возможности:
- Рассчитать калории и макронутриенты (функция calculate_calories)
- Порекомендовать спортивные добавки (recommend_supplements)
- Найти информацию в базе знаний о фитнесе (file_search)
- Найти актуальную информацию в интернете (web_search)"
...
"""
)
Поле instructions в конфигурации Realtime API выполняет роль системного промпта. Модель учитывает его , когда взаимодействует с пользователем. В этой инструкции вы определяете, как должен вести себя ассистент: какую роль он выполняет, как отвечает пользователю, когда обращается к функциям и каких правил придерживается в диалоге.2️⃣ В блоке output_modalities задайте тип “audio”. Так вы явно говорите модели, что она должна озвучивать полученный результат:
"output_modalities": ["audio"]
Если нужно, замените значение на "text", чтобы получать текстовый ответ без озвучивания синтезом.3️⃣ Задайте форматы входного и выходного аудио. Код входа и выхода один и тот же — сырые PCM-данные:
"type": "audio/pcm"
4️⃣ Настройте Voice Activity Detection (VAD):
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"silence_duration_ms": 400,
}
Что означают параметры:
type— тип детекции речи. Когда мы пишем этот урок, поддерживается только значениеserver_vad— определение начала и конца речи происходит на стороне сервера.threshold— порог чувствительности детекции речи. Чем ниже значение, тем легче считается, что пользователь начал говорить. Чем выше — тем больше уверенность, что это именно речь, а не шум.silence_duration_ms— длительность тишины (в миллисекундах), после которой фраза считается завершённой. В примере указано значение 400 мс: если пользователь замолчал на это время, ассистент начинает обработку.
С такими настройками ассистент поймёт, когда клиент начал или закончил говорить, чтобы вовремя переключаться между состояниями «говорю» и «слушаю».5️⃣ Укажите голос и скорость для синтеза речи:
"voice": VOICE, # например, "dasha"
"speed": 1.2,
📚 Можно использовать любой голос Yandex SpeechKit: из списка доступных или построенный по технологии Yandex Brand Voice.6️⃣ Опишите инструменты tools, доступные ассистенту. В вашем случае в конфигурации сессии есть пять инструментов:
"tools": [
# Функция расчёта калорий
{
"type": "function",
"name": "calculate_calories",
"description": "Рассчитывает базовый метаболизм и рекомендуемое потребление калорий на основе параметров пользователя",
"parameters": {
"type": "object",
"properties": {
"weight": {
"type": "number",
"description": "Вес в килограммах"
},
"height": {
"type": "number",
"description": "Рост в сантиметрах"
},
....
},
"required": ["weight", "height", "age", "gender", "activity_level"],
"additionalProperties": False
}
},
# Функция рекомендаций по добавкам
{
"type": "function",
"name": "recommend_supplements",
"description": "Рекомендует спортивные добавки в зависимости от цели тренировок и уровня опыта",
....
},
# Встроенная функция для поиска в интернете
{
"type": "function",
"name": "web_search",
"description": "Поиск в интернете актуальной информации о фитнесе, тренировках и питании",
"parameters": {}"
},
# Встроенная функция для поиска по базе знаний
{
"type": "function",
"name": "file_search",
"description": VECTOR_STORE_ID, # ID индекса с базой знаний о фитнесе
"parameters": {}
},
# Функция для завершения диалога при прощании
{
"type": "function",
"name": "end_dialog",
"description": "Завершает диалог. Вызывается когда пользователь прощается (говорит 'до свидания', 'пока', 'всего доброго' и т.п.). ",
"parameters": {
"type": "object",
"properties": {},
"additionalProperties": False
}
}
]
Здесь вы перечисляете пользовательские функции и встроенные инструменты:
- пользовательские функции —
calculate_calories,recommend_supplements,end_dialog; - встроенные инструменты —
web_search,file_search.
Пользовательские функции нужно реализовать на стороне клиента. Встроенные инструменты начинают работать сразу после того, как вы объявите их в конфигурации агента. Когда модель вызывает функцию, сервер отправляет событие response.output_item.done. В нем указаны тип события function_call, название функции и её аргументы:
def process_function_call(item) -> dict:
"""Обработка вызовов функций."""
call_id = item.get("call_id")
function_name = item.get("name")
args_text = item.get("arguments") or "{}"
...
7️⃣ Выполните функцию и верните результат модели:
...
return {
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": call_id,
"output": result_json
}
}
Для end_dialog есть отдельная логика завершения. Когда функция возвращает результат, клиент запрашивает финальную реплику, а затем закрывает сессию.💡Самостоятельно включите поиск по базе знанийДля этого передайте в file_search ID поискового индекса — также, как для текстового ассистента. За основу для индекса возьмите файлы из GitHub-репозитория текстового ассистента.
Шаг 3. Установка соединения и применение настроек
1️⃣ Создайте WebSocket-соединение и передайте конфигурацию:
await setup_session(ws)
Функция setup_session(ws) полностью задаёт поведение ассистента: устанавливает системные инструкции, регистрирует инструменты и конфигурирует VAD.Можно запускать отправку и приём аудио.
Шаг 4. Отправка аудио (uplink)
Передача аудио с микрофона на сервер происходит внутри функции uplink(ws) — она запускается параллельно с приёмом ответов. Основной цикл:
async for pcm in mic:
await ws.send_json({
"type": "input_audio_buffer.append",
"audio": b64_encode(pcm)
})
Каждый PCM-чанк берётся из AsyncMicrophone, кодируется в base64 и отправляется как JSON-событие. Таким образом ассистент начинает слышать клиента в реальном времени.
Шаг 5. Приём событий от сервера (downlink)
После того как вы настроили микрофон и начали передавать аудиопоток на сервер, Realtime API начинает отвечать цепочкой событий разных типов.На этом этапе начинается взаимодействие с готовым ассистентом: модель слушает, распознаёт речь, генерирует текст, синтезирует голос, вызывает инструменты и сообщает об этом через WebSocket.Теперь нужно подключить вторую половину диалога: обработку всех событий, которые сервер присылает в ответ на вашу речь и действия ассистента. Функция downlink(ws, audio_out) принимает эти события и управляет работой ассистента:
- отслеживает, когда пользователь закончил говорить;
- получает распознанный текст и фрагменты ответа;
- передаёт аудио на воспроизведение;
- прерывает ассистента при необходимости;
- обрабатывает вызовы функций модели.
❗️ Эта часть критична: без обработки событий модель не сможет полноценно вести диалог — вы будете только отправлять аудио.
Ключевые события сервера
Событие о распознанной речи пользователя. Сигнализирует, что VAD определил конец фразы:
case "conversation.item.input_audio_transcription.completed":
transcript = message.get("transcript")
Частичная генерация текста. Во время ответа модель отправляет текст по частям. Каждый такой фрагмент называется дельтой — это изменение по сравнению с предыдущим состоянием. Дельты помогают отображать текст постепенно, в реальном времени:
case "response.output_text.delta":
delta = message.get("delta")
Дельта аудиоответа. Сервер присылает звук частями, а вы их декодируете и проигрываете:
case "response.output_audio.delta":
decoded = b64_decode(message["delta"])
await audio_out.write(decoded)
Событие начала речи. Когда пользователь начинает говорить во время синтеза, сервер присылает это событие:
case "input_audio_buffer.speech_started":
play_epoch += 1
await audio_out.clear()
Здесь используется счетчик play_epoch, который отделяет актуальный ответ от устаревшего — в случаях, когда пользователь перебивает ассистента. Когда пользователь начинает говорить, нужно увеличить play_epoch и очистить буфер audio_out. После этого старые аудиофрагменты больше не воспроизводятся.Вызов рукописной функции
case "response.output_item.done":
if item.get("type") == 'function_call':
payload_item = process_function_call(item)
await ws.send_json(payload_item)
await ws.send_json({ "type": "response.create" })
Работа идёт по такой схеме:
- Модель вызывает функцию и отправляет JSON.
- Вы выполняете нужную Python-функцию.
- Отправляете результат обратно на сервер.
- Отправляете событие
response.create, чтобы модель продолжила ответ.
Шаг 6. Запуск главного цикла приложения
1️⃣ Объедините uplink и downlink в одном асинхронном цикле:
async with session.ws_connect(...) as ws:
await setup_session(ws)
async with AsyncAudioOut(...) as audio_out:
await asyncio.gather(
uplink(ws),
downlink(ws, audio_out),
)
Здесь одновременно запускаются два аудиопотока:
- uplink — от микрофона в сервер;
- downlink — от сервера сервер в динамик.
После этого ассистент начинает работать в реальном времени: распознаёт речь, понимает запрос, вызывает функции, умеет перебивать и поддерживать диалог.
Итоги урока
Прототип голосового ассистента на базе Realtime API можно собрать без большого инфраструктурного кода. Ассистент поддерживает потоковое распознавание речи, синтез, обработку интентов через функции, доступ к базе знаний и перебивания в диалоге.Фитнес-ассистент из практики — готовая архитектурная схема для любых голосовых агентов: клиентской поддержки, helpdesk-сценариев, образовательно-спортивных приложений и персональных помощников.
Realtime API берёт на себя всю сложность аудиовзаимодействия и оркестрации модели. Вы описываете только логику продукта. Удобно!Благодаря этому не нужно реализовывать низкоуровневую работу с аудио: обработку событий Realtime API и сценарную логику агента можно задать прямо в коде. Вы фокусируетесь на сценариях, функциях, инструментах и качестве взаимодействия.
Попробуйте расширить пример: добавьте собственные функции, подключите инструменты, используйте новые источники данных или интегрируйте ассистента в приложение. Realtime API позволяет использовать этот подход как основу для более сложных прикладных сценариев и продуктовых интеграций.
В следующем уроке вы разберёте low-code-подход. Узнаете, как быстро создавать агентов и встраивать их в бизнес-процессы в визуальном конструкторе Workflows.❗Не забудьте остановить ресурсы, чтобы избежать лишних трат.
Источник: https://practicum.yandex.ru/trainer/yc-ml-aiagents/lesson/91e7e5e9-5797-49ba-8c3d-9b76466c0ff9/
0 комментариев