- BrainTools - https://www.braintools.ru -
В первой части [1] ассистент записывал созвон и складывал его в граф, во второй [2] граф учился не врать неделями, в третьей [3] я трижды ошибся с вопросом, кто это говорит. Эта часть про двадцать пять минут после кнопки Стоп: на экране ничего не происходит, на диске происходит всё. Там на этой неделе нашлась дыра, которая жила с июля. Облачная ревизия находила ошибки [4] локальной модели, аккуратно их описывала, и никто её не слушал.
Пятница, рабочий созвон на два часа. Запись остановлена в 17:33. По логам:
17:33 стоп; живой черновик стенограммы закрыт
17:36 финальная стенограмма: диаризация по каналам, распознавание заново, имена
17:38 заметка встречи в графе, узлы людей, минутки, разбор локальной модели,
семь фактов ушли в память
17:58 облачная ревизия готова: 20 минут, 57 правок графа перенесены,
в минутки дописано 7 поручений
Локальная модель записала в минутки три решения и список поручений. Ревизия, прочитав стенограмму целиком, два решения из трёх отвергла: одно было шуткой про бюджет (один токен на всех, следующий запрос в следующем году), второе — задачей соседней команды. У третьего верное решение и не тот исполнитель. Три поручения сняла: человеку, которого на встрече не было, со сроком до 25-го, который никто не называл; без срока и исполнителю, который со следующей недели в отпуске; и одно с числом, прочитанным наоборот — план принят за разницу. Плюс имя: дорожку ведущего распознавание назвало чужим именем по обращению, ревизия по тем же обращениям нашла настоящее.
Всё это лежало в файле ревизии с цитатами и таймкодами. Дальше:
в минутки ревизия умела только дописывать: семь восстановленных поручений добавились, три ложных остались и уехали во вкладку Задачи;
в заметке встречи шутка осталась с пометкой 📌 решение, опровержение лежало ниже, в разделе для человека;
семь фактов ушли в память [5] в 17:38, за двадцать минут до того, как ревизия признала два из них несуществующими. Поиск по памяти потом спокойно отдавал единый токен для всех команд как принятое решение.
Ревизия работала с конца июля, каждый день, по каждой встрече. Её выводы никуда не доходили: второй проход был устроен как ещё один читатель, без права отменять.
Конвейер в пяти шагах, про первые два подробно было в прошлых частях.
Пересборка. Живой черновик собран из трёхсекундных кусков по ходу встречи. После стопа запись диаризуется заново по каналам, эхо микрофона режется по перекрытию с системным звуком, имена дорожек угадываются по обращениям. Старая версия уходит в скрытую папку: человек мог править стенограмму руками. Минутки либо пересобираются, либо перештамповываются — вместо номеров собеседников подставляются имена.
Граф. Локальная модель (35B, MoE, через Ollama) разбирает стенограмму в JSON: люди, системы, решения, поручения. Из этого — заметка встречи, узлы людей и систем, строка в оглавление, факты в память. Тут же пишется разбор: что это было и что делать дальше.
Ревизия. Если включён облачный слой, отдельный процесс отдаёт стенограмму, минутки, разбор и контекст графа большой модели через CLI по подписке. Модель пишет ревизию: где локальная ошиблась, что пропустила. В режиме правки она правит клон графа, и правки переносятся под замком с проверкой каждого файла, это было во второй части.
Доставка. Ревизия кладётся рядом со стенограммой, в архив и в документацию, минутки перечитываются во вкладку Задачи.
Ночь. Досье, ревизия ядер, утренний бриф.
Три модели: распознавание, локальная, облачная. И вопрос, который я полтора месяца не задавал: кто отвечает, если шаг три не согласен с шагом два?
Почему не сделать локальную модель умнее? Она ошибается по одной причине: пишет минутки по ходу встречи, каждые две с половиной минуты видит хвост стенограммы и должна выдать документ. Шутка про бюджет выглядит как решение, потому что смех через десять секунд модель ещё не видела. Человек, которого трижды упомянули, выглядит как участник. Слово текучка в разговоре про бюджет читается как текучесть кадров, и в рисках встречи появляется остановка разработки из-за увольнений.
Большая модель читает стенограмму один раз и целиком. Она видит, что на вопрос к Игорю отвечает дорожка с другим именем. Видит, что рекомендации в разборе пришли с другой встречи через контекст графа. Один проход большой модели после встречи дешевле, чем заставлять маленькую думать как большую на ходу.
Но тогда второй проход должен уметь снимать, а не только дописывать. То есть менять файлы, которые уже прочитали вкладка Задачи, граф и память. Этим я и занимался всю неделю.
Ревизия говорит с кодом не прозой, а строгими разделами в конце файла. Проза остаётся человеку.
## Восстановленные поручения
- [ ] **Имя** — что сделать — срок, если назван
## Снятые поручения
- **Имя** — что сделать — причина: …
## Исправления имён
- **Метка** → **Имя** — основание: …
Восстановленные были и раньше. Снятые и Исправления имён — новые. Модель просят писать имя и дело так же, как в минутках, чтобы код нашёл строку.
Самое скучное место — как код находит строку минуток по пересказу модели. Первая версия считала совпадением вложение в любую сторону: подготовить отчёт вкладывается в подготовить отчёт по бюджету, значит, это оно. Внешний ревьюер за пятнадцать минут показал, что так один снятый пункт снимает все похожие строки: отчёт по бюджету и отчёт по срокам уезжают из задач вместе. Съеденное поручение человек не увидит никогда, лишний дубль увидит и снимет одним кликом. Цена ошибок несимметричная, отсюда правила:
один пункт ревизии снимает ровно одну строку;
если подходят несколько — точное совпадение перевешивает, а без точного не снимается ничего, в лог уходит строка: подходит к двум пунктам, не гадаем;
вложение засчитывается от двух общих значимых слов: одно слово позвонить не снимает позвонить в банк;
пункт, который человек отметил сделанным, не снимается никогда;
снятая строка не удаляется, а переезжает в раздел Снято ревизией зачёркнутой, с причиной. Из Задач она исчезает, из минуток нет.
Правило не гадать в проекте уже было, для узлов графа: имя похоже на два существующих узла — узел не создаётся, кандидаты уходят человеку. Оно оказалось общим: где выбор идёт по нечёткому совпадению, а ошибка — тихая потеря, лучше ничего не делать и сказать об этом.
Второй круг ревью принёс историю про владельца. В настройках он записан двумя словами, минутки пишут его полным именем, а для сравнения код сводил владельца к первому слову только с одной стороны. Ключи расходились на фамилию, снятие для владельца не срабатывало никогда, и ни один тест этого не ловил: все тесты были с однословным владельцем. Две внешние головы нашли это независимо, одна поставила Critical. Чинится одной функцией канона на обе стороны. Тестовые данные должны включать неудобный случай из конфига, а не удобный из головы.
С решениями проще. Облако помечает ошибочное решение в заметке на месте: 📌 становится ⛔, в конце строки причина. Всё, что читает заметку — досье, поиск, память, — знает, что ⛔ не решение.
У памяти ассистента (отдельный сервис с векторной базой и Postgres, общий с моим чат-ботом) нет ключа факта, только ключ встречи. Нельзя забыть одно решение, можно забыть встречу. Поэтому после переноса правок код сравнивает список живых решений в заметке до и после. Изменился — встреча забывается целиком и присылается заново без ⛔. Не изменился — ничего не трогаем.
На бумаге две строки. В коде это вышел самый дорогой кусок недели: четыре круга ревью, и в каждом что-то находилось здесь. Обрыв между забыть и прислать заново оставлял память встречи пустой навсегда — появился файл-долг рядом с отметкой отправки. Долг жил не у того, кто отправляет, и повтор обработки его не гасил. Долг гасился по числу чужих ключей в отметке — при мёртвом сервисе, без единого удачного запроса. Это уже был Critical. Долг чужой встречи гасил чужой процесс посреди своей отправки. Пришлось добавить замок отправителя, возраст долга и очередь. К четвёртому кругу код был правильным и уродливым.
Аудит по зонам (о нём ниже) добил: из семнадцати подтверждённых дефектов пять сидели в синхронизации с памятью, и три вердикта из десяти независимо назвали один и тот же — долг встречи из другого графа гасится без оплаты. Механизм, который держит вторую память в согласии с первой, оказался главным местом, где они расходятся.
Тут я остановился и посчитал, что вторая память умеет такого, чего нет в графе. Заметка встречи с решениями — в графе есть. Семантический поиск — граф и так ищется гибридом. Уникального две вещи: личные факты, которых в графе встреч нет по замыслу, и память диалогов. Остальное — копия, за которую я плачу долгами, замками и кругами голов.
Вторую память я выключаю. Не сегодня и не удалением: сначала личное, несколько сотен фактов, переезжает в граф обычными markdown-файлами; подсказки получают свой поиск по графу без сервиса; диктовка и дневник пишутся в папку. Потом дамп, остановка служб и через неделю удаление. Старая память работает, пока новая не отвечает на те же вопросы. Две памяти без общего ключа — как два писателя в одну папку без замка из второй части: та же потеря данных, только медленнее.
Распознавание называет дорожку по обращениям и ошибается, ревизия это видит. Раньше она писала об этом абзац, и всё: заголовки реплик, участники минуток, копии в архиве оставались с чужим именем.
Теперь по разделу Исправления имён код перештамповывает заголовки реплик и строку участников в стенограмме и минутках. Версия до правки уходит в ту же скрытую папку. Хеш машинного текста обновляется, чтобы следующая пересборка не приняла правку за ручную. Четыре ограничения, каждое из находки ревьюера:
Поручения не переименовываются. Игорь — согласовать план могло относиться к настоящему Игорю из речи, не к дорожке с этим именем. Такие поручения ревизия снимает и возвращает сама, теми же разделами.
Обмен меток одним проходом. Последовательная замена А на Б, потом Б на А превращала двух участников в одного. Critical от внешней головы.
Слияние дорожек запрещено. Два голоса под одним именем без отката хуже, чем чужое имя на одной дорожке.
Метка микрофона владельца не трогается. Канал — факт железа, модель тут ни при чём.
И решение, с которым я спорил сам с собой. Перештамповка идёт только когда облаку разрешено править граф и перенос сверен. В режиме только чтения стенограмма не переименовывается, иначе получится расколотая правда: в стенограмме верное имя, в узле человека чужое, и никто их не сведёт. Обе головы назвали это риском, я согласился. Но восстановленное поручение с верным именем в этом режиме получало бы пометку, что исполнитель не участник. Поэтому карта имён из ревизии всё равно строится, файлы не трогает и отдаётся мосту поручений как список участников.
За три дня девятнадцать ревизий, четыре без результата: таймаут в тридцать минут на длинной стенограмме, два ненулевых кода через восемь и восемнадцать минут, один незапуск — в этот момент обновлялся CLI. Во всех четырёх случаях повторить можно было только всю обработку с пересборкой, а статус встречи показывал готовность.
Теперь у ревизии четыре исхода: доставлено, ответ не похож на ревизию, процесс не запустился или упал, таймаут. Повторяется один класс — упавший процесс, один раз, через десять минут и не под живой встречей. Таймаут повторять [6] бессмысленно: тот же контекст упрётся в тот же потолок. Ответ с кодом ноль, но не похожий на ревизию, — ответ модели, второй заход даст тот же.
Этап ревизии лежит в статусе встречи отдельным полем: идёт, повтор, готово, сбой. Готовность встречи он не сдвигает. Поле переживает запись готовности и протухает через три часа, если воркер убили в паузе. Ещё одну дыру нашёл ревьюер: кнопка Повторить обработку в приложении появляется через тридцать минут тишины, а легитимная ревизия идёт до тридцати минут плюс замок. Кнопка появлялась как раз тогда, когда первый воркер ещё жив, и второй запускал платный разбор заново. Теперь перед вторым заходом проверяется, не появилась ли ревизия свежее стенограммы.
Про внешние головы было во второй части: каждое изменение перед мержем читают DeepSeek и GLM, пока круг приносит Critical — гоним следующий. На этой неделе добавился другой режим: аудит кода по зонам.
Пять зон конвейера после встречи: пересборка и имена, граф, облачная ревизия, мост минуток, память и статусы. На зону — чистый клон репозитория, инструменты только на чтение, список файлов в порядке чтения и строка бюджета: двенадцать минут и не больше сорока ходов, дальше вердикт по прочитанному. Без бюджета широкая зона не читается: в прошлый раз одна голова дала три пустые попытки, вторая ответила с третьей. Обе головы получают одни и те же пять зон, десять процессов стартуют вместе.
10 зон (5 DeepSeek 4.1 flash + 5 GLM 5.3 flash), все параллельно
стена 10 мин 13 с (самая долгая зона — 10 мин)
на зону 6–10 мин, 17–39 ходов инструментов
токенов на зону 120–155 K входных, 1–2,8 M кэшированных, 33–61 K выходных
цена $0,38 + $0,40 = $0,79 за десять вердиктов
DeepSeek — 12 Important и 16 Minor, GLM — 1 Critical, 9 Important и 23 Minor. Critical — про фильтр эха, который режет речь владельца при одновременном разговоре: известный компромисс, померенный в третьей части, отклонил. Из двадцати одного Important по коду подтвердились все, после слияния дублей — семнадцать дефектов. Три вердикта из десяти сошлись на одном и том же дефекте, и это лучший сигнал, который может дать такой аудит.
Головы не голосуют. Каждую находку я проверял по коду сам, и это заняло больше времени, чем весь аудит. Восемь центов за зону — цена вердикта, не правды.
Одна поломка была в самой обвязке — скрипте вне репозитория, который поднимает клоны и собирает вердикты. Его гейт на исчерпанный лимит искал в ответе подстроки вроде 402 и rate_limit_error. Два вердикта из десяти он объявил остановкой по лимиту, потому что аудируемый код содержит те же строки, а вердикт их цитировал. Восстановил из сырого ответа, гейт теперь смотрит только на короткие ответы. Любой фильтр по подстроке однажды встретит текст, который про этот фильтр рассказывает.
Ревизия умеет снимать: ложные поручения уходят из задач в раздел Снято ревизией, ошибочные решения помечаются на месте, память встречи переотправляется без них.
Имена дорожек перештамповываются по строгому разделу, только под сверенной правкой графа и без гаданий.
Ревизия повторяется после сбоя запуска и показывает свой этап в статусе.
Разбор перестал тащить рекомендации соседних встреч: контекст графа теперь только для имён и терминов.
Вторая память признана дублем и идёт на выключение.
Аудит по зонам: десять минут, меньше доллара, семнадцать дефектов.
Семнадцать дефектов — в пяти карточках, каждая своим PR со своим кругом голов. Выключение второй памяти по этапам. И слепок голоса владельца, который я обещаю третью часть подряд: единственный способ отвечать на вопрос, где я, не по каналу, а по голосу.
Код и релизы: github.com/charoiteai/Charoite_audio [7], Apache 2.0. Девлог по-английски, с замерами и отменёнными решениями: charoiteai.github.io/Charoite_audio [8]. Первая часть — про устройство и запись [1], вторая — про граф встреч как память [2], третья — про диаризацию и три ложных диагноза [3].
Автор: Charoiteai
Источник [9]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/35429
URLs in this post:
[1] первой части: https://habr.com/ru/articles/1061864/
[2] второй: https://habr.com/ru/articles/1078828/
[3] третьей: https://habr.com/ru/articles/1080942/
[4] ошибки: http://www.braintools.ru/article/4192
[5] память: http://www.braintools.ru/article/4140
[6] повторять: http://www.braintools.ru/article/4012
[7] github.com/charoiteai/Charoite_audio: https://github.com/charoiteai/Charoite_audio
[8] charoiteai.github.io/Charoite_audio: https://charoiteai.github.io/Charoite_audio/
[9] Источник: https://habr.com/ru/articles/1081586/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1081586
Нажмите здесь для печати.