
Проблема
Ко мне часто обращаются с вопросами как к эксперту по ассемблеру PDP-11. Раньше я отвечал текстовыми сообщениями, затем перешёл на голосовые для экономии времени. Всё равно это было расточительно.
Задача
Встроить в мой сайт manwe.ru/bk0010 голосового ассистента, который будет консультировать людей, опираясь на накопленную мной базу знаний. Сайт – статический html, статьи в Markdown.
Архитектура
В качестве поставщика выбран x.ai. Подключение к нему из браузера требует API-ключа, но хранить его в статическом сайте небезопасно. Решение такое: браузер запрашивает токен у маленького php-скрипта, x.ai выдаёт временный токен на 5 минут. С ним браузер открывает WebSocket-соединение с агентом. Настоящий ключ лежит только на сервере с закрытым доступом. Скрипт проверяет, откуда пришёл запрос, и ограничивает число токенов с одного IP-адреса в час.
Цена вайбкодинга
Claude при реализации написал Cloudflare Worker для выдачи токенов, хотя я его об этом не просил. Пришлось объяснять ему как сделать на php. Over-engineering – типичная проблема ИИ-ассистентов.
Доработки и баги
Хоть виджет и подключается одной строкой кода, расшифровка голоса пользователя не работала. Помогло явное включение опции в настройках сессии.
Я хотел дать возможность пользователю говорить и печатать одновременно, но по умолчанию так не работало: текстовая реплика разбивала голосовую реплику на части. Пришлось привязать расшифровку голоса к ID реплики, а для напечатанного текста создавать отдельный ID.
Пауза в диктовке тоже разбивала фразу на части, причём с повтором: после паузы сервер создавал новую реплику, и её расшифровка почему-то содержала уже сказанное в прошлой реплике. Помогло сравнение содержания двух последних реплик: в случае совпадения они объединяются с одним ID.
Контекст
Сайт одностраничный, статьи подгружаются без перезагрузки. Агенту передаётся системное сообщение с содержанием страницы (благо, она изначально хранится в формате md). В конструкторе прописано отвечать в первую очередь по контексту текущей страницы, и лишь затем обращаться к базе знаний.
Это оказалось дешевле по расходу токенов, чем передавать агенту интернет-адрес страницы, чтобы он её парсил.
Каждая отправленная статья остаётся в истории разговора, контекст растёт. Но для коротких диалогов это даже плюс.
Ссылки и источники
Голосовой ассистент присылает обычный текст, а мой виджет находит а нём адреса (типа https://) и стилизует как нажимаемые ссылки.
В инструкции агенту сказано: если ответ взят с внешнего сайта, дописывать в конце «Source: адрес». Виджет оформляет ссылку на источник в виде тэга, а слово «Source» убирает из текста.
UX голосового ассистента
Я быстро понял, что UX виджета от x.ai никуда не годится. Например, почему нельзя сразу печатать текст? Поле ввода неактивно, пока не установлено соединение с сервером. Это ведь бессмыслица. В 99% случаев соединение установится раньше, чем пользователь закончит печатать свой вопрос. Я разрешил печатать и нажимать Enter для отправки сразу, но фактическая отправка произойдёт только после установки соединения.
Зачем в поле ввода писать «Send a message», а справа ставить кнопку микрофона? Пользователя сбивают с толку: что ему делать – писать или говорить? К тому же, справа обычно ставят кнопку отправки. Я максимально упростил интерфейс: в поле нет подсказки, зато всегда мигает курсор, а микрофон включен и отображает уровень сигнала. С первого взгляда понятно: система активна и одновременно принимает как голосовой, так и текстовый ввод. Сказанное сразу появляется в чате, а написанное – после нажатия клавиши Enter. Красный цвет подчёркивает, что идёт запись. Нажатие на красную кнопку – привычный паттерн, несложно догадаться как остановить запись.
Финансовые показатели
Работа заняла один день, не считая сбора базы знаний, которую я собирал и структурировал годами.
Один сеанс с обсуждением нескольких тем обходится примерно в 1$. Зато я больше не трачу своё время.
Что дальше
В одной модели сложно настроить разные правила для текста и голоса. Стало понятно, что нужны две модели: первая для генерации текста, вторая для его озвучивания. Тогда текст может содержать ссылки, а в голосовую модель они не попадут. Специфические термины можно преобразовывать в транскрипцию перед отправкой в голосовую модель. Возможностей для улучшения много.