Как читать 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 с > не производится.
Порядок и поля сообщений
-
Доступ к каналам. Расширение вызывает
/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. -
Подключение и подписки.
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. -
Регистрация
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 не собирается при регистрации. -
Открытие аудио
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; другие модели доступны через диагностические интерфейсы. -
Внутренний
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. -
Допуск
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. -
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 удалены. -
Исходный звук.
getUserMediaпринимаетaudio.noiseSuppression,echoCancellation,autoGainControlиvideo:false; значения на схеме — синтетический выбор. Requested и реальныеtrack.getSettings()сохраняются отдельно. AudioContext даёт Float32. Worklet ограничивает диапазон [-1,1], квантует в int16 и выдаёт блоки по 1024 отсчёта.MessagePortпередаёт ArrayBuffer, обычный размер 2048 байт. Локальныйackосвобождает ожидание Worklet; это не сетевой RPC. -
Пакет
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. Тишина передаётся вместе с речью. -
Аналитическая копия Silero. Worker получает
{type:"init",rate,offset}и{type:"pcm",pcm:ArrayBuffer}.rate— частота источника,offset— накопленный счёт исходных отсчётов. Только эта копия превращается int16→float32 и непрерывно 48→16 кГц через libsamplerate-js 2.1.2SRC_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. -
Начало и конец
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 мс — настройка, не обещание точного момента доставки. Для повторной доставки той же границы возвращается прежняя квитанция; конфликт координат не принимается. -
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, а не номером последнего байта пакета. -
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 байт. -
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× нет. -
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 в этой ветке не выполняются. -
Ответ worker и окончательный ASR. Worker возвращает
text,decode_ms, backend/model, scores, parameters, score_filtering:false, model_input, transport и words.avg_logprob,no_speech_prob,compression_ratio— диагностические оценки; они не отбрасывают непустой текст. Обратный subjectinference.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, а не ложный успех. -
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 и инструменты не запускает. -
Команда чата
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. -
Допуск Agent и PostgreSQL. Agent сохраняет квитанцию
(session_id,request_id,command_hash,client_id), после этого активирует выполнение. Значение хеша защищает от другого текста под тем же R. В текущем Agent 3 исполнителя, до 9 ожидающих, per_session=2. Ошибки этой очереди относятся к последующей обработке команды, не к ASR. Перед итоговым событием сохраняется финальная квитанция state/diagnostic/timings; это не автоматический повтор работы после перезапуска. -
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 ожидающих. -
Подсчёт
/tokenize. Перед генерацией есть самостоятельный HTTP roundtrip.model— served model Qwen3.8-27B-FP8,messagesиtools— фактический вход,add_generation_prompt:true— учёт шаблона начала ответа,chat_template_kwargs.enable_thinking:false— выбранный режим. Ответcount— число входных токенов, не длительность. Если нужен повтор подсчёта после удаления необязательного контекста, это отдельный HTTP-вызов. Входные инструкции/текущий запрос не заменяются произвольным обрезанием. -
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 subscriptioninference.vllm.requests.>, ответы возвращаются через rootsinference.vllm→from.inference.vllm. Receiver — один из llm-models_nats_proxy_vllm / _02, queue group vllm-gateway-receivers; upstreamhttp://llm-models_vllm_openai:8000. Существенный payload — HTTP method/path/headers/JSON и обратные status/headers/SSE. Точные поля внешнего конверта, tunnel/chunk framing и dynamic suffix закрытого образа dtorry/nats-proxy исходниками этой проверки не подтверждены. На схеме они сознательно не заменены выдуманной JSON-схемой. -
Генерация
/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. Оборванный/пустой ответ не считается успешным. -
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 различаются намеренно: один вопрос может иметь несколько шагов модели. -
Инструмент страницы. 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-RPCtools/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. -
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. -
Окончательный ответ.
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 не вызывается. Микрофон продолжает следующую фразу без нового подключения. -
Архив рядом с основным путём. Ingress отправляет
chat.internal.audio_archiveв фоновом writer: begin, append, heartbeat, event, finalize/fail. У appendstream_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 из браузера. -
Настройки и подтверждение показа.
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. Это не повторный запрос модели. -
Выключение. 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.