Към съдържанието

Sesamebot

Мост към Discord, който кара цял гласов канал да звучи като един човек за гласовия AI

Роля
Собственик на продукта, ръководя водещ агент и четирима агенти от екипа му
Статус
На пауза
Код
Частно хранилище
Технологии
Pythondiscord.pydiscord-ext-voice-recvfaster-whisperffmpegpytestsystemdGitHub Actions

В числа

64ms

ритъм на изпращащия цикъл: един блок от 1 024 звукови отчета при 16 kHz, със звук или тишина

13

итерации ми трябваха, за да открия поддържането на връзката чрез тишина

1,5s

минимално време, през което избраният говорещ държи думата

Проблемът

Гласовият асистент е направен за един човек в раздел на браузър: един микрофон, един глас и детектор на гласова активност, който решава кога е приключила репликата. В Discord каналът има няколко души, а Discord спира да подава звук, когато никой не говори. v1 работеше за един говорещ на моя сървър. Исках v2, която може да води разговор с група, да помни по-ранните сесии и да е покрита с тестове.

Подходът

Мостът кара канала да изглежда като един добре възпитан събеседник. Звукът идва от Discord като стерео на 48 kHz за всеки потребител, минава през списък с разрешени потребители и праг по RMS, понижава се до моно на 16 kHz и се нарежда на опашка, а отговорите на асистента на 24 kHz се преобразуват до стерео на 48 kHz и се възпроизвеждат. За v2 заведох 14 задачи в GitHub с подробни коментари и написах документ за предаване, после водещ агент раздели работата между четирима агенти от екипа в отделни git worktrees, които подаваха в един клон v2.

Как работи

  1. Цикъл, който изпраща тишина

    Когато всички замлъкнат, Discord спира да извиква приемника на звук на бота, затова асистентът никога не чува тишината, която слага край на репликата, и чака безкрайно, сякаш говорещият е по средата на изречението. Цикълът за изпращане излъчва по един блок от 1 024 отчета на всеки 64 ms, каквото и да става: звук, ако има на опашката, и тишина от нули, ако няма. Да го открия, ми отне 13 итерации, а тест фиксира ритъма както за опашка със звук, така и за празна опашка.

  2. Избор на говорещ

    При всеки запис приемникът измерва нивото по RMS на всеки потребител. Най-силният говорещ над прага на тишината взема думата и я държи поне 1,5 секунди, а срокът се подновява, докато продължава да говори, и се препраща само неговият звук. Пакетите на останалите се изхвърлят, както прекъсванията в истински разговор. Сървър с един-единствен водещ може да изключи избора.

  3. Маркери с имена

    Асистентът чува по един глас наведнъж и не може да познае чий е. Когато човек проговори за първи път в сесията, ботът пуска в потока предварително синтезиран клип с името или псевдонима му и изхвърля пакета, който го е задействал, така че маркерът се чува чисто преди първите му думи. Маркерите могат да се подготвят предварително за цял канал или сървър.

  4. Един път за вмъкване на звук

    Маркерите, началното встъпление за подготовка на асистента и припомнянето между сесии минават през една функция: тя нарязва PCM клипа на блокове с размера на блок от цикъла за изпращане, нарежда ги на опашка и заглушава микрофона на всеки потребител за времето на клипа плюс половин секунда опашка, така че два потока никога да не се застъпват във входа на асистента.

  5. Кодова дума, чута локално

    Малък локален модел за разпознаване на реч слуша този, който държи думата, а съвпадение с кодовата фраза затваря разговора през същото приключване като командата за напускане, срещу около 5 до 10 процента от едно процесорно ядро на малкия сървър.

  6. Проверка чрез пускане

    Водещият агент преглеждаше промените на всеки агент от екипа, като сам пускаше набора от тестове, преди да ги слее в v2 като един commit. Брой от 55 теста, заявен от агент от екипа, не съвпадна с кода, в който имаше 67, а заявена команда с псевдоним трябваше да бъде намерена в кода преди сливането. Агентите от екипа договаряха интерфейсите помежду си директно (регистър на hooks за жизнения цикъл, смяна на опашката, начална подготовка, която приема приемника на звук, имената на говорещите и клип за припомняне), а водещият не се намесваше, освен когато го помолят да реши спор.

Какво избрах и какво отпадна

Избрах

Избор на говорещ, който изхвърля вмешателствата

Пред

Смесване на всички говорещи в един поток

По един говорещ наведнъж е как вече работи разговорът, а детекторът на реплики на асистента очаква един глас.

Избрах

Отказ от whisperx и pyannote

Пред

Локално разпознаване на говорещите

И двете бяха твърде тежки за малкия VPS, а Discord вече етикетира всеки пакет с потребителя му.

Избрах

Аз сливам v2 в master след проверка

Пред

Агенти, които качват директно в master

Водещият агент отвори pull request, аз го проверих преди сливането, и никое автоматично качване не стига до master.

Резултатът

v2 работеше стабилно на моя сървър, влизайки в Discord като Джейсан Стейсън, името, което носят моите агенти. Ботът зависи от неофициален начин за вход в услугата на доставчика, който е извън предвиденото от доставчика ползване, а доставчикът прекрати анонимния достъп, затова остава личен инструмент. Описах обхвата на v3 върху друг набор от технологии за глас, после установих, че съществуващ framework за агенти покрива нуждата, и v3 не беше построена.