Загрузка 0
ПОДЕЛИТЬСЯ

Мой блог

Листай вниз

Локальный ИИ-ассистент для созвонов: как построить надежную память на графе знаний в Obsidian

Локальный ИИ-ассистент для созвонов: как построить надежную память на графе знаний в Obsidian

С момента запуска моего автономного ассистента для рабочих созвонов прошло полтора месяца непрерывной повседневной эксплуатации. Напомню, что первоначальная система разворачивалась локально на Mac: она распознает голоса, проводит диаризацию, создает стенограмму и формирует граф знаний в базе Obsidian без отправки данных в сторонние облака. Однако практическая проверка показала: зафиксировать разговор — это лишь начальный этап. Куда сложнее организовать структуру так, чтобы через несколько недель поиск выдавал точные факты, а не сгенерированные галлюцинации нейросети.

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

2

Реклама

Архитектура памяти: трехслойный граф без громоздких баз данных

Структура «Эпизоды — Сущности — Сообщества» на локальных файлах

В основе системы лежит трехслойная схема, напоминающая архитектуру современных графовых систем памяти вроде Graphiti или Zep, однако реализованная без использования специализированных СУБД. Вся база знаний представляет собой обычную директорию с файлами Markdown, которую открывает Obsidian:

  • Эпизоды: сырые записи встреч в папке Встречи/YYYY-MM-DD_HHMM_Тема.md и их архивы;
  • Сущности: карточки участников, программных систем и команд с фиксацией обратных ссылок;
  • Ядра (сквозные темы): ключевые предметные узлы, содержащие текущий статус и хронологию событий;
  • Досье: итоговые аналитические сводки по ключевым направлениям.

Главный плюс такого подхода — полный контроль над данными. База читается встроенными утилитами вроде grep, версионируется через git и остается читаемой, даже если я полностью остановлю разработку управляющего кода. К примеру, разбор встречи по выбору платежного провайдера автоматически обновляет узел ядра Ядра/Выбор платежного провайдера.md, где фиксируется решение («Решено: ЮPay»), а ниже формируется цепочка ссылок на прошедшие созвоны. Участники диалога также получают обновления в своих персональных заметках.

Хранение фактов с фиксацией периодов «с» и «по»

Первый важный урок регулярной работы: принятые на созвонах решения редко остаются неизменными навсегда. Их утверждая в среду, отменяют к пятнице, а спустя две недели могут вернуться к исходному варианту. Если база хранит исключительно финальное состояние, она искажает историю. Если же сохранять абсолютно все утверждения в кучу — искажается текущая картина.

Для решения проблемы я разделил узел ядра на два блока:

  1. Статус: жестко перезаписывается результатами последней встречи.
  2. Хроника: сюда смещается устаревший статус с явным указанием интервала действия (даты «с» и «по»).

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

Реклама

Привязка хроники к точным репликам из стенограммы

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

В ходе нескольких этапов код-ревью выяснилось, что парсер принимал за имена спикеров технические заголовки («Итог:», «Присутствовали:»), а также подписывал репликами людей внутренние заметки модели. Чтобы исключить подобное, я полностью переработал логику источника правды:

  • Единственным источником фактов признается итоговая стенограмма.
  • Имя говорящего и метка времени извлекаются строго из структуры реплики.
  • Если прямая цитата не находится в стенограмме, факт сохраняется без указания автора, исключая придумывание спикеров.

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

Досье как аналитический слой над графовыми узлами

Пользовательский запрос «какой итоговый статус по теме X» обычно заставляет языковую модель собирать фрагменты из десятков разных заметок, что каждый раз дает непостоянный результат. Чтобы сделать ответы стабильными, я ввел слой «Досье» — регулярно обновляемые выжимки с хронологией, принятыми решениями, открытыми вопросами и списками ответственных.

Кластер темы формируется вокруг узла ядра и всех связанных с ним документов. Границы кластеров определяются связями, которые проставляет сам пользователь, поэтому сложные математические алгоритмы кластеризации не потребовались.

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

Регламент ночных задач и системные ограничения

В 04:15 запускается комплексный фоновый процесс. Он выполняет формирование утреннего брифа, сверку ядер на предмет противоречий, обновление досье, дедупликацию документов и замер расхода памяти. Выполнение этих задач днем заблокировало бы локальный GPU и помешало бы обработке живых встреч.

В процессе отладки сформировался ряд жестких правил ночного конвейера:

  • Приоритет живой встречи: если в 07:00 начинается внеплановый созвон, ночной процесс моментально освобождает локальную модель и переходит в режим ожидания.
  • Ограничение по времени: регламент имеет фиксированный тайм-аут, выйти за который системные скрипты не имеют права.
  • Честный статус выполнения: проверкаclaude –version и валидация окружения выполняются перед стартом задач. Любые сбои CLI приведут к явному статусу ошибки, а не к ложной успешной отметке.
  • Учет сна ноутбука: если Mac засыпает с закрытой крышкой, система высчитывает разницу системного времени и выставляет статус «slept», отличая паузу от фатального сбоя.

Изолированная интеграция с облачными языковыми моделями

Локальные модели отлично справляются с пересказом, но иногда упускают логические коллизии между разными узлами графа. Для глубокого анализа можно задействовать мощную облачную модель (например, Claude Opus). Однако прямое предоставление доступа облачному ИИ к папке Obsidian привело к неприятности: параллельно прошедшая встреча была затерта откатом неудачного сеанса ревизии.

Реклама

Теперь облачная модель работает строго в изолированной песочнице — локальной копии графа. Перенос изменений в основную базу выполняется под системным замком с соблюдением правил:

  • Любой конфликт версий трактуется в пользу локальных данных.
  • Облачные правки при спорах отправляются в карантинную папку.
  • Команды на удаление файлов из песочницы игнорируются.
  • Стенограммы, первичные минутки и пользовательский раздел «Правки автора» недоступны для модификации облачными моделями.

Блочная индексация, векторный поиск и выявленные проблемы

Поисковый движок ассистента комбинирует два подхода: лексический поиск (стемминг, IDF, с учетом свежести заметок) и семантический векторный поиск (модель bge-m3 через Ollama). В ходе построения индекса я выявил три критические особенности:

  1. Проблема длинных заметок: построение одного вектора на первые 12 000 символов файла работает для коротких узлов, но полностью ломает поиск по стенограммам, где главные решения принимаются в конце. Из-за этого 63% контента выпадало из индекса. Решением стало разделение заметок на фрагменты по заголовкам с передачей контекста пути («Файл → H1 → H2»).
  2. Ограничения Ollama: экспериментальным путем выяснилось, что Ollama тихо обрезает входящий текст для bge-m3 на уровне ~12 300 символов, несмотря на заявленный лимит в 8192 токена.
  3. Скрытые файлы в iCloud: стандартные библиотеки обхода директорий проигрывали при работе с iCloud-контейнерами, так как система помечает часть папок флагом hidden. По этой причине поисковый индекс игнорировал более половины файлов базы. Пришлось отключить фильтрацию по флагу скрытости, перейдя на ручное исключение системных каталогов по имени.

Тестирование на контрольном наборе вопросов с нулевой температурой генерации показало: переход на блочную индексацию и понижение веса сырых стенограмм относительно итоговых выжимок подняли точность извлечения фактов с 11/14 до 14/14.

Автоматическая гигиена и предотвращение порчи графа

Без регулярного обслуживания граф знаний постепенно засоряется. Первичный аудит моей базы обнаружил 626 битых ссылок и 17 отдельный узлов людей, которые оказались техническими метками диаризации вида «Speaker 2» или «Собеседник 3».

Для поддержания порядка я внедрил следующие правила:

  • Метки диаризации никогда не образуют персональных узлов.
  • Ключи имен очищаются от скобок и дефисов («Иван (Иванов)» и «Иван Иванов» автоматически приводятся к единому узлу).
  • Служебный скрипт-доктор ежедневно проверяет базу на наличие осиротевших файлов и разорванных вики-ссылок, выводя отчет в утренний бриф.

Практические сценарии и стабильность конвейера

Предотвращение коллизий имени при параллельной записи

Если имена файлов стенограмм формируются с точностью до минуты, при перезапуске фонового процесса или непрерывной записи нескольких аудиофайлов возникает риск перезаписи данных. Для защиты от потерь я перевел метки времени на секунды, а открытие файлов перевел в режим O_CREAT | O_EXCL (исключительно создание нового файла).

Рядом с каждым аудио рендерится служебный метафайл с точностью таймштампов и хэш-суммами контента. При переименовании встречи метафайл перемещается следом. Это предотвращает случайное перетирание заметок при совпадении заголовков созидаемых встреч.

Гарантированное удаление сведений о прошедшей встрече

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

Специальный сценарий «Забыть встречу» сначала формирует детальный план очистки с возможностью предварительного просмотра. Ссылки на удаляемый разговор в сохраненных текстах аккуратно заменяются на нейтральные пометки, а удаляемые данные подстраховываются резервной копией. Исключение составляют лишь внешние бэкапы Time Machine и iCloud, к которым у скрипта нет прямого доступа.

Мобильный клиент и поддержка сторонних аудиозаписей

Недавно я расширил функционал ассистента двумя возможностями:

  • Импорт внешних записей: файлы с диктофонов, экспорты из Zoom или чужие аудиозаписи теперь можно перетащить в специальное окно. Они обрабатываются по стандартному конвейеру, а аудиоисходник удаляется спустя два дня.
  • Приложение-компаньон для iPhone: запускает запись сразу при открытии. Если телефон находится в режиме активного вызова, приложение ожидает завершения разговора и подхватывает аудио сразу после освобождения микрофона. Сам телефонный звонок записывается на стороне Mac из-за ограничений iOS.

Автоматизированное код-ревью с привлечением нейросетей

Чтобы гарантировать надежность кода, каждое изменение перед слиянием с основной веткой проходит проверку через внешние языковые модели (GLM, DeepSeek, Codex). Процесс итеративен: проверка продолжается до тех пор, пока сторонние ИИ не перестанут выдавать замечания уровня Critical.

К примеру, во время недавней правки две независимые модели указали на состояние гонки (race condition) при фоновом копировании файла. Проблема была решена атомарным переименованием временного скрытого файла, что предотвратило считывание недописанных данных поисковым сканером.

Итоги и векторы развития проекта

Система локального ассистента доказала свою жизнеспособность при ежедневном использовании. В ближайших планах развитие следующих функций:

  • Интеграция слепка голоса владельца для точной идентификации без привязки к аудиоканалу.
  • Публикация мобильного компаньона в App Store.
  • Тестирование новых локальных моделей распознавания речи. Например, тест MOSS на базе MLX показал высокую скорость (в 10–15 раз быстрее реального времени), но на 20-минутном фрагменте сгенерировал 19 фантомных спикеров. Поэтому на данный момент основной моделью для распознавания остаётся GigaAM.

Исходный код проекта распространяется под лицензией Apache 2.0 и доступен в репозитории charoiteai/Charoite_audio на GitHub.

Источник: habr.com

01.