{
  "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"
    },
    {
      "id": "02",
      "title": "Одна реплика — несколько границ",
      "chapter": "Запрос",
      "visual": "journey",
      "text": "Этот путь проходит через несколько сервисов. Каждый отвечает за свою часть: соединение, распознавание, состояние разговора или выполнение инструментов.",
      "source": "src_agent/service.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "03",
      "title": "Core принимает живой звук",
      "chapter": "Звук",
      "visual": "mic",
      "text": "Браузер передаёт микрофон по Веб эр ти си. Сервис Кор завершает соединение и превращает звук в короткие пакеты для распознавания.",
      "source": "src_core/handlers/handle_track.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "04",
      "title": "20 миллисекунд в одном кадре",
      "chapter": "Звук",
      "visual": "packets",
      "text": "В текущем формате кадр содержит двадцать миллисекунд звука. Пакеты идут через брокер сообщений НАТС в соседний проект распознавания.",
      "source": "src_core/processors/whisper_stream_node.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "05",
      "title": "Whisper bridge выделяет фразу",
      "chapter": "Звук",
      "visual": "vad",
      "text": "Там детектор речи отделяет голос от тишины. Когда фраза закончена, сервис отправляет её внешнему распознавателю Линто и получает текст.",
      "source": "../py_faster_whisper/src/stt_whisper_to_nats.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "06",
      "title": "Голос и текст сходятся",
      "chapter": "Диалог",
      "visual": "merge",
      "text": "Теперь голосовой и текстовый запросы идут одним путём. Кор передаёт Агенту текст и идентификаторы сессии и реплики.",
      "source": "src_core/handlers/handle_transcription.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "07",
      "title": "Agent хранит разговор",
      "chapter": "Диалог",
      "visual": "database",
      "text": "Агент загружает состояние диалога из базы Постгрес. Так следующий вопрос может продолжить предыдущий сценарий, а не начинать разговор заново.",
      "source": "src_agent/agent.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "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"
    },
    {
      "id": "09",
      "title": "Модель разбирает намерение",
      "chapter": "Действие",
      "visual": "llm",
      "text": "Для нового запроса об аэропортах текущий сценарий обращается к языковой модели дважды: сначала выбирает намерение, затем извлекает параметры поиска.",
      "source": "src_langgraph_rb_node/spec/scenarios/find_nearest_airport_spec.rb",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "10",
      "title": "Инструмент возвращает данные",
      "chapter": "Действие",
      "visual": "map",
      "text": "Сам поиск выполняет инструмент по каталогу аэропортов. Модель не должна придумывать координаты. Маршрут строится только при явном запросе пользователя.",
      "source": "src_api_gateway/main.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "11",
      "title": "Результат приходит событием",
      "chapter": "Действие",
      "visual": "event",
      "text": "Агент сохраняет результат и публикует команды интерфейсу. Браузер получает событие и обновляет сообщение или карту. Ответ по эйч ти ти пи — отдельная часть обмена.",
      "source": "src_agent/service.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "12",
      "title": "Стенд, а не источник авиационных данных",
      "chapter": "Границы",
      "visual": "limits",
      "text": "Здесь есть важная граница. Позиция, погода и рейсы в проверенном коде демонстрационные. Нарисованный маршрут не является дорожным маршрутом или инструкцией для полёта.",
      "source": "src_api_gateway/main.py",
      "status": "ПО КОДУ · 16.09.2026"
    },
    {
      "id": "13",
      "title": "Меньше ожидания перед стартом",
      "chapter": "Развитие",
      "visual": "bootstrap",
      "text": "Первое небольшое улучшение — начальная загрузка карты. Сейчас два запроса идут последовательно. Переиспользование соединения и параллельное выполнение уберут лишнее ожидание.",
      "source": "src_core/handlers/handle_init_map.py",
      "status": "ПРЕДЛОЖЕНИЕ"
    },
    {
      "id": "14",
      "title": "Очередь должна иметь предел",
      "chapter": "Развитие",
      "visual": "queue",
      "text": "Более серьёзная проблема — перегрузка. Семафор ограничивает активные вызовы, но ожидающие задачи уже созданы. Нужен предел очереди до приёма новой работы.",
      "source": "src_llm/utils/base_service.py",
      "status": "ПРЕДЛОЖЕНИЕ"
    },
    {
      "id": "15",
      "title": "Один turn — один сохранённый результат",
      "chapter": "Развитие",
      "visual": "atomic",
      "text": "Ещё важнее запись результата. Состояние и история сохраняются раздельно. При сбое повтор может снова вызвать сценарий. Нужны общая транзакция и защита от повторного исполнения.",
      "source": "src_agent/agent.py",
      "status": "ПРЕДЛОЖЕНИЕ"
    },
    {
      "id": "16",
      "title": "Три движка увеличивают цену изменений",
      "chapter": "Развитие",
      "visual": "runtimes",
      "text": "В репозитории три реализации сценарного движка. Прежде чем удалять две, надо сравнить поведение и проверить их пользователей. Переименование каталогов эту работу не заменяет.",
      "source": "stack/webrtc.drs",
      "status": "ПРЕДЛОЖЕНИЕ"
    },
    {
      "id": "17",
      "title": "Упростить границу диалога",
      "chapter": "Развитие",
      "visual": "consolidate",
      "text": "Следующий вариант — объединить Агент и выбранный сценарный движок. Исчезнет один сетевой переход, но пользу нужно измерить. Медиасоединения и модели разумно оставить отдельно.",
      "source": "src_agent/agent.py",
      "status": "ПРЕДЛОЖЕНИЕ"
    },
    {
      "id": "18",
      "title": "Измерить, затем менять",
      "chapter": "Итог",
      "visual": "metrics",
      "text": "Начинать стоит с очередей и корректности повторов. Затем считать вызовы модели, задержку ответа и ошибки. Полное переписывание без этих измерений пока не обосновано.",
      "source": "DOCS/architecture-2026-09-16/improvements/README.md",
      "status": "ПРЕДЛОЖЕНИЕ"
    },
    {
      "id": "19",
      "title": "От фразы до понятного результата",
      "chapter": "Итог",
      "visual": "ending",
      "text": "Так короткая просьба превращается в цепочку проверяемых действий. Этот разбор основан на снимке кода. Он объясняет устройство проекта, но не подтверждает состояние работающего кластера.",
      "source": "DOCS/architecture-2026-09-16/evidence.md",
      "status": "ПО КОДУ · 16.09.2026"
    }
  ]
}
