{
  "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": 332
    },
    {
      "id": "02",
      "title": "Одна реплика — несколько границ",
      "chapter": "Запрос",
      "visual": "journey",
      "text": "Этот путь проходит через несколько сервисов. Каждый отвечает за свою часть: соединение, распознавание, состояние разговора или выполнение инструментов.",
      "source": "src_agent/service.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 332,
      "duration": 372
    },
    {
      "id": "03",
      "title": "Core принимает живой звук",
      "chapter": "Звук",
      "visual": "mic",
      "text": "Браузер передаёт микрофон по Веб эр ти си. Сервис Кор завершает соединение и превращает звук в короткие пакеты для распознавания.",
      "source": "src_core/handlers/handle_track.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 704,
      "duration": 315
    },
    {
      "id": "04",
      "title": "20 миллисекунд в одном кадре",
      "chapter": "Звук",
      "visual": "packets",
      "text": "В текущем формате кадр содержит двадцать миллисекунд звука. Пакеты идут через брокер сообщений НАТС в соседний проект распознавания.",
      "source": "src_core/processors/whisper_stream_node.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 1019,
      "duration": 299
    },
    {
      "id": "05",
      "title": "Whisper bridge выделяет фразу",
      "chapter": "Звук",
      "visual": "vad",
      "text": "Там детектор речи отделяет голос от тишины. Когда фраза закончена, сервис отправляет её внешнему распознавателю Линто и получает текст.",
      "source": "../py_faster_whisper/src/stt_whisper_to_nats.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 1318,
      "duration": 334
    },
    {
      "id": "06",
      "title": "Голос и текст сходятся",
      "chapter": "Диалог",
      "visual": "merge",
      "text": "Теперь голосовой и текстовый запросы идут одним путём. Кор передаёт Агенту текст и идентификаторы сессии и реплики.",
      "source": "src_core/handlers/handle_transcription.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 1652,
      "duration": 306
    },
    {
      "id": "07",
      "title": "Agent хранит разговор",
      "chapter": "Диалог",
      "visual": "database",
      "text": "Агент загружает состояние диалога из базы Постгрес. Так следующий вопрос может продолжить предыдущий сценарий, а не начинать разговор заново.",
      "source": "src_agent/agent.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 1958,
      "duration": 304
    },
    {
      "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": 2262,
      "duration": 285
    },
    {
      "id": "09",
      "title": "Модель разбирает намерение",
      "chapter": "Действие",
      "visual": "llm",
      "text": "Для нового запроса об аэропортах текущий сценарий обращается к языковой модели дважды: сначала выбирает намерение, затем извлекает параметры поиска.",
      "source": "src_langgraph_rb_node/spec/scenarios/find_nearest_airport_spec.rb",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 2547,
      "duration": 365
    },
    {
      "id": "10",
      "title": "Инструмент возвращает данные",
      "chapter": "Действие",
      "visual": "map",
      "text": "Сам поиск выполняет инструмент по каталогу аэропортов. Модель не должна придумывать координаты. Маршрут строится только при явном запросе пользователя.",
      "source": "src_api_gateway/main.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 2912,
      "duration": 337
    },
    {
      "id": "11",
      "title": "Результат приходит событием",
      "chapter": "Действие",
      "visual": "event",
      "text": "Агент сохраняет результат и публикует команды интерфейсу. Браузер получает событие и обновляет сообщение или карту. Ответ по эйч ти ти пи — отдельная часть обмена.",
      "source": "src_agent/service.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 3249,
      "duration": 386
    },
    {
      "id": "12",
      "title": "Стенд, а не источник авиационных данных",
      "chapter": "Границы",
      "visual": "limits",
      "text": "Здесь есть важная граница. Позиция, погода и рейсы в проверенном коде демонстрационные. Нарисованный маршрут не является дорожным маршрутом или инструкцией для полёта.",
      "source": "src_api_gateway/main.py",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 3635,
      "duration": 378
    },
    {
      "id": "13",
      "title": "Меньше ожидания перед стартом",
      "chapter": "Развитие",
      "visual": "bootstrap",
      "text": "Первое небольшое улучшение — начальная загрузка карты. Сейчас два запроса идут последовательно. Переиспользование соединения и параллельное выполнение уберут лишнее ожидание.",
      "source": "src_core/handlers/handle_init_map.py",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 4013,
      "duration": 379
    },
    {
      "id": "14",
      "title": "Очередь должна иметь предел",
      "chapter": "Развитие",
      "visual": "queue",
      "text": "Более серьёзная проблема — перегрузка. Семафор ограничивает активные вызовы, но ожидающие задачи уже созданы. Нужен предел очереди до приёма новой работы.",
      "source": "src_llm/utils/base_service.py",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 4392,
      "duration": 361
    },
    {
      "id": "15",
      "title": "Один turn — один сохранённый результат",
      "chapter": "Развитие",
      "visual": "atomic",
      "text": "Ещё важнее запись результата. Состояние и история сохраняются раздельно. При сбое повтор может снова вызвать сценарий. Нужны общая транзакция и защита от повторного исполнения.",
      "source": "src_agent/agent.py",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 4753,
      "duration": 429
    },
    {
      "id": "16",
      "title": "Три движка увеличивают цену изменений",
      "chapter": "Развитие",
      "visual": "runtimes",
      "text": "В репозитории три реализации сценарного движка. Прежде чем удалять две, надо сравнить поведение и проверить их пользователей. Переименование каталогов эту работу не заменяет.",
      "source": "stack/webrtc.drs",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 5182,
      "duration": 404
    },
    {
      "id": "17",
      "title": "Упростить границу диалога",
      "chapter": "Развитие",
      "visual": "consolidate",
      "text": "Следующий вариант — объединить Агент и выбранный сценарный движок. Исчезнет один сетевой переход, но пользу нужно измерить. Медиасоединения и модели разумно оставить отдельно.",
      "source": "src_agent/agent.py",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 5586,
      "duration": 384
    },
    {
      "id": "18",
      "title": "Измерить, затем менять",
      "chapter": "Итог",
      "visual": "metrics",
      "text": "Начинать стоит с очередей и корректности повторов. Затем считать вызовы модели, задержку ответа и ошибки. Полное переписывание без этих измерений пока не обосновано.",
      "source": "DOCS/architecture-2026-09-16/improvements/README.md",
      "status": "ПРЕДЛОЖЕНИЕ",
      "from": 5970,
      "duration": 378
    },
    {
      "id": "19",
      "title": "От фразы до понятного результата",
      "chapter": "Итог",
      "visual": "ending",
      "text": "Так короткая просьба превращается в цепочку проверяемых действий. Этот разбор основан на снимке кода. Он объясняет устройство проекта, но не подтверждает состояние работающего кластера.",
      "source": "DOCS/architecture-2026-09-16/evidence.md",
      "status": "ПО КОДУ · 16.09.2026",
      "from": 6348,
      "duration": 399
    }
  ],
  "subtitles": [
    {
      "from": 9,
      "to": 103,
      "text": "Вы говорите: «Покажи ближайшие аэропорты».",
      "scene": "01"
    },
    {
      "from": 106,
      "to": 319,
      "text": "webrtc-komaroff должен распознать реплику, выбрать сценарий и показать результат на карте.",
      "scene": "01"
    },
    {
      "from": 341,
      "to": 433,
      "text": "Этот путь проходит через несколько сервисов.",
      "scene": "02"
    },
    {
      "from": 436,
      "to": 657,
      "text": "Каждый отвечает за свою часть: соединение, распознавание, состояние разговора или выполнение",
      "scene": "02"
    },
    {
      "from": 660,
      "to": 691,
      "text": "инструментов.",
      "scene": "02"
    },
    {
      "from": 713,
      "to": 815,
      "text": "Браузер передаёт микрофон по WebRTC.",
      "scene": "03"
    },
    {
      "from": 818,
      "to": 1006,
      "text": "Сервис Core завершает соединение и превращает звук в короткие пакеты для распознавания.",
      "scene": "03"
    },
    {
      "from": 1028,
      "to": 1144,
      "text": "В текущем формате кадр содержит двадцать миллисекунд звука.",
      "scene": "04"
    },
    {
      "from": 1147,
      "to": 1305,
      "text": "Пакеты идут через брокер сообщений NATS в соседний проект распознавания.",
      "scene": "04"
    },
    {
      "from": 1327,
      "to": 1441,
      "text": "Там детектор речи отделяет голос от тишины.",
      "scene": "05"
    },
    {
      "from": 1444,
      "to": 1639,
      "text": "Когда фраза закончена, сервис отправляет её внешнему распознавателю LinTO и получает текст.",
      "scene": "05"
    },
    {
      "from": 1661,
      "to": 1785,
      "text": "Теперь голосовой и текстовый запросы идут одним путём.",
      "scene": "06"
    },
    {
      "from": 1788,
      "to": 1945,
      "text": "Core передаёт Агенту текст и идентификаторы сессии и реплики.",
      "scene": "06"
    },
    {
      "from": 1967,
      "to": 2086,
      "text": "Агент загружает состояние диалога из базы PostgreSQL.",
      "scene": "07"
    },
    {
      "from": 2089,
      "to": 2249,
      "text": "Так следующий вопрос может продолжить предыдущий сценарий, а не начинать разговор заново.",
      "scene": "07"
    },
    {
      "from": 2271,
      "to": 2341,
      "text": "Дальше работает сценарный движок.",
      "scene": "08"
    },
    {
      "from": 2344,
      "to": 2534,
      "text": "Он решает, какой инструмент нужен, какие данные уже есть и что ещё спросить у пользователя.",
      "scene": "08"
    },
    {
      "from": 2556,
      "to": 2795,
      "text": "Для нового запроса об аэропортах текущий сценарий обращается к языковой модели дважды: сначала выбирает",
      "scene": "09"
    },
    {
      "from": 2798,
      "to": 2899,
      "text": "намерение, затем извлекает параметры поиска.",
      "scene": "09"
    },
    {
      "from": 2921,
      "to": 3035,
      "text": "Сам поиск выполняет инструмент по каталогу аэропортов.",
      "scene": "10"
    },
    {
      "from": 3038,
      "to": 3106,
      "text": "Модель не должна придумывать координаты.",
      "scene": "10"
    },
    {
      "from": 3109,
      "to": 3236,
      "text": "Маршрут строится только при явном запросе пользователя.",
      "scene": "10"
    },
    {
      "from": 3258,
      "to": 3382,
      "text": "Агент сохраняет результат и публикует команды интерфейсу.",
      "scene": "11"
    },
    {
      "from": 3385,
      "to": 3509,
      "text": "Браузер получает событие и обновляет сообщение или карту.",
      "scene": "11"
    },
    {
      "from": 3512,
      "to": 3622,
      "text": "Ответ по HTTP — отдельная часть обмена.",
      "scene": "11"
    },
    {
      "from": 3644,
      "to": 3705,
      "text": "Здесь есть важная граница.",
      "scene": "12"
    },
    {
      "from": 3708,
      "to": 3835,
      "text": "Позиция, погода и рейсы в проверенном коде демонстрационные.",
      "scene": "12"
    },
    {
      "from": 3838,
      "to": 4000,
      "text": "Нарисованный маршрут не является дорожным маршрутом или инструкцией для полёта.",
      "scene": "12"
    },
    {
      "from": 4022,
      "to": 4136,
      "text": "Первое небольшое улучшение — начальная загрузка карты.",
      "scene": "13"
    },
    {
      "from": 4139,
      "to": 4223,
      "text": "Сейчас два запроса идут последовательно.",
      "scene": "13"
    },
    {
      "from": 4226,
      "to": 4379,
      "text": "Переиспользование соединения и параллельное выполнение уберут лишнее ожидание.",
      "scene": "13"
    },
    {
      "from": 4401,
      "to": 4486,
      "text": "Более серьёзная проблема — перегрузка.",
      "scene": "14"
    },
    {
      "from": 4489,
      "to": 4647,
      "text": "Семафор ограничивает активные вызовы, но ожидающие задачи уже созданы.",
      "scene": "14"
    },
    {
      "from": 4650,
      "to": 4740,
      "text": "Нужен предел очереди до приёма новой работы.",
      "scene": "14"
    },
    {
      "from": 4762,
      "to": 4835,
      "text": "Ещё важнее запись результата.",
      "scene": "15"
    },
    {
      "from": 4838,
      "to": 4935,
      "text": "Состояние и история сохраняются раздельно.",
      "scene": "15"
    },
    {
      "from": 4938,
      "to": 5036,
      "text": "При сбое повтор может снова вызвать сценарий.",
      "scene": "15"
    },
    {
      "from": 5039,
      "to": 5169,
      "text": "Нужны общая транзакция и защита от повторного исполнения.",
      "scene": "15"
    },
    {
      "from": 5191,
      "to": 5301,
      "text": "В репозитории три реализации сценарного движка.",
      "scene": "16"
    },
    {
      "from": 5304,
      "to": 5475,
      "text": "Прежде чем удалять две, надо сравнить поведение и проверить их пользователей.",
      "scene": "16"
    },
    {
      "from": 5478,
      "to": 5573,
      "text": "Переименование каталогов эту работу не заменяет.",
      "scene": "16"
    },
    {
      "from": 5595,
      "to": 5722,
      "text": "Следующий вариант — объединить Агент и выбранный сценарный движок.",
      "scene": "17"
    },
    {
      "from": 5725,
      "to": 5847,
      "text": "Исчезнет один сетевой переход, но пользу нужно измерить.",
      "scene": "17"
    },
    {
      "from": 5850,
      "to": 5957,
      "text": "Медиасоединения и модели разумно оставить отдельно.",
      "scene": "17"
    },
    {
      "from": 5979,
      "to": 6089,
      "text": "Начинать стоит с очередей и корректности повторов.",
      "scene": "18"
    },
    {
      "from": 6092,
      "to": 6225,
      "text": "Затем считать вызовы модели, задержку ответа и ошибки.",
      "scene": "18"
    },
    {
      "from": 6228,
      "to": 6335,
      "text": "Полное переписывание без этих измерений пока не обосновано.",
      "scene": "18"
    },
    {
      "from": 6357,
      "to": 6482,
      "text": "Так короткая просьба превращается в цепочку проверяемых действий.",
      "scene": "19"
    },
    {
      "from": 6485,
      "to": 6571,
      "text": "Этот разбор основан на снимке кода.",
      "scene": "19"
    },
    {
      "from": 6574,
      "to": 6734,
      "text": "Он объясняет устройство проекта, но не подтверждает состояние работающего кластера.",
      "scene": "19"
    }
  ],
  "totalFrames": 6747,
  "voice": {
    "engine": "Fish Audio",
    "model": "s2.1-pro-free",
    "speed": 1.14,
    "referenceSha256": "9415f693598c54d004b6ac20d7a750527a8f5321281f74a843cadc86917a63c0",
    "sampleRate": 48000,
    "syntheticVoiceDisclosure": true
  }
}
