Короткий учебник · 6-7 октября 2026

Как мы переделали chat-agent за два дня

Начали со списка проблем: готовые сценарии ограничивали агента, запросы ждали друг друга, а аудио могло зависнуть без понятной ошибки. Сначала исправили выполнение запросов и голосовой тракт. Затем добавили выбор инструментов, вычисления и подтверждение действий.

01Перестали заставлять чаты ждать друг друга

Раньше Agent обрабатывал запрос целиком внутри одного обработчика. Долгий запрос задерживал следующие. Для простого разговора модель сначала выбирала сценарий, а потом отдельно писала ответ.

Теперь у Agent три обработчика и ограниченная очередь: девять ожидающих запросов, максимум два от одной сессии. Порядок внутри чата сохраняется. Независимые чаты могут выполнять подготовку и инструменты параллельно. У LLM пока одна активная генерация и три места ожидания: возможности GPU мы не увеличивали.

Обычный разговор обходится одной генерацией. Текст приходит частями. События связаны с конкретным чатом и запросом, поэтому чужой ответ или команда карты не должны попадать в открытый чат.

Вместо разных таймаутов по цепочке действует общий бюджет 120 секунд активной работы. Отмена доходит до следующих этапов; новые инструменты после неё не запускаются. "Остановить микрофон" и "Отменить ответ" стали отдельными действиями.

Код: очередь Agent, deadline и ограничения выполнения.

02Агент собирает решение из инструментов

Раньше модель выбирала один из трёх Ruby-сценариев. Новая задача часто требовала нового графа. Теперь Pydantic AI ведёт диалог с моделью и принимает типизированные вызовы разрешённых инструментов.

Как изменился выбор действий Раньше запрос проходил через модель-роутер и один из трёх сценариев. Теперь агент выбирает инструменты, выполняет вычисления и возвращает ответ либо предложение на подтверждение. БЫЛО Модель-роутер Один из 3 сценариев Модель пишет ответ СТАЛО Pydantic AIвыбирает действия Инструменты + Montyчтение и вычисления Ответ / предложениеизменение требует решения Обычный разговор: агент сразу отвечает, без вызовов инструментов.
Схема показывает выбор действий внутри Agent. Вызовы модели по-прежнему проходят через наш LLM gateway.

В Code Mode модель пишет небольшой код, который читает данные через восемь разрешённых функций и считает результат в Monty. Ещё три инструмента отдельно показывают точки, маршрут и предлагают сохранение плана. Названия и аргументы проверяются до выполнения.

Пример"Найди ближайшие открытые аэропорты с подходящей полосой и хорошей погодой". Агент получает данные, фильтрует аэропорты, сравнивает расстояния и показывает результат. Для этого сочетания не нужен отдельный сценарий.

Monty ограничен 64 МиБ памяти и 10 секундами вычислений. Доступа к файлам, сети и секретам нет. Общий бюджет запроса - десять инструментальных действий, включая вложенные вызовы. Ошибку нового агента больше не маскируем переходом в старый сценарий.

Модель заменили на Qwen3.8-27B-FP8 через существующий vLLM на H100. Путь сохранили: Agent → NATS → наш LLM gateway → существующий requester → vLLM. Вход считаем tokenizer этой модели: до 6144 токенов, ответ до 2048. Старый Ruby оставлен для совместимости с незавершёнными сценариями.

Код: native tools, подтверждение и Monty, бюджет и запрос к модели.

03Модель предлагает, пользователь разрешает запись

В начале запроса Core фиксирует положение самолёта, состояния аэропортов и погоду. Все инструменты работают с этим снимком. Данные не меняются под запросом, пока он ждёт очередь.

Путь сохранения плана Снимок данных приводит к черновику, который ждёт решения пользователя. Подтверждение проходит проверку версий; только после неё план записывается один раз. Устаревший черновик пересчитывается и требует нового подтверждения. Снимок данных Черновик в БД Решение человека Проверка версий Одна запись Данные устарели → новое предложение. Отказ → сохранённый план не меняется.
Ожидание человека освобождает обработчик и не расходует 120-секундный бюджет активной работы.

Черновик хранится в PostgreSQL без автоматического таймаута, переживает reload и перезапуск. При подтверждении проверяем положение, использованные данные и версию плана. Повторный клик не создаёт вторую запись. Для изменения уже сохранённого плана снова требуется подтверждение.

Заодно исправили составные запросы: расчёт и ответ должны появиться до ожидания решения. При отказе или подтверждении вычисленный текст сохраняется. Сам факт сохранения сообщаем по результату БД, без дополнительной генерации, которая могла бы его переиначить.

Код: снимок запроса, предложение и проверка подтверждения.

04У аудио появился общий, управляемый путь

WebRTC вынесли из Core в отдельный ingress. Добавили второй вход по WSS. Core отвечает за запросы, историю, общий лимит подключений и приём финальной транскрипции.

Два аудиовхода используют один формат для Whisper WebRTC передаёт сжатое аудио Opus, WSS - PCM. Соответствующий ingress приводит звук к PCM 16 килогерц, моно, 16 бит. Через общий NATS аудио попадает в удалённый Whisper, а финальная транскрипция через Core поступает Agent. WebRTC ingressOpus → декодирование WSS ingressPCM из браузера Общий PCM + FIFO16 kHz · mono · 16 bit NATS Удалённый Whisperфинальный текст → Core → Agent Оба входа используют NATS дальше по цепочке. Whisper не переносили.
На адресе chat-agent WebRTC остаётся основным транспортом; WSS через /audio/ws выбирается отдельно.

Остатки аудио теперь накапливаются в FIFO, а не дополняются тишиной после каждого входного пакета. На выходе - кадры по 20 мс: 640 байт PCM. Padding остаётся только в окончательном хвосте.

Аудио публикуется сразу. Подтверждающий NATS flush выполняется раз в 250 мс на соединение вместо каждого кадра: примерно четыре раза в секунду вместо пятидесяти на поток. Завершение и ошибка вызывают отдельный обязательный flush.

Очередь ограничена двумя секундами аудио. Ошибка публикации завершает поток явно. Максимум - три аудиоподключения; четвёртое получает "сервер занят" до передачи звука. Ожидание краткого обрыва согласовали: восемь секунд на обеих сторонах.

Код: FIFO и общий жизненный цикл аудио, WSS ingress.

05Агент работает с видимыми данными

Добавили 64 реальных аэропорта РФ и перемещаемый самолёт, положение которого принадлежит конкретному чату и сохраняется после reload. Каталог аэропортов остаётся на карте, а результаты агента выделяются отдельно. Подписи показывают код и состояние; при приближении добавляют подробности.

Клик открывает карточку аэропорта. Менять можно открытость и доступность полос. Координаты и длины полос справочные. Демопогоду заменили OpenWeather: на карте есть погодные слои, а для расчётов используются отдельные численные данные. Ключ остаётся на сервере; погода кешируется и проверяется на давность. Если её нет, агент должен объяснить это, а не выдумывать значения.

Ошибки стали конкретнее: этап, причина, возможное действие и идентификатор запроса. Добавили ссылки на настоящую трассу в Tempo и Phoenix. Диагностика сохраняется вместе с сообщением. Phoenix развёрнули отдельно; корпоративный экспорт телеметрии сохранили. В трассы не отправляем разговоры, аудио, координаты и секреты.

География аэропортов и погода реальные. Открытость и состояния полос задаются вручную. TEST123 остаётся деморейсом; 70 минут в примере - дорога до аэропорта, а не длительность полёта.

Код: погода и кеш, настоящие Trace ID и Span ID, отдельный стэк Phoenix.

06Что получилось и что осталось

В контрольной серии 6 октября проверили эффект очередей и удаления лишнего router. Отдельно заменили тяжёлые Python health-пробы на лёгкие HTTP-проверки через curl.

Исторические замеры 6 октября, до перехода на Pydantic AI и 27B
ПоказательБылоСтало
Медиана полного обычного ответа14,97 с3,11 с
Медиана от ASR final до ответа, нагрузка 1/2/346,01 с14,10 с
CPU всего стэка в простое после исправления healthcheck0,197 ядра0,049 ядра

Источники: C3: одинаковая модель qwen3:30b, по 12 текстовых ответов; C4: два трёхминутных окна простоя. Это короткие замеры, не обещание такой скорости на любой нагрузке.

За эти дни собственный стэк получил имя chat-agent. Имя Git-репозитория осталось прежним. Сокращение ожидания ответа подтверждено; новый агент умеет сочетать инструменты и ждать подтверждения без занятого worker. Общий размер кода и образов после добавления новых библиотек здесь не пересчитываем.

Не считаем закрытыми проблемы GPU scheduling, зависшего decoder и длинных фраз Whisper - пункты 25, 26 и 30. Несколько владельцев media-соединений, автоматический ICE restart и отказ от приватных ICE-патчей тоже остаются отдельной работой. Полную финальную проверку всех 48 комбинаций нового агента в этом срезе не объявляем завершённой.
Как это связано с исходным списком
ПунктыЧто сделалиГраница результата
10-12Общий deadline, отмена, ограниченные очередиВнешний GPU не обещаем мгновенно остановить
16-21Новый агент, инструменты, native схемы, одна генерация обычного ответа, streamingИнструментальные задачи могут требовать несколько генераций
22-24, 27-29Общий FIFO, владелец задач, лимит трёх потоков, явные отказы, редкий flushСогласован grace period; автоматического reconnect пока нет
25, 26, 30Оставлены владельцу voiceGPU и decoder не перерабатывали
40, 44Медиа отделено от Core, ICE-адаптер изолированНесколько media replicas и замена private patch не реализованы
1user123 оставлен по решению пользователяРазделение чатов опирается на session ID

Служебные вложения доступны в исходном архиве.