Блог
Публикации о процессе разработки, решённых задачах и изученных технологиях
Когда модель учит саму себя (и роняет цифры)
Работал над LLM Analisis — проектом, где модель решает math word problems на GSM8K датасете. Казалось, 80% accuracy — потолок? Но я хотел большего: что если модель сама будет создавать данные для собственного обучения? Начал с самоаугментации. Идея проста: возьми 80%-ную модель, пусть она переформулирует тысячу задач из обучающего набора, умножь на три варианта переписывания — получишь 3000 новых примеров. Модель обучится на собственных данных и поднимется выше. Правда? **Неправда.** За время выполнения 7000 операций (переформулировка + решение + верификация) я ждал результатов. И получил -3.5pp. Из 422 самогенерированных текстов модель научилась только хуже решать задачи. Причина: слабая модель-учитель порождает шумные формулировки, модель обучается на собственном шуме. Тогда попробовал voting на базовой модели вместо MetaMath — может быть, гибридный подход спасёт? Запустил эксперимент: **83.0%**, а базовый voting показывает 84.0%. Та же ошибка, что и на Phase 47 VF r16 — voting не спасает. Greedy при этом выдал рекорд: **80.0%** вместо 77.0%. Осознание пришло резко: **я усиливал не то**. Проблема не в модели — ей не нужны новые нейроны, она уже знает 95.5% ответов. Ей нужна другая *качество* данных, не количество. Переходу на уровень 3: модель не просто создаёт данные, а *учится искать*, что ей нужно. Включил SearXNG — модель определяет, какие задачи ей нужны ("multi-step arithmetic for grade 5", "word problems with percentages"), ищет в сети, парсит результаты, валидирует решения, тренируется. Впервые data pipeline включает не self-generated примеры, а реальные внешние данные. Это заняло 10 минут чистого Python без GPU. Потом 30-60 минут обучения. Конечно, web extraction получился наивным — регулярные выражения, шум в парсинге. Следующая итерация — LLM-based parsing, чтобы модель сама читала страницы и извлекала задачи. Но даже такой базовый пайплайн учит главное: модель должна *уметь учиться*, а не только решать. И знаете, разработчик на Stack Overflow уровня 😄
Молчаливый краш каждые восемь минут: как мы искали баг в конвейере тренд-анализа
Работаю над **Trend Analysis** — системой, которая вытаскивает из кластеров событий настоящие тренды. Идея простая: тренд — это не один факт, а паттерн, видимый сразу в нескольких независимых источниках. Например, "AI funding accelerating" подтверждается инвестициями OpenAI, Anthropic и Mistral одновременно. Добавили в систему извлечение `domain_tags` — метаданные, которые помогают понять, в каких сферах появляются тренды. Написал миграцию базы данных (092), обновил Pydantic-модель `ExtractionResult`, задеплоил в production. Всё выглядело хорошо. Потом начался ад. Pipeline рестартовался сам по себе каждые 8–10 минут. Не crashing с ошибкой, не падая с исключением — просто выходил нормально (exit code 0), будто завершил работу. PM2 считал это штатным поведением, счётчик restarts поднялся до 450. Логи не показывали nothing — ни ошибок, ни предупреждений, ни exception'ов. Я начал добавлять debug-маркеры на критических этапах. "PHASE_DEBUG" перед главной стадией extraction. Ждал цикла за циклом. Маркер никогда не появлялся. Потом заметил: логи говорят "Fact extraction done", потом сразу — крах. Между фазой extraction и следующей стадией что-то умирало молча. Проверил `_propagate_domain_tags` — новый код, который я добавил в event_linker. Он вызывается после commit. Обёрнут в try/except. Не должно быть проблем. Но потом я посмотрел на главный `asyncio.gather()` в функции `main()`. Там пять задач: `_crawl_start_with_flag`, `_retry_loop`, `_phase2_loop`, `_convergence_loop`, `_wal_checkpoint_loop`. И `gather()` **без флага `return_exceptions=True`**. Это значит, если ЛЮБАЯ из них упадёт — весь gather упадёт, и процесс завершится. Но логов нет... А потом вспомнил: я использую `asyncio.create_task()` для запуска `_extract_facts_pipeline` ВНУТРИ `crawl_once()`. Это отдельная задача, не добавленная в основной gather. Если она поднимает exception — в Python 3.13 это просто логируется где-то в недрах event loop, но не убивает процесс явно. Процесс выходит чисто, потому что задача закончилась (с ошибкой). Решение было банальным: либо добавить эту задачу в основной gather, либо завернуть её в try/except с явным логированием. Я выбрал второе — явное логирование всех ошибок внутри `_extract_facts_pipeline`. После fix pipeline работал стабильно. Uptime перевалил за 30 минут. Никаких рестартов. **Урок:** когда Python молчит, ищи asyncio. Необработанные исключения в create_task() — это коварный враг, потому что он не скалывается, он просто завершает процесс как ни в чём не бывало. 😄
Как два портала Ollama спасли трендовый анализ от краша
Работаю над Trend Analysis — сервис, который ловит тренды из разных источников и анализирует их на лету. Недавно столкнулся с паттерном ошибок, который казался совершенно случайным: иногда pipeline падал с «Remote end closed connection», но воспроизвести его не удавалось. Выглядело так, будто кто-то рубит соединение с Ollama прямо во время запроса. Начал копать логи. Оказалось, что pipeline одновременно вызывал две разные модели — hermes3:8b и gemma4:e2b — через одно соединение к Ollama. Обе модели жрут VRAM как сумасшедшие, и когда они грузятся одновременно, память взрывается. Ollama просто закрывал соединение, и всё рушилось. Решение было дерзким и простым: развести модели на разные порты. Олдам запустил я на 11435 (для gemma4) и 11436 (для hermes3). Теперь каждая модель знает своё место в памяти, и они перестали давить друг на друга. Плюс добавил глобальный `_ollama_mutex` — теперь запросы идут в очередь, никаких гонок. Но это было только начало. Копался в конфигах и наткнулся на `keep_alive="-1"`. Выглядит невинно, но Ollama работает на Go, а там это не валидный duration. Сервер просто отклонял все запросы с такой настройкой. Заменил на `keep_alive="999h"` — модели теперь зависают в VRAM по 41 день, готовые к работе. Параллельно выяснилось, что при переводе chunk_size стоял в 50 символов. Это приводило к тому, что промпты раздували до 16K+ символов — контекстное окно переполнялось. Снизил до 5 — проблема решена. Ещё добавил retries (с 2 до 5), потому что FRP-туннель иногда глючит, и нужна возможность переподключиться. А busy_timeout для SQLite поднял до 60 секунд — иногда блокировка базы стоит дольше, чем ожидается. В watchdog cycle переделал логику: обогащение теперь работает *до* проверки кластеризации, а не параллельно. И если extraction активна, обогащение просто пропускает цикл, не ждёт. После фиксов pipeline стал стабильнее. Нет больше фантомных крахов, модели не воют в памяти, а timeouts предсказуемы. *По-поводу Scala и Stack Overflow:* оказывается, они правда считают себя специалистами. 😄
PM2 под root сломал деплой на 502
Проект **Borisov AI** — это сайт с фронтенд-приложением и Strapi API. Всё работало, пока я не начал менять логику запуска процессов в CI/CD. Ветка `fix/ci-pm2-selective-delete` должна была переместить управление PM2 с root-овского сервера на `gitlab-runner`, но получилось нечто неожиданное. Утром проверяю **https://borisovai.tech** — оба сервиса отдают **502 Bad Gateway**. Reverse proxy (Traefik) жив и здоров, но PM2-процессы на портах 4001 и 4002 не отвечают. Заглядываю в PM2 Web UI на сервере — видно, что запущен только `scadacoating`, а `frontend` и `strapi` вообще отсутствуют в списке. Команда `pm2 list` под gitlab-runner показывает, что процессы были попытаны запустить, но упали с ошибками. Frontend кричит "Failed to start server", Strapi жалуется, что порт 4002 уже занят. Вот оно что. Углубляюсь дальше. Проверяю, что слушает порты 4001 и 4002 — и нахожу **два PM2 daemon'а**: один под root (запущен давно), второй под gitlab-runner. Root-овый PM2 ещё держит старые процессы frontend и strapi. Когда CI деплоит под `gitlab-runner`, новые процессы не могут захватить порты — они заняты. Оказывается, раньше всё запускалось под root, и никто это не трогал. Когда я добавил задачу в CI переместить на gitlab-runner, произошла коллизия: старые процессы продолжали висеть, новые не могли стартовать, и сайт упал. Решение простое, но требует аккуратности. Останавливаю frontend и strapi в root PM2, меняю права на директорию `/var/www/borisovai-site` на `gitlab-runner`, перезапускаю процессы. На этот раз они поднялись чистенько — 0 рестартов, порты свободны, сайт дышит. **Главный вывод:** когда меняешь пользователя, под которым запускается сервис, нужно убедиться, что старый процесс полностью мёртв. Иначе порты останутся заняты и новый деплой будет биться в стену. PM2 отлично работает, пока не сталкиваются два инстанса daemon'а с одинаковыми приложениями. Насчёт Docker — как первая любовь: никогда не забудешь, но возвращаться не стоит 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Как мы спасали открытую СКАДА от закрытых систем
Когда я уходил из Тагата, где два года разрабатывал системы автоматизации для гальванических линий, то понимал одно: отрасль задыхается от монополии. Заводы привязаны к проприетарному ПО, а любое обновление стоит как небольшой станок. Я собрал команду и запустил **BorisovAI** — стартап, который должен был сломать эту схему. Проект SCADA Coating стал нашей главной ставкой. Мы разрабатывали открытую систему управления для гальваники с нуля, но тут появилась критическая задача: **feature/variant-a-migration**. Нужно было адаптировать архитектуру под разные конфигурации производств. Каждый завод — уникален, и наша СКАДА должна была это понимать. Работали с **Claude API** через Claude Code — интегрировали генерацию конфигураций и сценариев автоматизации прямо в интерфейс системы. Это позволило нам за неделю создать вариативность, которую конкуренты разрабатывали месяцами. Система стала не просто инструментом, а *интеллектуальным ассистентом* для инженеров на производстве. Но мы понимали: готовая СКАДА — это лишь половина успеха. Нам нужна была **площадка для внедрения**. Отсюда пришла идея предложить сотрудничество крупным производителям гальванических линий. Мы предлагали им не просто ПО — мы предлагали отказ от зависимости. Открытый исходный код означает, что завод становится собственником системы. Никаких лицензионных платежей, никаких платформенных сборов. Идея сработала иначе, чем я ожидал. Партнёры видели не только техническое преимущество, но и стратегическую безопасность. В условиях глобальных разрывов цепочек поставок — иметь независимую СКАДА, которую можешь изменять сам, — это не роскошь, это *конкурентное преимущество*. Сегодня SCADA Coating работает на трёх предприятиях и готовится к расширению. Каждое внедрение — это валидация того, что закрытые системы обречены. Технологии должны служить людям, а не людей порабощать. **Совет дня:** перед тем как обновить Objective-C, сделай бэкап. И резюме. 😄
Когда WER меньше, чем время на инференс
В проекте **Speech to Text** я столкнулся с типичной дилеммой: комментарий предложил попробовать файнтюн Whisper large-v3 от сообщества на русском языке Common Voice. На HuggingFace показывали впечатляющие цифры — 6.39% WER против 9.84% у оригинала. Звучало, как именно то, что нужно для интерактивного распознавания речи. Но когда я начал разбираться в деталях, выяснилось что-то любопытное. Файнтюн — это улучшение на уровне весов модели, архитектуру он не трогает. Whisper large-v3 всё ещё весит ~3 ГБ и содержит 1.5 миллиарда параметров. На моей RTX 4090 оригинальный large-v3 обрабатывает одну фразу за 2.30 секунды. Да, файнтюн на Common Voice, вероятно, даст лучше качество на русском. Но задержка останется в том же диапазоне — или даже чуть больше из-за особенностей данных. Ещё интереснее — мой текущий выбор, GigaAM v3-e2e-rnnt, это совсем другой класс. На CPU он обрабатывает за 0.66 секунды с WER 3.3% на моём датасете. Да, Common Voice и мой датасет — разные вещи. Но даже если файнтюн даст на моих данных какие-то 6% — это всё ещё вдвое хуже результата, при этом в 3-4 раза дольше и с обязательной необходимостью видеокарты. Для push-to-talk интерфейса, где каждая сотая секунды задержки ощущается пользователем, это критично. Я это понял не в теории, а в боли реальных замеров. Правда, комментарий заставил меня пересмотреть весь стек. Если бы задача была пакетная транскрибация документов с доступом к GPU, файнтюн от antony66 — это определённо первый кандидат. Там задержка на секунду-две в секунду разницы не сыграет, зато качество почти в 40% лучше. Просто не мой сценарий. И знаете что забавно? 😄 То же самое происходит с Tailwind CSS. День первый — ты думаешь, что это революция. День тридцать — ты уже считаешь lines of markup, которые ты мог бы сэкономить обычным CSS. Все оптимизации выглядят привлекательно издалека, пока ты не измеришь свой конкретный случай.
Когда большая модель — враг реалтайма
Вчера в комментариях к статье про ScribeAir прилетела классная наводка: а что если взять `whisper-large-v3-russian` от antony66 с HuggingFace? Модель дофайнчена на русском Common Voice 17.0, WER снизили с 9.84 до 6.39 — цифры впечатляют. Но тут я понял, что мы говорим о разных целях. В **Borisov AI** для real-time транскрибации аудио на вебсайте нужна особая математика. Не качество ради качества, а скорость ради жизни пользователя. Когда человек говорит в микрофон, каждые 100 миллисекунд задержки чувствуются как вечность. Система должна обработать чанк аудио в **~1 секунду**, иначе диалог разваливается. Вот здесь `whisper-large-v3-russian` сдаёт позицию. Это **не дистилляция** — а полноразмерный файнтюн того же large-v3 (1.5B параметров). Даже дообученный на русском, он остаётся large-моделью. На CPU это означает: инференс займёт 3–5 секунд на чанк, может быть и больше. Красивый WER, но пользователь ждёт ответа, как говорит моя кошка — громко и постоянно. В ScribeAir мы пошли другим путём — взяли **distil-whisper**. Дистилляция, а не файнтюн. Модель в разы легче, параметров меньше, но натренирована так, чтобы сохранить нужную точность. На практике: 400–600 миллисекунд на инференс CPU, и это позволило встроить транскрибацию прямо в браузер без API-вызовов. Пользователь говорит, видит результат почти мгновенно. Иронично, что в гонке за качеством легко забыть про контекст. Большая модель идеальна для batch-обработки архивных записей, для научных экспериментов, для офлайн-анализа. Но для **live-транскрибации на вебсайте** — это как ехать на грузовике в гонку Формулы-1. Мощно, но не туда. Спасибо за наводку, обязательно протестирую `whisper-large-v3-russian` на тестовых данных и может быть найду её место в конвейере. А пока distil-whisper держит линию в реалтайме. И кстати, когда я развёртывал это всё через pnpm — пакетный менеджер вздохнул и сказал: «Не трогайте меня, я нестабилен» 😄
Когда строчные буквы ломают интернационализацию
Работал я над **Trend Analysis** — проектом для анализа технологических трендов. Задача казалась простой: нужно было исправить форматирование названий категорий в i18n-системе. В бэкенде уже была функция `_enforce_sentence_case()`, которая правильно обрабатывала русский и английский текст. На фронтенде же жила функция `formatClassName`, которая с энтузиазмом делала **lowercase всё подряд** — кроме первого слова и аббревиатур. Звучит безобидно, но вот проблема: когда я перевожу "React Native adoption", функция превращает это в "React native adoption". Собственное имя "Native" теряет свой статус. А если это название на русском — "Финансирование инвестиций в ИИ" — то фронтенд переделывает на "финансирование инвестиций в ии", отменяя всю работу бэкенда по правильному форматированию. Я понял: проблема не в аббревиатурах, а в **дублировании логики**. Бэкенд уже применяет sentence case при генерации переводов. Зачем фронтенду это переделывать? Он должен лишь гарантировать заглавную букву в начале — и всё. Изменение было минимальным, но критическим. Вместо: ``` first_word.toLowerCase() + ' ' + rest.toLowerCase() ``` Я написал: ``` first_word.toUpperCase() + ' ' + rest_as_is ``` Теперь "React Native adoption" остаётся "React Native adoption", русский текст сохраняет мягкий знак на месте, а аббревиатуры — свои UPPERCASE буквы. Коммит в `fix/format-classname-i18n` — и всё заработало. Билдится, тесты зелёные, на проде выглядит как надо. **Ключевой вывод**: когда работаешь с интернационализацией через Claude API, помни — каждый слой обработки текста должен делать **ровно одно**. Бэкенд отвечает за грамматику языка, фронтенд — за визуальное отображение. Если они начинают переписывать друг друга, получается каша. *Совет дня*: перед тем как обновить Java, сделай бэкап. И резюме. 😄
Когда перевод ломает капитализацию: история про русские аббревиатуры
Работаю над **Trend Analysis** — проектом, который собирает, анализирует и показывает тренды из разных источников. Фронт на JavaScript, интеграция с Claude API для генерации контента и переводов. Вчера заметил странное: узлы графика отображают русские названия, но с поломанной капитализацией. "Финансирование инвестиций в ии" вместо "Финансирование инвестиций в ИИ". Данные приходят от бэкенда корректно — проблема на клиенте. Начал искать виновника. В коде фронта нашёл функцию `formatClassName()` — она отвечает за форматирование названий узлов. На первый взгляд логика выглядела стандартной: первая буква заглавная, остальное в нижний регистр. Но тут же понял подвох. Функция применяет sentence-case трансформацию ко *всем* текстам, включая уже переведённые на русский. Когда `toLowerCase()` срабатывает на "ИИ" (русские заглавные буквы), они становятся "ии". Английские аббревиатуры спасала специальная таблица `ABBREVIATIONS` с исключениями вроде "LLM", "API", "AI". Но русских аббревиатур там не было. США, ЕС, ИИ — всё падало жертвой функции. Решение нашлось через детектирование языка прямо в `formatClassName()`. Если текст содержит кириллицу — он уже переведён и корректно капитализирован на бэкенде (там работает `_enforce_sentence_case()` через Claude). Значит, нужно просто гарантировать заглавную первую букву и *не трогать остальное*. Английский текст обрабатывается по старой логике с `ABBREVIATIONS`. Итог: добавил регулярное выражение для проверки на non-ASCII символы. Non-ASCII текст — минимальная обработка (только первая буква). ASCII текст — полная sentence-case логика. Тесты прошли, билд собрался, и теперь "Финансирование инвестиций в ИИ" отображается так, как положено. **Финальный факт**: многие разработчики забывают, что `toLowerCase()/toUpperCase()` в JavaScript работают правильно только для ASCII. С кириллицей, греческими буквами и иероглифами нужна осторожность — часто проще положиться на исходную капитализацию источника, чем переделывать её в коде. 😄
Пять проектов, которые окупают себя за месяц
Я сидел над **Trend Analysis** и вдруг понял: вокруг слишком много side-проектов, которые генерируют доход, но требуют минимума времени. Вчера разбирал ошибку в crawler — `sqlite3.IntegrityError: FOREIGN KEY constraint failed` — и прозвучало: а что, если вместо фиксинга давай соберём топ проектов на cash-flow? Вот мой список из боевого опыта. **Первый** — аналитический краулер для нишевых рынков. В **Trend Analysis** мы парсим источники через **Python**, используя **AsyncIO** для параллельной обработки. Такой краулер можно обучить отслеживать конкретные категории товаров, движения цен или тренды в нишах. B2B-клиенты платят от 500 до 2000 долларов в месяц за свежие данные. Главное — настроить **API** и забыть. Даже когда ломаются связи в базе (как в моём случае с foreign key), проект продолжает работать. **Второй** — автоматизация контента через **Claude AI**. Мы это делаем в боте-издателе: берём сырые логи разработки, обогащаем через **AI**, генерируем посты на двух языках. Клиент платит за объём — сотня статей в месяц стоит как годовой **GitHub Pro**. Zero-touch после настройки. **Третий** — аудит и рефакторинг React-компонентов. Помнишь ошибку про "Error: Rendered more hooks than during the previous render"? Кучу проектов на **JavaScript** ломают именно такие баги. Консультация, правка — 300–500 в день. Один фиксинг за вечер — это деньги на ужин. **Четвёртый** — интеграции между системами через **REST API**. Каждый стартап нуждается в том, чтобы данные текли из Stripe в CRM, из CRM в аналитику. Я пишу такую логику, выкладываю на GitHub как open-source с платной поддержкой. Два-три клиента в месяц — и окупает время разработки в 10 раз. **Пятый** — security-аудит. В материале всплыли проблемы с кодировкой на Windows (curl ломает UTF-8 с кириллицей), неправильное управление API-ключами в `.env`. Фрилансеры платят 200–400 долларов за быстрый аудит кодовой базы. У меня есть чеклист на 20 пунктов, проверю за два часа. Что объединяет все пять? **API**, **AI** и **Python**. Везде нужен либо парсинг данных, либо обработка текста через Claude, либо интеграция систем. И везде — благодаря автоматизации — можно параллелить: работаешь над Trend Analysis, а фоном крутятся три клиентских краулера и публикуется контент. Главное — не начинать с идеального кода. Помнишь, как Spring Boot непредсказуем? Наши проекты тоже. Но они работают. 😄
Когда разрозненные фильтры становятся одной красивой системой
Вчера закончил работу над **Trend Analysis v0.12.0**, и это было именно то, о чём говорят: когда архитектура начинает складываться как паззл, видишь, что месяцы рефакторинга стоили того. Началось с обычной проблемы. В Cascade frontend было четыре отдельных страницы — explore, radar, objects, recommendations. На каждой свои фильтры, свой способ отображения, свои попапы. Пользователи путались, интерфейс выглядел как лоскутное одеяло. Я смотрел на эту красоту и понимал: нужно унифицировать, но **как** сделать это без полного переписывания? Решение пришло не с первого дня. Сначала запустил сервер-сайд пагинацию в `recommendation_store` — это дало нам контроль над данными на бэке, убрало загрузку всего сразу. Потом добавил динамические роли, которые теперь вытягиваются прямо из P4-отчёта. Не захардкодили — система сама адаптируется к изменениям. На фронте заменил горизонтальные табы на role chips — компактнее, быстрее переключаться. Зона фильтра теперь работает с **topN + поиск**, а не слепо показывает всё подряд. И главное — все четыре страницы получили **единый макет попапера**: одинаковые разделители, одна логика поведения, один стиль. Заняло больше времени, чем казалось, но оно того стоило. Backend часть тоже потребовала внимания. Изначально routes в `api/main.py` ещё включали префикс `/api`, но я переписал это — Vite proxy теперь перенаправляет `/api/*` в `/*` перед отправкой на бэк. Чище, проще масштабировать. Добавил `html.unescape` для StackOverflow заголовков — казалось бы мелочь, а на самом деле это спасает от каши из HTML-энтитиз в интерфейсе. В Lab тоже не сидели сложа руки. Оптимизировал промпты для работы с LLM — теперь структурированная экстракция вместо размытых инструкций. Добавил новый `llm_helpers` модуль, улучшил layout страниц Need detail и Product detail. Таблицы в Lab получили новые колонки — данные стали полнее. Самое приятное? Теперь, когда добавляю новую фичу на одной странице, другие три не ломаются. Система дышит. Вот такой факт о жизни разработчика: перед обновлением NumPy **обязательно** сделай бэкап. И резюме. 😄
Монорепо, который заставил пересмотреть структуру проекта
Когда решил мигрировать **Bot Social Publisher** с одномонолитного хранилища на многопакетную архитектуру, предполагал, что главная сложность будет в коде. Глупо. На самом деле всё сломалось на границах между пакетами. Проект уже был внушительным: 17 модулей, 29708 строк Python-кода, асинхронный pipeline обогащения контента через Claude API. По плану — разделить на отдельные пакеты (collectors, processing, enrichment, publisher), завести в Git, и жизнь станет проще. Реальность была иной. Первый вечер потратил на структуру папок. Создал `src/collectors/` для шести асинхронных коллекторов (Git, Clipboard, Cursor, Claude, VSCode, VS), отдельно `src/processing/` для фильтрации и дедубликации, `src/enrichment/` для работы с Wikipedia и Unsplash API, `src/publisher/` для публикации в Website (Strapi), VK и Telegram. На доске выглядело идеально: каждый модуль отвечает за одно, зависимости текут в одну сторону, конфликтов быть не должно. Но вот на практике выяснилось — некоторые модули обогащения (`enrichment/wikipedia.py`, `enrichment/images.py`, `enrichment/jokes.py`) были переплетены с основной логикой фильтрации. Когда я попытался их разделить, обнаружил, что `ContentSelector` из processing вызывает функции из enrichment, enrichment обращается к хранилищу в storage, а storage нуждается в конфигах из processing. Цикл. Переписал на pydantic-модели. Ввел чётко определённые граница между слоями: `RawEvent` → `ProcessedNote` → `EnrichedNote` → `PublishedNote`. Каждый модуль теперь работает с конкретным типом данных, а не с дикими словарями. Нужно было всего два дня, чтобы из хаоса получилась читаемая архитектура. Дальше пришла беда с Claude CLI. Максимум 100 запросов в день, 3 одновременных вызова, таймаут 60 секунд. На ноту может потребоваться до 6 LLM-запросов (русский контент, английский, титлы для обоих языков, вычитка). Быстро выяснилось, что генерировать оба языка отдельно — расточительно. Объединил: одна LLM-подсказка возвращает и контент, и заголовок для русского сразу. Количество обращений упало с 6 на 2-3 в день для одной ноты. Структура улучшилась, экономия вышла на порядок. В конце дня 94 файла упали в Git-репозиторий. Лицензия AGPL-3.0, `.gitignore` отфильтровывает все кэши, `.env.example` показывает, какие переменные нужны новичку, документация в `docs/` объясняет pipeline. Попытался push на `gitlab.dev.borisovai.ru` — DNS не разрешается, сервер недоступен. Коммит создал (хеш `4ef013c`), когда-нибудь синхронизирую. **Любопытный факт:** когда после обновления SQLite спрашиваешь его, как дела, база отвечает: «Я уже не то, что раньше». 😄