# Потоки и LLM: ограничить очередь до создания задач

> Исторический snapshot. После разделения chat/voice адреса и deployment ownership изменились: чат — https://chat-agent.gis-master.ru/, file UI/diagnostics — https://voice.gis-master.ru/. Текущая поставка: [DEPLOYMENT](</experiments/voice-ai/archive/w/DOCS/DEPLOYMENT.md>). Старые адреса и service owners ниже описывают дату исследования, а не инструкции для текущего runtime.

Статус: предложение, не реализовано. Инвазивность: **Средняя**. Основа: `webrtc-komaroff@772de4a`, 16.09.2026.

`WhisperStreamNode.handle_frame` создаёт task на каждый кадр. Semaphore ограничивает число одновременно выполняемых publish, но все остальные tasks уже существуют и удерживают payload. Такая же форма есть в LLM BaseService: входящие сообщения превращаются в tasks до semaphore.

![Где накапливается работа](</experiments/voice-ai/archive/w/DOCS/architecture-2026-09-16/diagrams/p03-bounds.svg>)

Ограничивать нужно входной буфер, а не только исполнителей. Иначе при медленном backend память и задержка продолжают расти, хотя число активных вызовов выглядит нормальным.

## Предлагаемая правка

Для аудио ввести один publisher-worker на stream и bounded queue с учётом **байтов и возраста**. Кадр поступает в очередь без отдельной task. При заполнении нужна явная политика: краткое ожидание в пределах latency budget, затем сигнал перегрузки и корректное завершение потока либо согласованный gap protocol. Молчаливое выпадение PCM нельзя считать безопасным: оно меняет временную шкалу и распознавание.

В LLM adapter использовать ограниченную очередь запросов и фиксированное число workers. До дорогостоящего inference проверять дедлайн. В контракты добавить deadline/cancellation совместимым способом: Pydantic сейчас запрещает лишние поля. Не полагаться на timeout ожидающего NATS request как на отмену работы responder.

Время очереди измерять отдельно. Текущий `duration_ms` LLM начинает отсчёт уже внутри semaphore и скрывает ожидание.

## Масштабирование звука требует ещё одного решения

Whisper хранит буфер в RAM по stream subject и получает wildcard без queue. Обычные реплики дублируют обработку. Случайная queue group для каждого PCM frame разрушит целостность потока. Для живого аудио закрепить stream за worker: ingress directory, стабильный shard-key или отдельный session owner. Core peer connection также остаётся у создавшего его процесса до завершения.

Эта часть пересекает оба репозитория и не должна попадать в MR только для WebRTC producer без совместимого consumer.

## Проверяемые пределы

При 16 кГц mono s16 один поток создаёт 50 кадров/с и 32 000 байт PCM/с. Например, **предлагаемый**, ещё не принятый бюджет 0.5 с — 25 кадров и 16 000 байт PCM на stream без overhead. Выбирать его нужно по допустимой задержке и тестам качества.

Приёмка: размер очереди никогда не превосходит заданного предела; количество tasks перестаёт зависеть от числа входных frames; очередь не содержит просроченных запросов после drain. Нагрузочный прогон: 1/10/50 потоков, искусственная пауза NATS/backend, reconnect и отсутствие end. Снять RSS, pending bytes, p95 queue wait, возраст старейшего кадра и число отказов admission.

Для deadline проверить согласованность source defaults и DRS: frontend default 70 с, DRS frontend 300 с; Agent default 60 с, DRS 270 с; Core DRS request timeout 600 с. Это разные конфигурации, а не доказанный production timeout-инцидент.

## Цена и откат

Появится явный отказ при перегрузке там, где раньше росло ожидание. Это нужно показать в UI. Вводить limits постепенно с метрикой would-reject, затем включать enforcement. При rollback протокол `frame/end` должен остаться совместимым; новые поля требуют versioned rollout.

## Основания

[Audio tasks](<https://git.giscloud.ru/voice-chat/webrtc-komaroff/-/blob/772de4a714ae6c5abea91946e85a1953ee919ec3/src_core/processors/whisper_stream_node.py>), [LLM BaseService](<https://git.giscloud.ru/voice-chat/webrtc-komaroff/-/blob/772de4a714ae6c5abea91946e85a1953ee919ec3/src_llm/utils/base_service.py>), [settings и DRS](<https://git.giscloud.ru/voice-chat/webrtc-komaroff/-/blob/772de4a714ae6c5abea91946e85a1953ee919ec3/stack/webrtc.drs>). [Python asyncio.Queue](<https://docs.python.org/3/library/asyncio-queue.html>) документирует `maxsize` и блокирующий put; `Queue.shutdown` появился только в Python 3.13, тогда как образы проекта — 3.11. Для текущего runtime нужен совместимый drain/cancel. Context7 NATS queue groups подтверждает случайное распределение сообщений внутри группы: оно не обеспечивает affinity сессии.

[Все предложения](</experiments/voice-ai/archive/w/DOCS/architecture-2026-09-16/improvements/README.md>) · [Фактическая архитектура](</experiments/voice-ai/archive/w/DOCS/architecture-2026-09-16/README.md>)
