{
  "title": "webrtc-komaroff",
  "subtitle": "Как голос превращается в действие",
  "fps": 30,
  "width": 1280,
  "height": 720,
  "language": "ru",
  "caption_replacements": {
    "Веб эр ти си Комаров": "webrtc-komaroff",
    "Веб эр ти си": "WebRTC",
    "НАТС": "NATS",
    "Линто": "LinTO",
    "Постгрес": "PostgreSQL",
    "эйч ти ти пи": "HTTP",
    "Кор": "Core"
  },
  "source_commit": "772de4a714ae6c5abea91946e85a1953ee919ec3",
  "scenes": [
    {
      "id": "01",
      "title": "От голоса к действию",
      "chapter": "Запрос",
      "visual": "hook",
      "text": "Вы говорите: «Покажи ближайшие аэропорты». Веб эр ти си Комаров должен распознать реплику, выбрать сценарий и показать результат на карте.",
      "source": "src_core/handlers/handle_transcription.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 0,
      "duration": 265
    },
    {
      "id": "02",
      "title": "Одна реплика — несколько границ",
      "chapter": "Запрос",
      "visual": "journey",
      "text": "Этот путь проходит через несколько сервисов. Каждый отвечает за свою часть: соединение, распознавание, состояние разговора или выполнение инструментов.",
      "source": "src_agent/service.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 265,
      "duration": 320
    },
    {
      "id": "03",
      "title": "Core принимает живой звук",
      "chapter": "Звук",
      "visual": "mic",
      "text": "Браузер передаёт микрофон по Веб эр ти си. Сервис Кор завершает соединение и превращает звук в короткие пакеты для распознавания.",
      "source": "src_core/handlers/handle_track.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 585,
      "duration": 251
    },
    {
      "id": "04",
      "title": "20 миллисекунд в одном кадре",
      "chapter": "Звук",
      "visual": "packets",
      "text": "В текущем формате кадр содержит двадцать миллисекунд звука. Пакеты идут через брокер сообщений НАТС в соседний проект распознавания.",
      "source": "src_core/processors/whisper_stream_node.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 836,
      "duration": 239
    },
    {
      "id": "05",
      "title": "Whisper bridge выделяет фразу",
      "chapter": "Звук",
      "visual": "vad",
      "text": "Там детектор речи отделяет голос от тишины. Когда фраза закончена, сервис отправляет её внешнему распознавателю Линто и получает текст.",
      "source": "../py_faster_whisper/src/stt_whisper_to_nats.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 1075,
      "duration": 267
    },
    {
      "id": "06",
      "title": "Голос и текст сходятся",
      "chapter": "Диалог",
      "visual": "merge",
      "text": "Теперь голосовой и текстовый запросы идут одним путём. Кор передаёт Агенту текст и идентификаторы сессии и реплики.",
      "source": "src_core/handlers/handle_transcription.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 1342,
      "duration": 236
    },
    {
      "id": "07",
      "title": "Agent хранит разговор",
      "chapter": "Диалог",
      "visual": "database",
      "text": "Агент загружает состояние диалога из базы Постгрес. Так следующий вопрос может продолжить предыдущий сценарий, а не начинать разговор заново.",
      "source": "src_agent/agent.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 1578,
      "duration": 263
    },
    {
      "id": "08",
      "title": "Workflow выбирает следующий шаг",
      "chapter": "Диалог",
      "visual": "workflow",
      "text": "Дальше работает сценарный движок. Он решает, какой инструмент нужен, какие данные уже есть и что ещё спросить у пользователя.",
      "source": "src_langgraph_rb_node/lib/src_langgraph_rb_node/runtime/engine.rb",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 1841,
      "duration": 245
    },
    {
      "id": "09",
      "title": "Модель разбирает намерение",
      "chapter": "Действие",
      "visual": "llm",
      "text": "Для нового запроса об аэропортах текущий сценарий обращается к языковой модели дважды: сначала выбирает намерение, затем извлекает параметры поиска.",
      "source": "src_langgraph_rb_node/spec/scenarios/find_nearest_airport_spec.rb",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 2086,
      "duration": 302
    },
    {
      "id": "10",
      "title": "Инструмент возвращает данные",
      "chapter": "Действие",
      "visual": "map",
      "text": "Сам поиск выполняет инструмент по каталогу аэропортов. Модель не должна придумывать координаты. Маршрут строится только при явном запросе пользователя.",
      "source": "src_api_gateway/main.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 2388,
      "duration": 286
    },
    {
      "id": "11",
      "title": "Результат приходит событием",
      "chapter": "Действие",
      "visual": "event",
      "text": "Агент сохраняет результат и публикует команды интерфейсу. Браузер получает событие и обновляет сообщение или карту. Ответ по эйч ти ти пи — отдельная часть обмена.",
      "source": "src_agent/service.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 2674,
      "duration": 329
    },
    {
      "id": "12",
      "title": "Стенд, а не источник авиационных данных",
      "chapter": "Границы",
      "visual": "limits",
      "text": "Здесь есть важная граница. Позиция, погода и рейсы в проверенном коде демонстрационные. Нарисованный маршрут не является дорожным маршрутом или инструкцией для полёта.",
      "source": "src_api_gateway/main.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 3003,
      "duration": 322
    },
    {
      "id": "13",
      "title": "Меньше ожидания перед стартом",
      "chapter": "Развитие",
      "visual": "bootstrap",
      "text": "Первое небольшое улучшение — начальная загрузка карты. Сейчас два запроса идут последовательно. Переиспользование соединения и параллельное выполнение уберут лишнее ожидание.",
      "source": "src_core/handlers/handle_init_map.py",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 3325,
      "duration": 326
    },
    {
      "id": "14",
      "title": "Очередь должна иметь предел",
      "chapter": "Развитие",
      "visual": "queue",
      "text": "Более серьёзная проблема — перегрузка. Семафор ограничивает активные вызовы, но ожидающие задачи уже созданы. Нужен предел очереди до приёма новой работы.",
      "source": "src_llm/utils/base_service.py",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 3651,
      "duration": 322
    },
    {
      "id": "15",
      "title": "Один turn — один сохранённый результат",
      "chapter": "Развитие",
      "visual": "atomic",
      "text": "Ещё важнее запись результата. Состояние и история сохраняются раздельно. При сбое повтор может снова вызвать сценарий. Нужны общая транзакция и защита от повторного исполнения.",
      "source": "src_agent/agent.py",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 3973,
      "duration": 363
    },
    {
      "id": "16",
      "title": "Три движка увеличивают цену изменений",
      "chapter": "Развитие",
      "visual": "runtimes",
      "text": "В репозитории три реализации сценарного движка. Прежде чем удалять две, надо сравнить поведение и проверить их пользователей. Переименование каталогов эту работу не заменяет.",
      "source": "stack/webrtc.drs",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 4336,
      "duration": 319
    },
    {
      "id": "17",
      "title": "Упростить границу диалога",
      "chapter": "Развитие",
      "visual": "consolidate",
      "text": "Следующий вариант — объединить Агент и выбранный сценарный движок. Исчезнет один сетевой переход, но пользу нужно измерить. Медиасоединения и модели разумно оставить отдельно.",
      "source": "src_agent/agent.py",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 4655,
      "duration": 334
    },
    {
      "id": "18",
      "title": "Измерить, затем менять",
      "chapter": "Итог",
      "visual": "metrics",
      "text": "Начинать стоит с очередей и корректности повторов. Затем считать вызовы модели, задержку ответа и ошибки. Полное переписывание без этих измерений пока не обосновано.",
      "source": "DOCS/architecture-2026-09-16/improvements/README.md",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 4989,
      "duration": 317
    },
    {
      "id": "19",
      "title": "От фразы до понятного результата",
      "chapter": "Итог",
      "visual": "ending",
      "text": "Так короткая просьба превращается в цепочку проверяемых действий. Этот разбор основан на снимке кода. Он объясняет устройство проекта, но не подтверждает состояние работающего кластера.",
      "source": "DOCS/architecture-2026-09-16/evidence.md",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 5306,
      "duration": 338
    }
  ],
  "subtitles": [
    {
      "from": 9,
      "to": 90,
      "text": "Вы говорите: «Покажи ближайшие аэропорты».",
      "scene": "01"
    },
    {
      "from": 93,
      "to": 252,
      "text": "webrtc-komaroff должен распознать реплику, выбрать сценарий и показать результат на карте.",
      "scene": "01"
    },
    {
      "from": 274,
      "to": 348,
      "text": "Этот путь проходит через несколько сервисов.",
      "scene": "02"
    },
    {
      "from": 351,
      "to": 537,
      "text": "Каждый отвечает за свою часть: соединение, распознавание, состояние разговора или выполнение",
      "scene": "02"
    },
    {
      "from": 540,
      "to": 572,
      "text": "инструментов.",
      "scene": "02"
    },
    {
      "from": 594,
      "to": 674,
      "text": "Браузер передаёт микрофон по WebRTC.",
      "scene": "03"
    },
    {
      "from": 677,
      "to": 823,
      "text": "Сервис Core завершает соединение и превращает звук в короткие пакеты для распознавания.",
      "scene": "03"
    },
    {
      "from": 845,
      "to": 942,
      "text": "В текущем формате кадр содержит двадцать миллисекунд звука.",
      "scene": "04"
    },
    {
      "from": 945,
      "to": 1062,
      "text": "Пакеты идут через брокер сообщений NATS в соседний проект распознавания.",
      "scene": "04"
    },
    {
      "from": 1084,
      "to": 1162,
      "text": "Там детектор речи отделяет голос от тишины.",
      "scene": "05"
    },
    {
      "from": 1165,
      "to": 1329,
      "text": "Когда фраза закончена, сервис отправляет её внешнему распознавателю LinTO и получает текст.",
      "scene": "05"
    },
    {
      "from": 1351,
      "to": 1446,
      "text": "Теперь голосовой и текстовый запросы идут одним путём.",
      "scene": "06"
    },
    {
      "from": 1449,
      "to": 1565,
      "text": "Core передаёт Агенту текст и идентификаторы сессии и реплики.",
      "scene": "06"
    },
    {
      "from": 1587,
      "to": 1682,
      "text": "Агент загружает состояние диалога из базы PostgreSQL.",
      "scene": "07"
    },
    {
      "from": 1685,
      "to": 1828,
      "text": "Так следующий вопрос может продолжить предыдущий сценарий, а не начинать разговор заново.",
      "scene": "07"
    },
    {
      "from": 1850,
      "to": 1913,
      "text": "Дальше работает сценарный движок.",
      "scene": "08"
    },
    {
      "from": 1916,
      "to": 2073,
      "text": "Он решает, какой инструмент нужен, какие данные уже есть и что ещё спросить у пользователя.",
      "scene": "08"
    },
    {
      "from": 2095,
      "to": 2277,
      "text": "Для нового запроса об аэропортах текущий сценарий обращается к языковой модели дважды: сначала выбирает",
      "scene": "09"
    },
    {
      "from": 2280,
      "to": 2375,
      "text": "намерение, затем извлекает параметры поиска.",
      "scene": "09"
    },
    {
      "from": 2397,
      "to": 2495,
      "text": "Сам поиск выполняет инструмент по каталогу аэропортов.",
      "scene": "10"
    },
    {
      "from": 2498,
      "to": 2566,
      "text": "Модель не должна придумывать координаты.",
      "scene": "10"
    },
    {
      "from": 2569,
      "to": 2661,
      "text": "Маршрут строится только при явном запросе пользователя.",
      "scene": "10"
    },
    {
      "from": 2683,
      "to": 2782,
      "text": "Агент сохраняет результат и публикует команды интерфейсу.",
      "scene": "11"
    },
    {
      "from": 2785,
      "to": 2897,
      "text": "Браузер получает событие и обновляет сообщение или карту.",
      "scene": "11"
    },
    {
      "from": 2900,
      "to": 2990,
      "text": "Ответ по HTTP — отдельная часть обмена.",
      "scene": "11"
    },
    {
      "from": 3012,
      "to": 3068,
      "text": "Здесь есть важная граница.",
      "scene": "12"
    },
    {
      "from": 3071,
      "to": 3180,
      "text": "Позиция, погода и рейсы в проверенном коде демонстрационные.",
      "scene": "12"
    },
    {
      "from": 3183,
      "to": 3312,
      "text": "Нарисованный маршрут не является дорожным маршрутом или инструкцией для полёта.",
      "scene": "12"
    },
    {
      "from": 3334,
      "to": 3434,
      "text": "Первое небольшое улучшение — начальная загрузка карты.",
      "scene": "13"
    },
    {
      "from": 3437,
      "to": 3506,
      "text": "Сейчас два запроса идут последовательно.",
      "scene": "13"
    },
    {
      "from": 3509,
      "to": 3638,
      "text": "Переиспользование соединения и параллельное выполнение уберут лишнее ожидание.",
      "scene": "13"
    },
    {
      "from": 3660,
      "to": 3739,
      "text": "Более серьёзная проблема — перегрузка.",
      "scene": "14"
    },
    {
      "from": 3742,
      "to": 3876,
      "text": "Семафор ограничивает активные вызовы, но ожидающие задачи уже созданы.",
      "scene": "14"
    },
    {
      "from": 3879,
      "to": 3960,
      "text": "Нужен предел очереди до приёма новой работы.",
      "scene": "14"
    },
    {
      "from": 3982,
      "to": 4044,
      "text": "Ещё важнее запись результата.",
      "scene": "15"
    },
    {
      "from": 4047,
      "to": 4129,
      "text": "Состояние и история сохраняются раздельно.",
      "scene": "15"
    },
    {
      "from": 4132,
      "to": 4210,
      "text": "При сбое повтор может снова вызвать сценарий.",
      "scene": "15"
    },
    {
      "from": 4213,
      "to": 4323,
      "text": "Нужны общая транзакция и защита от повторного исполнения.",
      "scene": "15"
    },
    {
      "from": 4345,
      "to": 4430,
      "text": "В репозитории три реализации сценарного движка.",
      "scene": "16"
    },
    {
      "from": 4433,
      "to": 4557,
      "text": "Прежде чем удалять две, надо сравнить поведение и проверить их пользователей.",
      "scene": "16"
    },
    {
      "from": 4560,
      "to": 4642,
      "text": "Переименование каталогов эту работу не заменяет.",
      "scene": "16"
    },
    {
      "from": 4664,
      "to": 4777,
      "text": "Следующий вариант — объединить Агент и выбранный сценарный движок.",
      "scene": "17"
    },
    {
      "from": 4780,
      "to": 4884,
      "text": "Исчезнет один сетевой переход, но пользу нужно измерить.",
      "scene": "17"
    },
    {
      "from": 4887,
      "to": 4976,
      "text": "Медиасоединения и модели разумно оставить отдельно.",
      "scene": "17"
    },
    {
      "from": 4998,
      "to": 5083,
      "text": "Начинать стоит с очередей и корректности повторов.",
      "scene": "18"
    },
    {
      "from": 5086,
      "to": 5193,
      "text": "Затем считать вызовы модели, задержку ответа и ошибки.",
      "scene": "18"
    },
    {
      "from": 5196,
      "to": 5293,
      "text": "Полное переписывание без этих измерений пока не обосновано.",
      "scene": "18"
    },
    {
      "from": 5315,
      "to": 5419,
      "text": "Так короткая просьба превращается в цепочку проверяемых действий.",
      "scene": "19"
    },
    {
      "from": 5422,
      "to": 5489,
      "text": "Этот разбор основан на снимке кода.",
      "scene": "19"
    },
    {
      "from": 5492,
      "to": 5631,
      "text": "Он объясняет устройство проекта, но не подтверждает состояние работающего кластера.",
      "scene": "19"
    }
  ],
  "totalFrames": 5644,
  "voice": {
    "engine": "Silero TTS v5.5",
    "speaker": "xenia",
    "model": "v5_5_ru.pt",
    "modelSha256": "50081637b602126ee06cb3bc8a744d25651d2da149ee8864b9a379bfdd934437",
    "sampleRate": 48000,
    "threads": 4,
    "autoStressYoHomographs": true
  }
}
