История о том, как я пытаюсь создать голосового AI помощника для моего 5-летнего сына.
Создание AI помощника — идея не новая, особенно с учетом массового распространения ИИ в последний год и появления голосового ассистента от OpenAI и их Realtime API — которое позволяет разработчикам создавать мультимодальные интерфейсы с низкой задержкой преобразования речи в речь.
Хотя API OpenAI предлагает потрясающие возможности, высокая стоимость ($100 за 1 млн входных токенов и $200 за 1 млн выходных токенов) подталкивает к поиску более доступных решений. Поэтому я обратил внимание на опенсорсный проект LiveKit, предлагающий масштабируемую связь в реальном времени. Одной из интересных функций — возможность интеграции ИИ агентов. И я решил попробовать использовать их решение, для создания госового помощника.
Типичный pipeline голосового агента в LiveKit выглядит следующим образом:
Если просто: голосовой поток пользователя преобразуется в текст. Полученный текст передается в большую языковую модель (LLM), которая, следуя заданным инструкциям (промпту), генерирует текстовый ответ. Текстовый ответ, сгенерированный LLM, преобразуется в аудиопоток. Сгенерированный аудиопоток воспроизводится.
В моем проекте используются следующие компоненты LiveKit:
-
VAD: детектор голосовой активности — Silero VAD .
-
SST (Speech-to-Text): Мультиязычная модель nova-2-general от Deepgram.
-
LLM (Large Language Model): GPT-4o от OpenAI. Однако, LiveKit поддерживает и другие модели, совместимые с OpenAI API (Groq, Perplexity, TogetherAI и др.).
-
TTS (Text-to-Speech): Использован сервис OpenAI с голосом «alloy». Возможны и другие варианты (Deepgram, и др.).
Цели проекта
Моя цель — создать интерактивного учителя, способного не только объяснять материал, но и управлять интерактивными инструментами в режиме реального времени, подобно живому учителю, который одновременно рассказывает и показывает. Ключевая задача — реализовать голосового помощника с «потоковым» управлением интерактивными инструментами .
Математика с интерактивной таблицей
Чтобы проверить идею управления интерактивными инструментами с помощью голосового помощника, я выбрал упражнение из учебника Oxford International Primary Maths на тему «десятки». Помощник должен объяснять материал и одновременно используя интерактивную таблицу выделять столбцы, строки или отдельные ячейки, иллюстрируя свои объяснения.
Этапы реализации:
1. Создание интерактивной таблицы
На Vue.js создана интерактивная таблица 10×10, позволяющая выделять столбцы, строки и отдельные ячейки.
2. Постановка задачи для LLM
LLM получила инструкцию: объяснить пятилетнему ребенку понятие «десятки» и как с ними считать, используя интерактивную таблицу.
Описание API для взаимодействия с таблицей:
Я описал взаимодействие с интерактивной таблицей через OpenAPI. Например, для выделения столбца должен быть использован вызов: http POST /highlight-column?column=1
.
###API You may call interactive table use openAPI: openapi: 3.0.0 info: title: Interactive Table API version: 1.0.0 description: API for managing a 10x10 interactive number table paths: /highlight-column: post: summary: Highlight Column description: Highlights the specified column in the table operationId: highlightColumn parameters: - name: column in: query description: Column number to highlight (1-10) required: true schema: type: integer minimum: 1 maximum: 10 responses: '200': description: Successfully highlighted the column /highlight-row: post: summary: Highlight Row description: Highlights the specified row in the table operationId: highlightRow parameters: - name: row in: query description: Row number to highlight (1-10) required: true schema: type: integer minimum: 1 maximum: 10 responses: '200': description: Successfully highlighted the row /highlight-number: post: summary: Highlight Number description: Highlights the specified number in the table operationId: highlightNumber parameters: - name: number in: query description: Number to highlight (1-100) required: true schema: type: integer minimum: 1 maximum: 100 responses: '200': description: Successfully highlighted the number ###CALL API If you need to set an example for a child, always use an interactive table, for example: ```http POST /highlight-column?column=1```
Ответ LLM: LLM генерирует вызов функции непосредственно в ответе, а не отдельным процессом.
3. Синхронизация вызова функции и воспроизведения аудио
Основная сложность заключалась в синхронизации вызова функции с воспроизведением аудио. Синхронизация должна происходить именно во время воспроизведения, а не во время генерации ответа LLM или преобразования текста в речь.
Архитектура потока данных
Базовый поток данных в системе выглядит следующим образом:
STT → LLM → TTS → PlayoutAudio
Для реализации синхронизации потребовалось внести изменения в исходный код проекта LiveKit на следующих этапах LLM — TTS — PlayoutAudio. А именно, найти и пробросить на все дальнейшие этапы “вызов функции”.
-
Анализ ответа от LLM: На этапе получения ответа от LLM (в режиме stream) добавлена посимвольная проверка на наличие маски вызова функции, которая определяет, есть ли в тексте вызов функции. Найденная функция собирается как отдельное предложение.
-
Обработка TTS: Текст, генерируемый LLM (в режиме stream), накапливается в буфере и периодически, когда набирается достаточно данных (достигнута минимальная длина предложения), отправляет на генерацию аудио.
При этом, если при разбивке на предложения мы встречаем “вызов функции”, то следующему предложению мы добавляем отдельный параметр call_tools, а сам “вызов функции” не оптравялется в TTS (озвучивать же его не надо).
Таким образом это эмулируют стриминг, разбивая текст на предложения и отправляя каждое предложение в TTS . Это позволяет обрабатывать длинные тексты, обеспечивая постепенное получение аудиофреймов.
-
Воспроизведение и вызов функции: На этапе воспроизведения (PlayoutAudio) происходит проверка
call_tools
в каждом аудиофрейме. Еслиcall_tools
содержит данные, то осуществляется вызов соответствующей функции в момент начала воспроизведения текущего аудиофрейма. Это обеспечивает синхронизацию с содержанием, произносимым в данный момент.
Таким образом, данная архитектура позволяет достигнуть синхронизации: разбиение текста на предложения и обработка каждого предложения в отдельном экземпляре — ключевой элемент реализации, позволяющий связать команды с аудио и обеспечить их синхронное выполнение.
P.S. Разбиение на отдельные предложения уже был реализован в LiveKit для работы с нестриминговыми TTS.
Почему стандартный вызов функций не подошёл
Как вы знаете LLM может вызывать функции, чтобы управлять внешними сервисами и на самом деле LiveKit реализовало поддержку call function.
Однако, стандартный вызов функций LLM — не обеспечивал необходимую синхронизацию, проблемы возникали при множественных вызовах, не позволяя четко определить момент их исполнения.
Небольшой тест работы вызовов функций в «потоковом» режиме
Это лишь первые эксперименты, и я планирую дальше развивать данный проект. Любые ваши предложения и критика помогут мне улучшить ассистента быстрее. Спасибо!
ссылка на оригинал статьи https://habr.com/ru/articles/854656/
Добавить комментарий