Голосовые системы

Как читать Mermaid-схему

Схема описывает микрофон установленного плагина: Chrome отдаёт исходный звук, браузерный Silero выделяет готовую фразу, сервер распознаёт её одним batch-вызовом Whisper Turbo. Распознанный текст возвращается панели. Панель запускает отдельную команду чата; Agent получает ответ Qwen или выполняет запрошенный моделью инструмент страницы и возвращает текст пользователю.

Все UUID, тексты, временные отметки, размеры примера и оценки в телах сообщений синтетические. Короткие S/U/A/C/T/P/R/G — подстановки значений из таблицы, а не буквальные значения production subjects. capability, token, JWT и sha256 на стрелках обозначают поля; действующие credentials и фактические аудиоданные не включены. Значения decode_ms:150 и других длительностей — учебный пример, не новый замер производительности.

Обозначение Значение Кто создаёт и зачем
S 11111111-1111-4111-8111-111111111111 Существующая сессия пользователя/переписки
U 22222222-2222-4222-8222-222222222222 Gateway выдаёт UI client_id при bootstrap
A 33333333-3333-4333-8333-333333333333 Gateway выдаёт отдельный audio client_id
T 44444444-4444-4444-8444-444444444444 Ingress создаёт stream_id захвата
P 55555555-5555-4555-8555-555555555555 Браузерный VAD создаёт phrase_id
C 66666666-6666-4666-8666-666666666666 Контекст исходного документа панели
R UUIDv5(NAMESPACE_URL, "chat-agent:S:T:P") Core детерминированно связывает ASR-фразу с командой чата
G 77777777-7777-4777-8777-777777777777 Agent создаёт новый ID на каждый шаг LLM; это request_id внутри LlmRequest
X 88888888-8888-4888-8888-888888888888 Bridge создаёт ID одного обмена с GPU worker

НовыйInbox / _INBOX.… — временный NATS reply subject. Точный суффикс создаёт клиентская библиотека, он не фиксирован. > и … у subjects nats-proxy обозначают семейство динамических адресов. Публикация в literal subject с > не производится.

Порядок и поля сообщений

  1. Доступ к каналам. Расширение вызывает /auth/nats отдельно для UI и audio через src_front → src_api_gateway. session_id связывает доступ с сессией, role выбирает конечный набор прав. В ответе client_id — владелец соединения; prefix — его namespace; url — WebSocket path. connect_token нужен NATS CONNECT, capability подписывает прикладные команды, refresh_token обновляет доступ. capability_expires_at и transport_exp — Unix seconds, срок прикладных полномочий и длительного транспорта. Обновление bootstrap раз в 60 секунд переиспользует тот же client_id. На повторном bootstrap может быть поле refresh_token.

  2. Подключение и подписки. name — имя NATS-клиента для диагностики, auth_token — connect grant. Через $SYS.REQ.USER.AUTH брокер запрашивает у Gateway разрешение. Ответ — подписанный JWT с ограниченными pub/sub-правами. UI подписан на chat.ui.U.events.ui и .events.tools; audio — на chat.audio.A.events.audio. Временные ответы остаются внутри .reply.>. Нет подписки плагина на произвольный серверный subject. Обе роли используют WSS /chrome-chat-ws → nginx → chat-agent_chat_nats:9222; это не старый аудиомаршрут /ws.

  3. Регистрация browser_context. Внешний envelope прикладных команд: {version:1, capability, data:{…}}. version здесь — версия браузерного envelope. В data регистрация имеет protocol:2, kind:"widget", context_id:C и один инструмент run_js. tools[].name выбирает инструмент, description описывает его для модели, inputSchema задаёт JSON Schema аргументов {code:string}, provider:"page" — владелец исполнения, purpose:"action" — назначение. На диаграмме схема аргументов сокращена; в реализации она содержит type:"object", required:["code"], additionalProperties:false, а code ограничен 16000 символами. Gateway хранит каталог. Полный DOM не собирается при регистрации.

  4. Открытие аудио start. audio_protocol:3 выбирает новый batch-контур с браузерными границами, segmentation:"browser_vad" определяет владельца границ. sample_rate — реальная частота AudioContext, 48000 только в примере; channels:1, format:"pcm16le" — mono signed 16-bit little-endian. asr_backend:"streaming_worker" — историческое имя Whisper Turbo; оно не включает streaming-декодирование. context_id и ui_capability доказывают принадлежность звука исходной панели. Обычный голосовой плагин жёстко выбирает Turbo; другие модели доступны через диагностические интерфейсы.

  5. Внутренний chat.browser.audio. Gateway проверяет внешнюю capability и создаёт другой envelope: command, data, session_id, client_id, profile, lease_exp, необязательный user_agent. command — start/phrase/end, client_id:A — аудиовладелец, profile:"extension" — профиль клиента, lease_exp — крайний допустимый Unix second. Для START Gateway добавляет проверенную widget_route:{session_id:S,client_id:U,context_id:C}; это UI-владелец будущего текста. ui_capability во внутреннем data больше не передаётся. Для PHRASE/END уже используется существующая привязка ingress.

  6. Допуск chat.agent.audio.control. action:"admit" резервирует поток T. transport:"nats" обозначает ingress. Core допускает до 3 live-потоков и отдельный replay. Ответ содержит ok, epoch, token, pending_phrases и asr_result_subject. epoch — запуск владельца Core, token — доказательство владения конкретным потоком, pending_phrases — количество принятых фраз без терминального результата. Эти поля используются в heartbeat, queued, ending и ended. queued добавляет P в ожидаемые; heartbeat поддерживает lease и возвращает pending; ending запрещает преждевременное завершение; ended освобождает слот. code:"audio_stopped" — нормальное завершение. Межсервисный token не равен браузерной capability.

  7. READY. Ответ браузерного RPC имеет {status:200,data:{type:"ready",session_id:S,stream_id:T,sample_rate,asr_backend}}. status — прикладной status внутри NATS, не отдельный HTTP-response code. READY подтверждает допуск и подписку ingress на PCM. Оно не означает распознавание речи. Подписки UI/tools/audio уже поставлены перед следующим RPC; лишние отдельные PING/PONG удалены.

  8. Исходный звук. getUserMedia принимает audio.noiseSuppression, echoCancellation, autoGainControl и video:false; значения на схеме — синтетический выбор. Requested и реальные track.getSettings() сохраняются отдельно. AudioContext даёт Float32. Worklet ограничивает диапазон [-1,1], квантует в int16 и выдаёт блоки по 1024 отсчёта. MessagePort передаёт ArrayBuffer, обычный размер 2048 байт. Локальный ack освобождает ожидание Worklet; это не сетевой RPC.

  9. Пакет chat.audio.A.pcm. Первые 8 байт — беззнаковый sequence в big-endian; затем исходные mono PCM16LE байты. Sequence начинается с 1 и растёт непрерывно. Предел всего сообщения — 8192 байта, обычное сообщение 2056 байт. При 48 кГц блок покрывает около 21,33 мс. В нём нет JSON, sample_rate, WAV, base64 или Opus: формат и частота уже согласованы START. Gateway маршрутизирует команды, а ingress подписан на PCM непосредственно через chat_nats. Тишина передаётся вместе с речью.

  10. Аналитическая копия Silero. Worker получает {type:"init",rate,offset} и {type:"pcm",pcm:ArrayBuffer}. rate — частота источника, offset — накопленный счёт исходных отсчётов. Только эта копия превращается int16→float32 и непрерывно 48→16 кГц через libsamplerate-js 2.1.2 SRC_SINC_FASTEST. Silero получает по 512 новых отсчётов и 64 отсчёта контекста, с сохранённым ONNX-state. Исходный PCM и его амплитуда не изменяются. vad содержит provider, revision ONNX, analysis_rate, resampler и parameters. start:0.65/continuation:0.50 — пороги наличия речи; pre_roll_ms:200, activation_ms:160, silence_ms:450, max_phrase_ms:30000 — параметры границ. Это не фильтрация текстового ответа Whisper.

  11. Начало и конец phrase. type — phrase_start или phrase_end; phrase_id:P — одна фраза. start_sample включён, end_sample исключён; оба в исходной частоте, а не 16 кГц. last_speech_sample — последний речевой отсчёт по VAD. last_seq — последний PCM-пакет, который должен быть получен ingress перед принятием границы. reason — pause/mute/limit; служебный selection относится к диагностике, не live-микрофону. collection_ms считается как (last_speech_sample-start_sample)/rate*1000, silence_ms — (end_sample-last_speech_sample)/rate*1000; первый показатель включает pre-roll. vad_ms — накопленное вычислительное время VAD на диапазоне, не длительность фразы. Реальная пауза квантуется кадрами модели; 450 мс — настройка, не обещание точного момента доставки. Для повторной доставки той же границы возвращается прежняя квитанция; конфликт координат не принимается.

  12. phrase_accepted. {ok:true,type:"phrase_accepted",phrase_id:P,last_seq:186} внутри {status:200,data:…} подтверждает наличие исходного диапазона и постановку ограниченной отправки. Модель в этот момент может ещё не начать работу. 2,95-секундный диапазон [48000,189600) содержит 141600 исходных отсчётов = 283200 байт при 48 кГц. Пакет 186 может содержать также отсчёты после правой границы — границы определяются samples, а не номером последнего байта пакета.

  13. Batch begin. Формат каждого сообщения: uint32_be json_length | UTF-8 JSON | raw PCM. В begin PCM пуст. version:3 — версия бинарного ASR-протокола; она отличается от version:1 браузерного envelope. S/T/P — стабильная идентичность задачи. seq:0, type:"batch_begin"; sample_rate:16000, channels:1, sample_width:2, bytes:94400, sha256 описывают уже подготовленный вход. source хранит исходные координаты, sample_rate:48000, format, channels, pcm_sha256 и число исходных samples. capture_timings содержит collection_ms/silence_ms/vad_ms; prepare_ms — подготовку source→16 кГц; replay:false обозначает live. Параметры vad остаются для диагностики. Заголовок ≤8192 байт.

  14. Batch PCM и commit. batch_pcm содержит только идентичность version/S/T/P, последовательный seq и raw PCM после JSON. Максимум 128 КиБ PCM за сообщение. batch_commit — следующий seq, PCM пуст; только он использует RPC. Bridge проверяет объём, SHA-256 и непрерывность; ответ {ok:true,phrase_id:P,state:"queued"} относится к завершённой загрузке. Сохранённое finished-состояние позволяет ответить на повтор commit без второго inference. Вход уже готов — искусственной подачи в темпе 1× нет.

  15. H100. Bridge пересылает те же begin/PCM/commit по to.inference.webrtc.asr.sample.X; X — новый ID обмена, S/T/P внутри не меняются. Account mapping превращает subject в inference.webrtc.asr.sample.X. NATS leafnode использует nats://192.168.28.134:37422, опубликованный на :7422 контейнера inference_nats. Worker превращает int16 в float32 делением на 32768 и один раз вызывает выбранную модель. decode_ms включает весь генератор сегментов, decode_count:1 — один decode, model_input описывает float32-вход и его хеш. Ни server VAD, ни word timestamps, ни growing window в этой ветке не выполняются.

  16. Ответ worker и окончательный ASR. Worker возвращает text, decode_ms, backend/model, scores, parameters, score_filtering:false, model_input, transport и words. avg_logprob, no_speech_prob, compression_ratio — диагностические оценки; они не отбрасывают непустой текст. Обратный subject inference.webrtc.asr.sample.result.X становится from.inference.webrtc.asr.sample.result.X. Bridge публикует webrtc.komaroff.asr.text.S: version:1, revision:1, result_type:final/rejected/error, S/T/P, text, code, processing_mode:batch, source, vad, evaluation и timings. worker_queue_ms исключён из worker_decode_ms; prepare_ms тоже отдельный. Пустой текст получает rejected/empty_text; техническая ошибка имеет error/code, а не ложный успех.

  17. asr_result в панель. Core проверяет владельца и повтор результата, вычисляет R и публикует nats.events.user123. Общая обёртка события имеет time, service, type, kind, data, name, uid. type:"command", kind:"asr_result"; в data — S/T/P/revision/text, turn_id, R, UI client_id U, context_id C и доступные timings. Gateway отправляет событие в chat.ui.U.events.ui. Offscreen → background → content.ts — локальная доставка Chrome, без ещё одного HTTP/NATS hop. При rejected/error панель показывает причину/ID, а message и инструменты не запускает.

  18. Команда чата message. Панель немедленно рисует распознанный пользовательский текст, затем отправляет {text,request_id:R} через локальный runtime. Offscreen добавляет session_id, context_id и source_page. Внешний NATS envelope прежний {version:1,capability,data}. Gateway добавляет widget_cache.tools из регистрации и передаёт chat.browser.core. Core создаёт action:"submit", message и browser-binding для chat.internal.agent.widget. browser.kind/context_id/client_id/tools определяют владельца выполнения. source_page — адрес исходной вкладки, не endpoint модели. В показанном новом запросе ACK — строго {status:202,data:{}}, без выдуманного accepted-флага. Дубликат может вернуть сохранённое state.

  19. Допуск Agent и PostgreSQL. Agent сохраняет квитанцию (session_id,request_id,command_hash,client_id), после этого активирует выполнение. Значение хеша защищает от другого текста под тем же R. В текущем Agent 3 исполнителя, до 9 ожидающих, per_session=2. Ошибки этой очереди относятся к последующей обработке команды, не к ASR. Перед итоговым событием сохраняется финальная квитанция state/diagnostic/timings; это не автоматический повтор работы после перезапуска.

  20. nats.llm.user123. mode:"agent_step"; input.messages — сообщения system/user/assistant/tool, input.tools — OpenAI-описание run_js. trace_id и correlation_id относятся к родительской команде R; request_id:G — новая генерация, session_id:S — сессия, ts_ms — Unix milliseconds. constraints.tool_choice по умолчанию auto; repair может потребовать инструмент через required. headers исполнения отдельно несут deadline/attempt/generation identity и trace context; на схеме их значения не выдуманы. LLM gateway имеет 1 активный слот и до 3 ожидающих.

  21. Подсчёт /tokenize. Перед генерацией есть самостоятельный HTTP roundtrip. model — served model Qwen3.8-27B-FP8, messages и tools — фактический вход, add_generation_prompt:true — учёт шаблона начала ответа, chat_template_kwargs.enable_thinking:false — выбранный режим. Ответ count — число входных токенов, не длительность. Если нужен повтор подсчёта после удаления необязательного контекста, это отдельный HTTP-вызов. Входные инструкции/текущий запрос не заменяются произвольным обрезанием.

  22. HTTP/NATS proxy. Вход src_llm: https://nats-proxy-vllm.gis-master.ru; requester — rag-stack_nats_proxy_vllm, его NATS — nats://rag-stack_inference_nats:4222. Subject root запроса to.inference.vllm, H100 subscription inference.vllm.requests.>, ответы возвращаются через roots inference.vllm → from.inference.vllm. Receiver — один из llm-models_nats_proxy_vllm / _02, queue group vllm-gateway-receivers; upstream http://llm-models_vllm_openai:8000. Существенный payload — HTTP method/path/headers/JSON и обратные status/headers/SSE. Точные поля внешнего конверта, tunnel/chunk framing и dynamic suffix закрытого образа dtorry/nats-proxy исходниками этой проверки не подтверждены. На схеме они сознательно не заменены выдуманной JSON-схемой.

  23. Генерация /v1/chat/completions. model, messages, tools — вход; stream:true включает SSE ответа Qwen, stream_options.include_usage:true запрашивает счётчики, temperature:0, max_tokens:2048, enable_thinking:false, tool_choice:"auto", parallel_tool_calls:false — фактические параметры обычного run_js-профиля. Streaming здесь относится к LLM, аудиораспознавание остаётся batch. В ответе choices[].delta.content — фрагмент текста; delta.tool_calls может содержать код инструмента; usage — prompt/completion/total tokens; [DONE] завершает SSE. Оборванный/пустой ответ не считается успешным.

  24. LlmResponse. Ответ NATS содержит trace/correlation/request/session IDs, ok, data.response_text, data.tool_calls, data.usage, error и доступные timings. Пустой tool_calls означает, что Agent может завершить команду; непустой вызывает ветку инструмента. error:null соответствует ok:true, при ошибке есть code/message/details. ID R и G различаются намеренно: один вопрос может иметь несколько шагов модели.

  25. Инструмент страницы. Agent публикует kind:"widget_tool", stage:tool_call, request_id:R, client_id:U, context_id:C, sequence, name:"run_js", tool_call_id и arguments.code. Gateway адресует .events.tools. LocalMcp создаёт локальный JSON-RPC tools/call; это InMemoryTransport, не новый сервис. Background принимает script_run, проверяет привязку и вызывает Chrome userScripts.execute с target.tabId, target.documentIds, world:"USER_SCRIPT", js[].code. Внутренний dom helper использует target:"run_js", method, args → background → action:"script_dom" в исходный документ. Результат {ok,value} возвращается через Chrome/LocalMcp. При изменении DOM важны actual/errors внутри value, а не только внешний ok.

  26. browser_tool_result. Панель отправляет R/C/name/tool_call_id и result либо error. Gateway → chat.browser.core → chat.internal.agent.widget с action:"browser_tool_result", outcome и тем же владельцем. Agent разблокирует ожидание именно этого tool_call_id и передаёт результат в следующий LLM input как role:tool. Новый G не означает новый пользовательский вопрос. Дубликат с теми же аргументами не запускает JS повторно; timeout/unknown не гарантирует откат DOM.

  27. Окончательный ответ. kind:"widget_stage", stage:"done", R/S/U/C, возрастающий sequence, text, timings идут по nats.events.user123 → Gateway → chat.ui.U.events.ui. Панель завершает конкретный R и рисует Markdown. stage:"failed"/cancelled — другой терминальный исход. Ответ в показанном плагине текстовый, TTS не вызывается. Микрофон продолжает следующую фразу без нового подключения.

  28. Архив рядом с основным путём. Ingress отправляет chat.internal.audio_archive в фоновом writer: begin, append, heartbeat, event, finalize/fail. У append stream_id — recording ID, chunk_index — номер блока, pcm — base64 исходного PCM ≤24000 байт, sha256 — его хеш, packets — sequence/bytes/received_mono_ms. В PostgreSQL звук хранится как bytea в audio_chunks, с исходными coordinates, packets и waveform. Браузер отдельно отправляет audio_archive action:"manifest": sequence, chunk_index, bytes, first_seq/last_seq, sha256, packets с sent_mono_ms. В полном манифесте есть все пакеты блока. Так сверяется побайтовая полнота без второй отправки PCM из браузера.

  29. Настройки и подтверждение показа. audio_archive action:"settings" содержит requested/actual с полными именами noiseSuppression/echoCancellation/autoGainControl, success, capture epoch и microphone label. Requested:null означает «по умолчанию браузера», actual:null — «браузер не сообщил». capture хранит состояние muted/requesting/capturing/failed. receipt отправляется UI-владельцем с phrase_id, request_id, text, text_sha256 и displayed_at; подтверждает реально показанный ASR final. Это не повторный запрос модели.

  30. Выключение. Hardware track останавливается сразу. Уже начатая фраза получает reason:mute; только неподтверждённый кандидат не становится командой. При закрытии всего аудиоканала END содержит last_seq; ingress ждёт соответствующий PCM и pending-результаты, Core освобождает слот. Дополнительного flush/PING перед END нет. При простом mute канал может оставаться подключённым — END не вызывается после каждой фразы. Браузерный archive finish хранит итоговые packets/bytes и mono timestamps, server finalize идемпотентен и сверяет манифесты. Только {status:200,data:{ok:true,type:"stopped",error:null}} подтверждает нормальное завершение транспорта; событие stopped может прийти раньше RPC reply и не должно оборвать его ожидание.

Для диагностики Monitor/Compare используют общий batch backend; отличие — источник и адресат результата. Повтор архива Gateway читает на сервере, без скачивания PCM браузером и обратной загрузки. У него временная replay-сессия и свои stream/phrase IDs; результат читается через audio_replay action:"status", а не через новый chat message. Сохранённый звук в проигрывателе — исходный PCM Chrome до VAD и серверного ресэмплинга. Это отдельная диагностическая ветка, не новый этап каждого живого вопроса.

Граница подтверждения

Сверены исходники и desired-конфигурация на 30.10.2026: webrtc-komaroff 3e201723254ad48e53f6c9fa1eb9e9cca52c4338, py_faster_whisper ca436b4d7b3b2f2848743b6b04a8c33067798522, rag-stack b98aab1dcf9318d9a527f36e9e560b0cd2c94cef. Реальный микрофон/архив/повтор пакета 2.1.2 проверялись 29.10.2026 в предыдущем прогоне. Новый runtime rollout или замер задержки 30.10 ради схемы не выполнялся. Точный закрытый envelope nats-proxy остаётся отмеченным пробелом, схема не выдаёт его за исследованный.

Источники: браузерный NATS-клиент, захват и границы, Silero, ingress, batch upload, ASR Bridge, ASR Worker, Whisper parameters, возврат ASR, Agent, LLM HTTP/SSE, Chat stack, H100 worker service, requester, receiver/vLLM.

Исходник

Скачать Markdown · К материалам исследования