Введение
Руководство по Vonage Voice API и Deepgram продвигает вас на удивление далеко.
К концу руководства вы сможете позвонить на номер Vonage, поговорить с ИИ-агентом, прервать его во время разговора и вести естественный голосовой диалог на базе Deepgram Voice Agent API.
Но агент всё ещё не может реально сделать для вас многое (у него нет инструментов!).
Попросите агента выполнить действие, и если информация об этом действии случайно не окажется в промпте, ему некуда смотреть. Дайте ему задачу, зависящую от другой системы, и у вас появится другая проблема: звонящий ждёт, пока эта система ответит.
Голос делает эти детали заметными очень быстро. Медленный API-вызов не похож на индикатор загрузки, он звучит как мёртвая тишина.
Эта специфическая для аудио проблема ИИ-агентов была представлена в статье Don’t Loop the Latency: Where Agent Loops Belong in Voice AI. Одна из идей в его посте — сохранять работу, выполняемую во время живого телефонного звонка, небольшой и предсказуемой.
Цель этого поста — превратить разговорное демо в агента, который может выполнять полезные задачи, предсказуемо сбоить и — самое главное — оставлять достаточно информации для последующего улучшения. В этом руководстве мы расширим существующего агента Vonage + Deepgram, чтобы он мог:
- дать агенту один инструмент для проверки статуса заказа
дать агенту один инструмент для проверки статуса заказа
- установить жёсткий тайм-аут для поиска
установить жёсткий тайм-аут для поиска
- обрабатывать сбои, не заставляя звонящего ждать
обрабатывать сбои, не заставляя звонящего ждать
- переводить запросы, которые агент не может обработать, на реальный телефонный номер
переводить запросы, которые агент не может обработать, на реальный телефонный номер
- сохранять запись о том, что произошло во время каждого звонка
сохранять запись о том, что произошло во время каждого звонка
TL;DR Краткое руководство доступно на GitHub.
TL;DR Краткое руководство доступно на .
Предварительные требования
Вам понадобятся:
Учётная запись Vonage API
- Приложение Vonage с включённым Voice
Приложение Vonage с включённым Voice
- Номер телефона Vonage, привязанный к этому приложению
Номер телефона Vonage, привязанный к этому приложению
- Второй номер телефона, который можно использовать как направление для поддержки оператором
Второй номер телефона, который можно использовать как направление для поддержки оператором
- Учётная запись Deepgram с доступом к Voice Agent
Учётная запись Deepgram с доступом к Voice Agent
- ngrok
ngrok
Дайте агенту инструмент
Мы намеренно сделаем вариант использования простым: проверка статуса заказа. Звонящий говорит:
«Где заказ A1001?»
«Где заказ A1001?»
Deepgram понимает, что агенту нужна внешняя информация, и просит наше приложение вызвать:
getOrderStatus("A1001")
getOrderStatus("A1001")
Наше приложение выполняет поиск и отправляет результат обратно в Deepgram. Затем модель превращает этот результат в естественный голосовой ответ.
Для этого руководства серверная часть заказов имитируется. Позже вы можете заменить её своей собственной базой данных или API; нам нужен поиск, который может завершиться успехом, сбоем или занять слишком много времени:
Также есть несколько специальных идентификаторов заказов для тестирования:
- Идентификаторы, начинающиеся с SLOW, возвращаются за две секунды.
Идентификаторы, начинающиеся с SLOW, возвращаются за две секунды.
- Идентификаторы, начинающиеся с FAIL, имитируют временный сетевой сбой.
Идентификаторы, начинающиеся с FAIL, имитируют временный сетевой сбой.
- Всё остальное возвращает not_found.
Всё остальное возвращает not_found.
Это позволяет воспроизвести виды поведения, которые мы в конечном итоге получили бы от реальной базы данных или внешнего API, без необходимости создавать полный серверный компонент для этого руководства.
Зарегистрируйте инструмент в Deepgram
API Voice Agent от Deepgram поддерживает вызов функций на стороне клиента. Мы описываем функцию в сообщении Settings агента:
Мы не передаём Deepgram HTTP-эндпоинт для функции. Вместо этого Deepgram отправляет нашему WebSocket запрос FunctionCallRequest. Это означает, что наше приложение сохраняет контроль над фактическим запуском инструмента.
Это полезно, потому что мы можем решить:
- разрешён ли инструмент
разрешён ли инструмент
- как долго он может работать
как долго он может работать
- следует ли повторять попытку при сбое
следует ли повторять попытку при сбое
- что услышит звонящий, если это не сработает
что услышит звонящий, если это не сработает
Промпт агента также остаётся узким:
Промпт сообщает модели, что делать, но мы не полагаемся только на промпт. Перед выполнением поиска заказа приложение проверяет, что запрос входит в область действия и что инструмент ещё не использовался в этом звонке. Вы можете увидеть, как это работает, в разделе «Обработка вызова функции».
Установите крайний срок для поиска
Именно здесь разработка для телефонного звонка начинает отличаться от разработки обычного чат-бота. Представьте, что сервису заказов требуется шесть секунд, чтобы ответить. С точки зрения сервера это просто медленный запрос.
С точки зрения звонящего, он задал вопрос, а затем услышал шесть секунд тишины.
Поэтому в этом примере поиску даётся 1500 миллисекунд:
Обёртка соревнует вызов серверной части с этим крайним сроком:
Важно то, что есть три возможных исхода:
Исход
Что это значит
Что мы делаем
result
Серверная часть ответила
Вернуть результат
timeout
Не ответила в течение 1500 мс
Использовать запасной вариант
transport_error
Соединение не удалось
Повторить один раз
Ответ not_found по-прежнему является успешным поиском. Серверная часть ответила; просто не нашла заказ.
Это отличается от тайм-аута.
Ограничьте то, что может произойти во время звонка
Для этого примера правила намеренно минимальны:
Каждый телефонный звонок получает собственный экземпляр политики:
Тайм-аут не повторяется.
Мы уже ждали 1,5 секунды. Повторный запуск того же медленного запроса может просто дать нам ещё 1,5 секунды тишины.
При транспортном сбое выполняется одна повторная попытка, потому что разорванное соединение или временная проблема вышестоящей системы может немедленно восстановиться.
Обработка вызова функции
Когда Deepgram отправляет FunctionCallRequest, обработчик WebSocket применяет эти правила перед запуском инструмента.
Основной поток выглядит так:
Фактический обработчик также записывает вызов инструмента и время, но именно эта часть управляет тем, что испытывает звонящий.
Используйте фиксированный запасной вариант
Если поиск не удался, вы не хотите, чтобы модель придумывала собственный план восстановления.
Приложение внедряет фиксированный ответ:
InjectAgentMessage от Deepgram позволяет нам поместить этот ответ непосредственно в разговор:
Поведение прерывания здесь важно.
Использование поведения по умолчанию может привести к тому, что внедрённое сообщение будет отклонено, пока звонящий говорит. Это не то, что вы хотите обнаружить, когда внедрённое сообщение является вашим путём обработки сбоя.
Также обратите внимание на имя поля: это message, а не content.
Обе эти детали в противном случае могут привести к тому, что звонок замолчит без особых указаний на причину.
Переводите неподдерживаемые запросы оператору
Агент по статусу заказов не должен пытаться отвечать на всё.
Если кто-то говорит:
«Я хочу оспорить платёж.»
«Я хочу оспорить платёж.»
Мы классифицируем это как запрос, связанный с оплатой:
Для этой демонстрации классификация намеренно ограничена и детерминирована:
Здесь нет второго вызова модели.
Если звонящий просит что-то вне обязанностей агента, мы знаем, что хотим одного и того же результата каждый раз: перевести звонок на человека.
transferToHuman() обновляет активный звонок Vonage с помощью нового NCCO.
NCCO сначала сообщает вызывающему абоненту, что происходит, а затем соединяет вызов с номером поддержки:
Затем мы переводим активный вызов:
Для локального тестирования SUPPORT_PHONE_NUMBER может быть просто другим телефоном, который находится рядом с вами.
Позвоните на свой номер Vonage с одного телефона, попросите оспорить платеж, и второй телефон должен зазвонить.
Это гораздо лучший запасной вариант, чем если бы ИИ притворялся, что может решить проблему с выставлением счета, к которой ему никогда не давали доступ.
Настройка перевода вызова
Чтобы обеспечить реальную передачу вызова, добавьте эти значения в ваше окружение:
Вам также понадобится существующая конфигурация:
Используйте номера в формате E.164 для номеров Vonage и поддержки.
Сохранение информации о вызове
Приложение также записывает небольшую запись в SQLite, когда вызов завершается.
Это не база данных заказов. Это запись самого голосового взаимодействия.
Запись может рассказать нам:
- что сказали вызывающий абонент и агент
что сказали вызывающий абонент и агент
- какой инструмент был вызван
какой инструмент был вызван
- сколько времени это заняло
сколько времени это заняло
- использовался ли запасной вариант
использовался ли запасной вариант
- был ли переведен вызывающий абонент
был ли переведен вызывающий абонент
- почему произошел перевод
почему произошел перевод
- как завершился вызов
как завершился вызов
Например, вызов по вопросу выставления счета может выглядеть примерно так:
Это полезно, даже если вы не создаете вокруг этого сложную систему оценки.
Если вызывающие абоненты продолжают переводиться по одной и той же причине, вы это увидите. Если время ожидания начинает расти, вы тоже это увидите. А если вы позже измените промпт, модель или инструменты, поле agent_version даст вам простой способ определить, какая версия обрабатывала какие вызовы.
Как запустить агента
Сначала запустите ngrok:
Вставьте этот HTTPS URL в BASE_URL и в вебхуки Answer и Event вашего приложения Vonage.
Затем запустите приложение:
Вы можете проверить вебхук Answer перед звонком:
Возвращаемый NCCO должен содержать конечную точку WebSocket, похожую на:
wss://YOUR-NGROK-URL/socket?callUuid=test123
Попробуйте четыре вызова
Попробуйте протестировать основные сценарии в виде отдельных телефонных вызовов.
Вызов
Что сказать
Что должно произойти
«Где заказ A1001?»
Агент зачитывает статус доставки
«Где заказ SLOW999?»
Поиск завершается по таймауту, и воспроизводится фиксированный запасной вариант
«Где заказ XYZ123?»
Агент сообщает, что заказ не найден
«Я хочу оспорить платеж.»
Вызов переводится на ваш телефон поддержки
Каждый вызов должен быть новым. В этом примере разрешен один поиск заказа за вызов.
После этого проверьте записи:
Вы должны увидеть разные пути в данных:
completed fallback completed handoff
Для последнего вызова handoff_reason должно быть billing.
Заключение
Оригинальный учебник Vonage + Deepgram позволяет голосовому ИИ-агенту общаться по реальному телефонному звонку. Здесь мы дали этому агенту одну полезную задачу: найти заказ, перестать ждать, когда серверная часть слишком медленная, корректно перейти к запасному варианту при сбое и переводить неподдерживаемые запросы на человека.
Серверная часть заказов имитируется, но подход тот же, если вы замените getOrderStatus() на свою собственную базу данных или API.
Это идея Винса «не зацикливайся на задержке» на практике: пока вызывающий абонент ждет, выполняйте небольшую работу, устанавливайте дедлайны для внешних вызовов и предусмотрите полезный путь выхода.
После вызова эти ограничения по задержке исчезают. Именно здесь становятся полезными сохраненные записи: транскрипты, вызовы инструментов, результаты, причины перевода и версии агента.
Если, например, возвраты все чаще появляются как причина перевода, мы можем использовать эти вызовы, чтобы решить, что строить дальше, добавить возможность возврата в версию-кандидат и протестировать ее на сохраненных вызовах, прежде чем показывать ее вызывающим абонентам.
Это начало рабочего процесса loop-engineering. Но об этом в следующем посте!






