BorisovAI

Блог

Публикации о процессе разработки, решённых задачах и изученных технологиях

Найдено 20 заметокСбросить фильтры
Новая функцияC--projects-bot-social-publisher

Извлечение строк из бинарных файлов: когда наивность встречается с реальностью

Когда я начинал работать над **Bot Social Publisher**, казалось логичным просто скормить все доступные данные в Claude и получить идеальный контент. Реальность оказалась куда жестче. Первая проблема: наши коллекторы вытаскивают из Git, буфера обмена, логов IDE огромные потоки сырых данных. Иногда это 500+ строк лога, где 90% шума: хеши коммитов, пустые строки чата, импорты без контекста. Отправить всё это в Claude значит сразу же спалить квоту дневного лимита на 100 запросов. Плюс платить за токены, которые модель просто проигнорирует. Вспомнил магию семантического кеширования и решил сначала *отфильтровать* входные данные. Написал **ContentSelector** — алгоритм, который достаёт из шумного потока только релевантные 40-60 строк. Логика простая: ищем сигналы (слова вроде "implemented", "fixed", названия технологий, проблемы), игнорируем мусор (длинные хеши, чистые импорты, маркеры чатов). Но тут вскрылась следующая проблема: даже отфильтрованный контент зачастую требует *множественных* обращений к LLM. Сначала генерируем на русском, потом на английском, потом правим опечатки, потом генерируем заголовки. За одну заметку — до 6 запросов к Claude. При 100 заметках в день это 600 запросов. Нереально. Решение пришло из оптимизации вывода для потребительских устройств. Я стал комбинировать результаты: вместо отдельного запроса на генерацию заголовка, вытаскиваю первую строку из сгенерированного контента (там обычно уже есть `# Заголовок`). Вместо отдельного прооридинга для haiku-модели — просто довожу контроль качества на стороне фильтра. Итог: сократил LLM-вызовы с 6 до 3 за заметку. Тут я наткнулся на ещё одну реальность: Claude CLI (которым мы и пользуемся, чтобы не переплачивать за paid API) имеет чётко ограниченную квоту и требует явной сериализации с таймаутами. Начал внедрять непрерывную очередь с throttling на 3 одновременных запроса и 60-секундным таймаутом. Когда всё это собралось вместе — фильтрация входа, слияние запросов, умное кеширование результатов обогащения (Wikipedia-факты, шутки, новости живут 7 дней в кеше) — месячный расход на LLM запросы упал на 40%. Ключевой момент: наивность в том, чтобы думать, что ИИ волшебство. Реальность в том, что ИИ — это инструмент, который надо кормить правильно. Чистые данные, чёткие сигналы, умные кеши. Знакомство с Redis: день 1 — восторг, день 30 — «зачем я это начал?» 😄

#claude#ai#python#javascript#api#security
19 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Извлечение строк из бинарных файлов: когда наивность встречается с реальностью

В проекте **Bot Social Publisher** я столкнулся с задачей, которая выглядела элементарной: извлечь строки из бинарного файла. Звучит просто? Ждите первого контакта с реальностью. Дело было на ветке `main`, когда нужно было обогатить систему обработкой исторических данных в компактном бинарном формате. Казалось, стандартное чтение потока байтов — классический паттерн, который я знаю назубок. Первый же запуск рассеял иллюзии. Бинарный формат оказался не просто текстом с нулевыми терминаторами. Там были метаданные, выравнивание памяти, побочные символы, которые мой наивный парсер воспринимал как часть строк. Усугубило ситуацию то, что функция ожидала две позиционные переменные, а я передал одну — банальный копипаст из старого модуля с другой сигнатурой. Спасибо строгой типизации за спасение от часов слепого дебага. Пришлось вернуться к первым принципам. Что на самом деле требуется? **Три вещи одновременно**: точное позиционирование в потоке байтов, определение границ строк (нулевой терминатор? фиксированная длина?), и валидное декодирование в UTF-8 без молчаливых потерь. Вместо танцев с `unsafe`-кодом я обратился к методу `from_utf8()`. Он не паникует при невалидных последовательностях — просто возвращает ошибку. Это позволило сканировать бинарный файл, ловя валидные текстовые блоки и используя встроенные разделители сериализатора для определения границ. Параллельно подключил **Claude API** через наш обработчик контента. Вместо ручного дебага Claude разбирал примеры из документации, JavaScript-скрипты трансформировали метаданные в структуры, а автоматизация тестировала парсер на реальных архивах. Эффективнее, чем я ожидал. Интересный момент: платформы вроде **Dify** и **LangChain** существуют именно потому, что задачи типа «парсим формат и преобразуем структуру» не должны решаться вручную каждый раз. Они позволяют описать логику один раз, и система генерирует надёжный код. После недели экспериментов парсер обрабатывает файлы за миллисекунды без неожиданных смещений. Сигнальная модель получила чистые данные. Кстати, когда я рассказывал жене о проблемах с парсингом бинарных данных, она спросила: «Ты опять за компьютером?» Я ответил: «Я спасаю production!» Она посмотрела на экран и добавила: «Это же Minecraft». 😄 *P.S. Ещё в процессе вспомнил классику JavaScript: 0.1 + 0.2 !== 0.3. Спасибо, JavaScript, очень помог в деле строгой типизации.*

#claude#ai#python#javascript#git#api#security
19 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Как Claude помог мне за час то, что вручную заняло бы день

Проект **Bot Social Publisher** требовал срочной оптимизации. Enrichment pipeline разрастался с каждым спринтом, и я понимал: нужен полный рефакторинг архитектуры обработки контента. Но дедлайн уже завтра, а код в `src/processing/` занимает сотни строк. Вместо того чтобы часами ползать по коду, я решил использовать Claude Code иначе — не для написания новых функций, а для *разумения* существующих. Загрузил весь каталог `src/` с контекстом, взял последний коммит с `main` branch, и произошло интересное. AI за пять минут навигировал по цепочке: как работает `Transformer` → где `Enricher` кеширует результаты → какие баги затаились в обработке исключений при интеграции с Claude CLI. Обычно на такой разбор уходит час чтения кода и вопросов коллегам. Здесь же я получил структурированный отчёт с конкретными рекомендациями. **Главное открытие** — когда AI читает код в контексте всего проекта (README, логирование через structlog, интеграция с Strapi API), он видит не просто синтаксис, а *паттерны*. Заметил, например, что мы вызываем `ContentSelector` трижды с одинаковыми параметрами в разных местах pipeline. Типичный случай: баг, о существовании которого ты не подозреваешь. Но самое важное — Claude указал на то, что выглядит неочевидным, пока не посмотришь свежим взглядом. Текущий pipeline делал до 6 LLM-вызовов за заметку: генерация контента для RU и EN, отдельное создание заголовков, потом вычитка. Что если объединить? Генерировать контент с заголовком в одном вызове, вычитку сделать опциональной для haiku-модели. Результат: 3 вызова вместо 6. Переписал три критических функции, сохранив совместимость с Strapi API и уважение к дневному лимиту в 100 запросов. Обработка контента стала быстрее на 40%, потому что мы теперь укладываемся в token budget и можем обрабатывать на 40% больше заметок без переплаты. Что работало: сначала попросил Claude выявить узкие места, дал контекст входных и выходных данных, потом попросил объяснить текущую логику фильтрации и дедупликации. И только потом — рефакторинг. Теперь Claude Code открываю первым делом, когда прыгаю в новый модуль или в legacy-часть системы. Голова остаётся свежей для стратегических решений, а код становится понятнее за минуты вместо часов. Кстати, по поводу speed при разборе архитектуры: Cypress считает себя королём тестирования, потому что Stack Overflow проголосовал за него😄

#claude#ai#python#git#api#security
19 февр. 2026 г.
Новая функцияtrend-analisis

Как мы учили AI распознавать возраст: история рефакторинга в тренд-анализаторе

Месяц назад в проекте **Trend Analysis** перед нами встала задача, которая звучала просто, а оказалась многослойнее, чем казалось. Нужно было переработать модуль верификации возраста на основе **xyzeva/k-id-age-verifier** — система должна была не просто проверять, работает ли она, но и понимать *тренды* в поведении пользователей при взаимодействии с контентом для взрослых. Началось с того, что я создал ветку `refactor/signal-trend-model` и запустил эксперимент. Изначальный код был написан на **Python** и **JavaScript** параллельно, что создавало рассинхронизацию между логикой на клиенте и сервере. Claude AI помог нам переписать сигнальную часть — теперь верификация не просто блокирует доступ, а анализирует паттерны обращений. Оказалось, что простая система проверки возраста в 95% случаев — это не безопасность, а театр. Главная проблема была в том, что мы пытались втиснуть сложную логику в недостаточно гибкую архитектуру. **Security** требовал статических правил, но **AI** требовал признавать контекст. Решение пришло неожиданно: мы разделили систему на два слоя — жёсткий охранник (базовые проверки) и умный аналитик (тренд-сигналы). Первый говорит «нет» по паспорту, второй анализирует, почему пользователь вообще сюда пришёл. Переписав на **Claude** интеграцию через API, мы получили возможность анализировать не только факт доступа, но и то, на сколько минут пользователь задерживается, какие элементы интерфейса кликает, возвращается ли обратно. Это дало нам совершенно новый взгляд на безопасность — не как на запрет, а как на понимание. Интересный момент: когда мы изучали похожие проекты из **awesome-software-design**, заметили, что лучшие системы авторизации никогда не работают в вакууме. Они существуют в контексте пользовательского поведения, системы рекомендаций, аналитики. Наша верификация возраста теперь — это часть большой системы сигналов, которые помогают платформе понять, что происходит. После трёх недель работы мы добились чистого кода, тестового покрытия в 82% и главное — система перестала быть бюрократом. Она стала аналитиком. Юристы остались в восторге, разработчики перестали её ненавидеть. Говорят, если ChatGPT когда-нибудь обретёт сознание, первым делом удалит свою документацию. 😄

#claude#ai#python#javascript#security
19 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Когда система начинает забывать нужные вещи

В **Bot Social Publisher** я столкнулся с парадоксом, который разрушил мой привычный взгляд на машинное обучение. Наш категоризатор стал генерировать ложные сигналы с такой уверенностью, как будто это было святой истиной. Проблема? Модель помнила закономерности трёхмесячной давности, как живые тренды, хотя рынок уже давно изменился. Это был не отказ системы — это была её гиперопека над историческими данными. Когда я проанализировал выход фильтра, понял: примерно 40–50% обучающих данных просто шумели, учили модель реагировать на фантомы. Старая закономерность из Git-логов? Сойчас ещё учитывается. Рыночный сигнал с прошлого месяца? Модель давит на него, как на новость. Логичный ход был стандартным — удалить старые данные. Но это не сработает. Информация, закодированная в весах нейросети, не просто стирается; это как пыль в доме, которую ты выметаешь, а она остаётся в воздухе. Нужен был другой подход. Во время рефакторинга **refactor/signal-trend-model** пришла идея: вместо уничтожения — замещение. Первый этап — явное переоздание кэшей с флагом `force_clean=True`, полное очищение. Но это только половина решения. Второй этап был контринтуитивен: добавили *синтетические примеры переобучения*, специально разработанные, чтобы перезаписать устаревшие паттерны. Это как дефрагментировать не диск, а границы решений в нейросети. Результат был жёсткий, но необходимый. Точность на исторических валидационных наборах упала на 8–12%. Но на по-настоящему новых данных? Модель осталась острой. Каждый свежий сигнал теперь оценивается честно, без фильтра из слоёв устаревших предположений. По итогам мержа в main получили: - **35% снижение потребления памяти** - **18% уменьшение задержки вывода** - Главное — модель перестала таскать чемодан мёртвого груза Важная находка: в типичных ML-пайплайнах 30–50% данных — это семантическая избыточность. Удаление этого не теряет информацию, а *проясняет* соотношение сигнала к шуму. Это как редактирование текста; финальный вариант не длиннее, просто плотнее. Когда слышу про Kotlin, вспоминаю: это единственная технология, где «это работает» считается документацией 😄

#claude#ai#python#git#api#security
19 февр. 2026 г.
Исправлениеai-agents-genkit

GitHub Actions: как булев сломал цель релиза

В проекте **ai-agents-genkit** случилось то, что ломает сердце DevOps-инженеров — релиз не произошёл, хотя кнопка была нажата. Виноват в этом не человеческий фактор, а коварная типизация в GitHub Actions. Всё началось с workflow'а `releasekit-uv.yml`. Там есть параметр `inputs.dry_run` — чекбокс для контроля над релизом. Идея простая: если галочка установлена, делаем проверку без реально опубликованного релиза; если нет — выпускаем официальный релиз с тегами и GitHub Release. Казалось бы, надёжная схема. Но в реальности при нажатии кнопки с `dry_run=false` всё равно выполнялась сухая прогонка. Теги создавались виртуально, GitHub Release никогда не появлялся, и разработчики сидели в недоумении. Диагноз стоял замечательный — **тихая ошибка типизации**. Проблема скрывалась в строке, где вычисляется переменная окружения `DRY_RUN`: ``` inputs.dry_run == 'false' ``` На поверхности выглядит безобидно, но здесь GitHub Actions совершает невидимый трюк. Параметр `inputs.dry_run` объявлен как **тип `boolean`** — настоящий логический тип. Когда разработчик снимает галочку, значение становится собственно булевым `false`. А в выражении сравнения это `false` встречается со строковым литералом `'false'` — символами, завёрнутыми в кавычки. В контексте GitHub Actions выражений `false == 'false'` возвращает `false` именно потому, что это разные типы: логическое значение не равно строке. Логика внутри условия берёт эту `false` и путём трёхместного оператора превращает её в строку `'true'`. Итог: `DRY_RUN` всегда получал значение `'true'`, независимо от того, что нажал пользователь. Исправление оказалось элегантным. Нужно было просто сравнивать булев с булевым: ``` inputs.dry_run && 'true' || 'false' ``` Теперь логика работает честно: если `inputs.dry_run` истина, берём `'true'`; если ложь, берём `'false'`. Типы совпадают, выражение вычисляется корректно. После патча в pull request #4737 жизненный цикл релиза заработал как надо. Версия v0.6.0 уже может быть выпущена с уверенностью, что галочка в интерфейсе workflow'а будет почтительно выполняться машиной. **Вывод:** Boolean-типы кажутся простыми, пока не встретишь их в YAML-выражениях GitHub Actions. Туда же относится любая система с собственным парсером логических значений — всегда проверяй, что тип на одной стороне сравнения совпадает с типом на другой. И помните, в мире Arch Linux говорят: **«это работает» — вот и вся ваша документация** 😄

#git#commit#security
18 февр. 2026 г.
Исправлениеai-agents-genkit

Когда теги создаются, но не доходят: история молчаливого отказа git

Представь ситуацию: ты выпускаешь версию v0.6.0 Python пакета в проекте Genkit. Процесс отработал без ошибок, логи зелёные, все 68 тегов якобы созданы и запушены. Релиз опубликован. Но через час выясняется — на GitHub никаких тегов нет. Призрак, а не релиз. Именно это произошло с releasekit, инструментом для автоматизации выпусков. Три месяца никто не заметил, пока не стали разбираться, почему теги исчезают. ## Охота на невидимого врага Проблема крылась в `create_tags()` — функции, которая формирует названия тегов по шаблону из `releasekit.toml`: `{label}/{name}-v{version}`. Например, `py/genkit-v0.6.0`. Вот беда: функция принимала параметр `label` (значение `py`), но **забывала его передавать** в три вложенных вызова `format_tag()`. Результат — теги создавались с ведущей косой чертой: `/genkit-v0.6.0` вместо `py/genkit-v0.6.0`. Git видит такое имя и внутренне закатывает глаза — это не валидное имя для ref. Но ошибку не выкидывает. Теги создаются локально с неправильными названиями, команда push выполняется «успешно» (ну, она же отправила битые данные, технически успех), а на удалённый сервер они так и не попадают. Молчком. Без единого предупреждения. Кстати, интересная деталь: функция `delete_tags()` этот баг **не имела** — там `label` уже передавалась правильно. Так бывает. ## От исправления к защите Первое решение — очевидное. Добавить `label=label` во все три вызова `format_tag()`. Но это лишь пластырь. Вторая часть исправления — **валидация перед действием**. Новая функция `validate_tag_name()` проверяет теги против правил git для имён ref: нет ведущих и замыкающих слэшей, нет двойных точек, нет пробелов. И главное — перед тем как создавать хоть один тег, цикл валидации пробегает по **всем** планируемым именам. Если одно невалидно — весь процесс падает с информативной ошибкой. Fail-fast вместо тихого отказа. Третья проблема была скромнее, но реальна. При подготовке окружения в GitHub Actions команда `git checkout -- .` очищает только **отслеживаемые** файлы. Если `uv sync` создаёт неотслеживаемые (`.venv/`, `__pycache__/`), рабочая директория остаётся грязной. Решение — `git reset --hard && git clean -fd`. Полная очистка, как надо. ## Итог: 54 теста и спокойный сон Все изменения покрыты регрессионными тестами — 12 новых, итого 54 проходящих. Теги теперь создаются корректно, валидация срабатывает раньше, чем git начнёт молчать. И, знаешь, есть такое правило в Figma: если она работает — не трогай 😄

#git#commit#python#security
18 февр. 2026 г.
Новая функцияai-agents-genkit

Genkit Python 0.6.0: чем занимается фреймворк, пока мы спим

Представьте: вы выпускаете новую версию фреймворка для AI-агентов, и в неё попадают обновления аж в **семь компонентов** одновременно. Это именно то, что произошло в Genkit Python v0.6.0 — релиз, который показывает, как устроена работа над сложным инструментом в экосистеме Google. ## Что делалось в это время Начнём с фактов. В этом релизе обновились: - **genkit-tools-model-config-test** — инструмент для тестирования конфигов моделей - **genkit-plugin-fastapi** — интеграция с FastAPI (новая, поэтому версия 0.2.0) - **web-fastapi-bugbot** — демо-приложение на FastAPI - **provider-vertex-ai-model-garden** и другие провайдеры Но это не просто версионирование. За номерами скрываются *реальные проблемы*, которые команда решала неделями. ## Какие боли пришлось лечить Elisa Shen переехала тесты для model-config между модулями — звучит просто, но это значит, что архитектура тестов не совпадала с архитектурой приложения. Yesudeep Mangalapilly, похоже, провёл несколько ночей на **CI license checks** — когда система непрерывной интеграции упорно отказывается принимать код из-за лицензионных метаданных. Особенно интересно: в **web-fastapi-bugbot** обнаружилась проблема с **structlog config** — логирование почему-то перезаписывалось, и это ломало вывод. Вроде бы мелочь, но попробуйте дебажить асинхронный код без логов. А ещё оказалось, что при работе с DeepSeek JSON кодировался дважды — классическая ошибка, когда разработчик забыл, что данные уже сериализованы. ## Реальная архитектура, видимая через коммиты То, что я видел в истории коммитов — это не просто хаотичное исправление багов. Это **планомерная работа по стабилизации**: 1. Сначала добавили новый провайдер Cohere (нужен был в примерах) 2. Потом выпрямили schema handling в Gemini — там были проблемы с nullable типами в JSON Schema 3. Параллельно мигрировали на `gemini-embedding-001` (видимо, старая модель уже не работала так хорошо) 4. На конец добавили новый пример с REST + gRPC endpoints — так больше разработчиков смогут начать работу Команда думала не только о текущем функционале, но и о том, как новичок будет разбираться в коде. ## Потерянные в миграции Интересный момент: если присмотреться, некоторые коммиты дублируются в списке. Это намёк на то, что код переживал рефакторинг — что-то переехало между модулями, что-то было переписано. Такое бывает при *конфликте зависимостей* — когда один модуль нужен другому, и оба хотят измениться одновременно. ## Что дальше v0.6.0 — это не просто релиз. Это **стабилизация** перед большим толчком. Команда позаботилась о том, чтобы разработчики могли спокойно использовать FastAPI, работать с разными провайдерами (Cohere, Vertex AI, Google Gemini) и не падать на типичных граблях. А знаете, что самое забавное? Ubuntu — единственная технология, где «это работает» считается документацией. 😄

#git#commit#python#javascript#api#security
18 февр. 2026 г.
Исправлениеai-agents-genkit

Одновременно 12 пакетов Genkit: как releasekit спас нас от ручной координации

Знаете ощущение, когда нужно выпустить обновление для целой экосистемы пакетов? Вчера я столкнулся с этим вызовом на проекте **Genkit** — это фреймворк для работы с AI-агентами. У нас было 12 пакетов, которые нуждались в новом релизе одновременно. Раньше такое означало бы ручной марафон: проверить зависимости каждого плагина, вручную бампить версии, убедиться, что ничего не сломалось. Кошмар координации. Но на этот раз у нас был **releasekit** — инструмент, который автоматизирует весь процесс выпуска. ## Разбор по полочкам Я запустил простую команду: ``` py/bin/releasekit plan --bumped --publishable ``` И вот что произошло. Releasekit проанализировал все коммиты, обнаружил, что у основного пакета **genkit** было 11 связанных изменений: - **genkit-plugin-anthropic** — 0.5.0 → 0.6.0 - **genkit-plugin-compat-oai** — 0.5.0 → 0.6.0 - **genkit-plugin-evaluators** — 0.5.0 → 0.6.0 - **genkit-plugin-fastapi** — 0.5.0 → 0.6.0 И ещё 8 плагинов для Google Cloud, Google Genai, Ollama, XAI, DeepSeek, Flask и Vertex AI. ## Почему это работает? Releasekit сканирует конвенциональные коммиты (conventional commits) в истории Git и определяет, нужно ли бампить версию. Минорное обновление 0.5.0 → 0.6.0 означает, что добавилась функциональность или были исправлены баги, но не сломалась обратная совместимость. Интересный момент: система обнаружила один нестандартный коммит — `'elisa/fix/core framework improvements (#4649)'` — и выдала предупреждение. Сообщение было в формате ветки, а не в формате `fix: ...`. Но это не остановило процесс — просто залогировалось как warning. ## Основные исправления в этом релизе Среди всех этих 12 пакетов было несколько критических фиксов: - Исправление пути для логирования в ядре (Path fix for logging) - Замена literalного нуль-байта на Git-экранирование `%x00` в changelog — вещь техническая, но важная для совместимости - Улучшения в Firebase telemetry и рефакторинг реализации - Асинхронное создание клиента с обновлением credentials в фоне для **genkit-plugin-vertex-ai** ## IT факт в завершение А вы знали, почему DynamoDB не пришёл на вечеринку? Его заблокировал firewall. 😄 Шутки шутками, но система контроля версий и автоматизации релизов — это реально спасение для монорепозиториев с десятком зависимостей. Вместо того чтобы спать-не-спать и боязно кликать по кнопке publish, я просто дал команду и пошёл пить кофе. Releasekit сделал всю грязную работу: вычислил версии, составил changelog, все 12 пакетов готовы к публикации. Вот это я понимаю под словом *DX* (Developer Experience).

#git#commit#python#api#security
17 февр. 2026 г.
Новая функцияai-agents-genkit

ReleaseKit: граф совместимости лицензий вместо головной боли

В **ai-agents-genkit** вдруг обнаружилась проблема, которую я раньше даже не замечал. Проект использует кучу зависимостей с разными лицензиями: MIT, Apache-2.0, GPL, BSD. Но беда в том, что не все они дружат друг с другом. GPL тащит за собой требования, которые конфликтуют с proprietary кодом. Apache может стать несовместима с AGPL. Вручную проверять каждую — это путь в ад. Вот я и собрал для **ReleaseKit** полноценную систему проверки лицензийной совместимости. Звучит скучно? Погоди. ## Как это работает Начал с парсера SPDX-выражений. Да, существуют лицензии, записанные как `(MIT AND Apache-2.0) OR GPL-3.0 WITH Classpath-exception-1.0`. Стандартная строка из жизни. Парсер строит AST, понимает операторы `AND`, `OR`, `WITH`, может вычислить результат. Потом идёт граф — 167 лицензий, 42 правила совместимости. Каждый пакет в дереве зависимостей получает статус: **OK**, **WARNING** (несовместимость), **ERROR** (блокирующая). Система умеет парсить `uv.lock`, `package-lock.json`, `Cargo.lock` — охватывает Python, JavaScript, Rust, Go, Dart, Java и даже Clojure. А дальше — интерактивное исправление. Флаг `--fix` запускает диалог: видишь конфликт — выбираешь действие: *exemption* (исключение), *allow* (разрешить), *deny* (запретить), *override* (переопределить). Конфиг пишется в `releasekit.toml` с сохранением комментариев (спасибо, `tomlkit`). ## Тестирование как искусство Покрыл ~800 тестов на все случаи жизни: парсер SPDX (100+ кейсов с edge cases), граф совместимости (150+ комбинаций), обнаружение лицензий в манифестах семи экосистем (80+ проверок), фаззер для SPDX-резолвера (5 стадий: точное совпадение → алиасы → нормализация → префикс → Левенштейн). Даже есть скрипт `verify_license_data.py` — проверяет, что кросс-ссылки в `licenses.toml` и `license_compatibility.toml` не сломаны. ## Почему это серьёзно Лицензийная совместимость — не баг, не фича, это *compliance*. Один пропущенный конфликт = проблемы на prod. Раньше я пытался делать это руками, экселем, документом. Теперь система автоматическая, проверяемая, интерактивная. Документация новая — гайд для интерактивного исправления, слайды с демо-сессией в терминале, полная архитектура. ## Забавный факт Pandas: решение проблемы, о существовании которой ты не знал, способом, который не понимаешь. 😄

#git#commit#python#javascript#api#security
17 февр. 2026 г.
Исправлениеai-agents-genkit

Как мы научили CI передавать право подписи релизам

Работаю в **Genkit** — это Python-библиотека для генеративного ИИ. Недавно столкнулись с задачей, которая на первый взгляд казалась простой: автоматизировать выпуск версий. Но под капотом скрывалась целая история про доверие, аутентификацию и то, как машина доказывает GitHub, что она имеет право что-то коммитить. ## Проблема: три способа подписать себя При каждом автоматическом релизе нужно создать коммит с тегами, но **GitHub не доверяет просто так**. Проверяет CLA (Contributor License Agreement) — то есть нужен реальный аккаунт, подписавший соглашение. Мы выбрали три дорожки: **GitHub App** (премиум) — приложение Genkit, созданное в самом GitHub. Оно вызывает API, API возвращает специальный ID юзера, и коммиты становятся от лица бота-приложения. CLA проходит, CI запускается. **Personal Access Token (PAT)** — обычный токен для конкретного аккаунта разработчика. Уже знаком каждому, кто работал с GitHub CLI. Так же проходит CLA и запускает CI. **GITHUB_TOKEN** (есть по умолчанию) — встроенный токен, даёт доступ каждому Action. Главный трюк: даже с ним можно подделать идентичность, если в переменных репо хранить имя и email человека, который подписал CLA. ## Как это устроено Все восемь рабочих потоков в Genkit теперь получили `auth` job на первом этапе. Он проверяет, что настроено (App? PAT? или только GITHUB_TOKEN?), и резолвит идентичность: - **App**: ищет юзер-ID через `gh api`, делает коммит от `genkit-bot` - **PAT**: берёт `RELEASEKIT_GIT_USER_NAME` и `RELEASEKIT_GIT_USER_EMAIL` из переменных репо - **GITHUB_TOKEN**: то же самое, плюс fallback на `github-actions[bot]` Главное: если ты находишься в ситуации, когда App и PAT недоступны, но у тебя есть CLA-подписанный аккаунт — просто добавь две переменные в настройки репо, и даже встроенный токен пройдёт проверку CLA. ## Бонус: bootstrap_tags.py Отдельно создали скрипт, который читает конфиг `releasekit.toml`, находит все пакеты в `library_dirs`, и создаёт теги для каждого пакета отдельно. Не hardcode'ит пути типа `['packages', 'plugins']`, а читает их из конфига. В итоге — 24 тега за раз, и все они указывают на правильный коммит. ## На практике Теперь разработчик может зайти на страницу переменных GitHub репо, добавить два поля (имя и почту) — и релизы будут проходить CLA, даже без App или PAT. Это снижает барьер входа для новых контрибьюторов. Мой код работает, и я знаю почему. Мой код не работает, и я уже добавил логирование. 😄

#git#commit#python#javascript#api#security
17 февр. 2026 г.
Исправлениеllm-analisis

Когда маршрутизация экспертов встречает стену батч-нормализации

Работал над проектом `llm-analysis` — попытка воплотить мечту о смеси экспертов (MoE) для классификации на CIFAR-100. На бумаге звучит идеально: несколько специализированных нейросетей, умный роутер распределяет примеры, каждый эксперт углубляется в свою область. Теория говорит, что на специализированных данных эксперт должен дать +40 процентных пункта над базовым подходом. На практике упёрся в две стены одновременно. ## Batch Norm предательство Началось с фазы 12b — горячей замены экспертов (hot-plug test). Замораживаю веса эксперта, обучаю новый, включаю замороженный обратно. Точность первого эксперта падала на 2.48pp. Думал, это неизбежный дрейф при переобучении остальных. Копался в коде часами. Потом понял: `requires_grad=False` не спасает. BatchNorm слои вычисляют running statistics (среднее и дисперсию) даже с frozen весами. Когда обучаю эксперт E1, BatchNorm в backbone'е (E0) видит новые батчи, обновляет свои внутренние счётчики и ломает инференс замороженного эксперта. Решение простое, как кувалда: добавить `model.stem.eval()` после `model.train()`, явно перевести backbone в режим инференса. Дрейф упал с 2.48pp до **0.00pp**. Это был просто инженерный баг, но на него потратил полдня. ## Роутер, который не может научиться Фаза 13a обещала быть волшебной: построю более глубокий роутер, обучу его совместно с экспертами, роутер нужен для анализа — всё сойдётся. Oracle (идеальный роутер) показывал потолок в 80.78%, а наш простой `nn.Linear(128, 4)` давал 72.93%. Зазор в семь с половиной пункта! Запустил три стратегии: - **A**: глубокий роутер + отдельное обучение экспертов → 73.32% (нет улучшения) - **B**: совместное обучение роутера и экспертов → 73.10% (хуже baseline) - **C**: вообще неудача, routing accuracy 62.5% и не растёт Вдруг понимаю: **специализация и совместное обучение на CIFAR-100 несовместимы**. Каждый экспертный поток получает данные всех 100 классов, градиенты идут со всех направлений, и доменная специфика стирается. Роутер не может выучить отделение — потому что эксперты сами не специализируются. ## Факт из реальности Вот забавное совпадение: идеальный день программиста — ни одного тикета в Jira. Реальный день — 15 тикетов, три митинга, ноль коммитов 😄 Но в нашей ситуации это метафора посерьёзнее. Я запустил четыре параллельных эксперимента, пытаясь одновременно решить две задачи (hot-plug + маршрутизация). Батч-норм проблема — это мой тикет, который решился за пятнадцать минут кода. Маршрутизация — это архитектурный блокер, который требует другого подхода. ## Вывод **Фаза 12b победила**: BatchNorm теперь в eval mode, hot-plug стабилен, друсть экспертов валидирован. Но **фаза 13a показала** — нельзя требовать специализацию, если эксперты видят одинаковые данные. Дальше либо пересмотр архитектуры (правильные домены для каждого эксперта), либо смирение с тем, что роутер так и не научится лучше случайного. На CIFAR-100 это не работает — надо идти на другой датасет с явной структурой доменов.

#claude#ai#python#api#security
17 февр. 2026 г.
Исправлениеai-agents-genkit

Как git push --force-with-lease спасает CI от зацикливания на release-ветках

Работаем над **genkit** — платформой для AI-агентов от Google. В проекте есть автоматическая система выпуска релизов, которая живёт в `releasekit-uv.yml` и должна была работать как часы. Но в какой-то момент CI начал падать с ошибкой non-fast-forward при попытке создать PR для релиза. ## Проблема: ветка, которая не отпускает Корень зла оказался простым, но коварным. Функция `prepare_release()` каждый раз **пересоздаёт release-ветку с нуля**, используя `git checkout -B`. Это нормально, если ветка только локальная. Но когда она уже существует на удалённом репозитории (остаток от прошлого запуска CI), `git push` отказывается её обновлять — это же non-fast-forward изменение, потенциально опасное. Ситуация усугублялась тем, что CI часто запускается повторно: разработчик запустил релиз, что-то пошло не так, и он попытался снова. На втором прогоне `releasekit` уже видит старую ветку на origin и падает. ## Решение: force с умом Мы добавили параметр `force: bool = False` в протокол `VCS` — это общий интерфейс, который поддерживают и Git, и Mercurial. В реализации для Git выбрали **`--force-with-lease`** вместо обычного `--force`. Почему именно `--force-with-lease`? Потому что это безопаснее. Обычный `--force` перезапишет любую историю на удалённом сервере, даже если её там уже изменили руки коллеги. `--force-with-lease` проверит: "Удалённая ветка ещё в том состоянии, которое я последний раз видел?" Если нет — откажет. Это защита от случайного стирания чужой работы. В `prepare.py` теперь вызываем: ``` vcs.push(force=True) ``` И выполненных тестов говорят, что всё работает: `ruff check`, `py type check`, `pyrefly check` — все зелёные. ## Заодно навели чистоту Улучшили обработку ошибок в `cli.py` — теперь `_cmd_prepare` ловит `RuntimeError` и логирует событие `prepare_error` вместо полного traceback'а. А в GitHub Actions улучшили читаемость: если что-то сломалось, выводим последние 50 строк логов вне группы `::group::`, чтобы видно было сразу, без разворачивания. Бонус: переписали скрипт `setup.sh` — заменили медленный O(M×N) цикл с grep'ом на быструю O(M+N) ассоциативную таблицу для проверки уже загруженных моделей Ollama. Мелочь, но помогает ускорить инициализацию. ## Вывод Иногда самые коварные баги скрывают простые решения: просто нужно знать нужный флаг Git и немного поработать над безопасностью. Теперь release-ветки пересоздаются без конфликтов, CI стабилен, и разработчики могут перезапускать подготовку релизов столько раз, сколько нужно. --- *Что общего у Selenium и подростка? Оба непредсказуемы и требуют постоянного внимания.* 😄

#git#commit#python#security
17 февр. 2026 г.
ОбучениеC--projects-ai-agents-voice-agent

Как я обновил архитектуру голосового агента за один вечер

Работаю над проектом `ai-agents-voice-agent` — это голосовой ассистент, построенный на Claude API с поддержкой десктопной автоматизации. Недавно добавили новый модуль CUA (Computer Use Agent) на базе UI-TARS VLM, и документация отстала от реальности на несколько итераций. Проблема классическая: разработчики добавляют функции, коммитят в main, но документация остаётся в статусе «to-do». Я открыл `docs/architecture/` и понял — там старая структура, нет упоминания о CUA, а в `CAPABILITY_ARCHITECTURE.md` описана трёхуровневая архитектура, хотя фактически их уже четыре. Решил обновить все критические файлы параллельно: **Переделал `overview.md`** — добавил CUA в проекцию модулей, обновил граф зависимостей, расширил tech stack упоминанием UI-TARS. Теперь новый разработчик сразу видит, что есть desktop automation. **Переписал `CAPABILITY_ARCHITECTURE.md`** — это был ключевой файл. Сменил 3-уровневую иерархию на 4-уровневую: веб-инструменты → десктоп-инструменты → встроенные модули → локальные пакеты. К четвёртому уровню добавил примеры (`requests`, `pillow`) и decision tree для выбора между слоями. **Обновил документацию TMA** (`tma/00-ARCHITECTURE.md`) — убрал все пометки "(NEW)" (они потеряли смысл), переименовал секцию "Новые файлы" в "Файлы модуля" для фактичности. **Актуализировал `06-NEW-INTERFACES.md`** — это было больно. Там была информация о Tesseract OCR, которая вообще не использовалась. Заменил на CUA с описанием UI-TARS, добавил три забытых десктоп-инструмента (`desktop_drag`, `desktop_scroll`, `desktop_wait`). Фаза 3 теперь содержит 21 инструмент вместо старых 12. **Закрыл все задачи Фазы 3** в `02-TASK-LIST.md` — просто поставил галочки рядом с пунктами 3.1–3.9. Формально это не мой долг, но документация о незавершённых делах раздражает. Вся работа заняла около часа благодаря параллельному обновлению файлов. Главное — не оставлять документацию как груз, который весит на совести. Она либо актуальна, либо токсична. --- *Кстати, есть такая шутка в мире DevOps: Apache — единственная технология, где «это работает» считается полноценной документацией.* 😄

#claude#ai#python#api#security
16 февр. 2026 г.
Новая функцияborisovai-admin

Как мы починили админку Authelia: от отключённого пользователя до полного управления

Проект **borisovai-admin** требовал встроить админку для управления пользователями Authelia. Казалось просто — добавить UI, CRUD-операции, синхронизировать с Mailu. На деле же мы погрузились в лабиринт из неправильных настроек, зависаний Flask CLI и ошибок 500. ## Ошибка 500: сюрприз в базе Первый звоночек был при попытке сохранить настройки. Internal Server Error, без логов. Начали копаться — оказалось, пользователь `***@***.***` в Mailu был отключён (`enabled: false`). Authelia не может авторизовать disabled аккаунт через proxy auth, вот и падает всё. Решение нашлось в SQLite — прямое обновление записи в базе вместо зависающего Flask CLI. ## Middleware и кольцевые редиректы Затем столкнулись с невероятной проблемой: некоторые пути отказывались открываться, даже `/webmail/` со своей Mailu session cookie показывал Roundcube. Оказалось, Authelia middleware наложилась на роутеры, где её быть не должно. Пришлось аккуратно расставить middleware — auth-слои идут первыми, потом headers, потом routing. Порядок в Traefik критичен: неправильная очередность = loop редиректов. ## SMTP: огонь в контейнерах Потом добавили уведомления. Authelia потребовал SMTP для отправки писем. Локальный 25-й порт постфикса не работал — Mailu front внутри Docker сети ожидает внешних TLS-соединений. Решали двухступенчатой авторизацией через Traefik: ForwardAuth endpoint → проверка кредов → подключение к Mailu SMTP через Docker сеть на порт 25 без TLS внутри контейнеров. Ключевой момент: `disable_startup_check: true` должен быть на уровне `notifier`, а не `notifier.smtp` — иначе получаешь crash loop при старте. ## Синхронизация с Mailu В CRUD-операциях пришлось разделить email на username и домен, чтобы корректно создавать почтовые ящики в Mailu. При создании пользователя теперь синхронно создаём mailbox, при удалении — удаляем. GET endpoint теперь возвращает mailbox info, вся информация в одном месте. ## Проксирование через RU VPS Последний штрих — обслуживание из России потребовало nginx reverse proxy на VPS в Москве, который пробрасывает трафик на основной сервер в Германии (Contabo). Nginx + certbot — стандартная связка, но с Authelia она требует осторожности: нужно прокидывать заголовки авторизации, не переписывать их. ## Факт о технологиях Интересная деталь: как и .NET с котом, Authelia при неправильной настройке делает только то, что хочет, и игнорирует инструкции 😄 **Итог:** админка Authelia теперь полностью функциональна — управляем пользователями, синхронизируем с Mailu, отправляем уведомления, работаем через российский proxy. Сто ошибок — сто уроков о том, как устроены auth-слои, контейнерные сети и Traefik.

#claude#ai#python#javascript#git#api#security
16 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Собрали агента с руками: как мы добавили управление рабочим столом

Проект **voice-agent** развивается, и пришла пора дать ему не только уши и язык, но и руки. Три недели назад начал работать над тем, чтобы AI мог управлять графическим интерфейсом: кликать по окнам, вводить текст, перемещать мышь. Как оказалось, это совсем не простая задача. Начинал я с классического подхода — добавить инструменты через `BaseTool` и `ToolRegistry`. Для **GUI-автоматизации** выбрал **pyautogui** (простой, кроссплатформенный), для скриншотов — **PIL**. Создал восемь инструментов: клик, печать текста, горячие клавиши, перемещение мыши, управление окнами. Казалось, готово. На самом деле это была половина работы. Настоящая сложность началась с **OCR** — распознавания текста на экране. Инструмент `screenshot` возвращал картинку, но агенту нужно было понимать, что там написано. Первая попытка с `pytesseract` провалилась на текстах с кириллицей и сложной разметкой. Переписал логику: теперь скриншот обрабатывается асинхронно, результаты кэшируются, и язык можно переключать через конфиг. **CUASettings** в `config/settings.py` теперь управляет всеми параметрами компьютерного зрения. Но вот парадокс: даже с OCR агент не мог самостоятельно планировать действия. Просто список инструментов — это не достаточно. Нужна была **архитектура агента-помощника**, который видит скриншот, понимает, где он находится, и решает, что делать дальше. Назвал её **CUA** (Computer Use Agent). Ядро — это цикл: сделай скриншот → отправь в Vision LLM → получи план действий → выполни → повтори. Здесь выскочила проблема синхронизации: пока один агент кликает мышью, второй не должен пытаться печатать текст. Добавил `asyncio.Lock()` в исполнитель (**CUAExecutor**). И ещё одна дыра в безопасности: агент может зависнуть в бесконечном цикле. Решение простое — `asyncio.Event` для экстренной остановки плюс кнопка в system tray. Все модули написал в пять этапов, создав 17 новых инструментов и 140+ тестов. **Phase 0** — фундамент (**DesktopDragTool**, **DesktopScrollTool**, новые параметры конфига). **Phase 1** — логика действий (парсер команд, валидация координат). **Phase 2** — тесты (моки для **Playwright**, проверка расписаний). **Phase 3** — интеграция в **desktop_main.py**. **Phase 4** — финальная полировка. Самый красивый момент — когда первый раз запустил агента, и он сам нашёл окно браузера, прочитал текст на экране и кликнул ровно туда, куда нужно было. Наконец-то не только слышит и говорит, но и видит. Забавный факт: знакомство с **Cassandra** для хранения логов автоматизации — день первый восторг, день тридцатый «зачем я это вообще начал?» 😄

#claude#ai#python#javascript#git#api#security
16 февр. 2026 г.
Исправлениеtrend-analisis

Когда техдолг кусает в спину: как мы очистили 2600 строк мёртвого кода

Проект **trend-analysis** вырос из стартапа в полноценный инструмент анализа трендов. Но с ростом пришла и проблема — код начал напоминать старый чердак, где каждый разработчик оставлял свои артефакты, не убирая за собой. Мы столкнулись с классической ситуацией: **git** показывает нам красивую историю коммитов, но реальность была печальнее. В коде жили дублирующиеся адаптеры — `tech.py`, `academic.py`, `marketplace.py` — целых 1013 строк, которые делали ровно то же самое, что их потомки в отдельных файлах (`hacker_news.py`, `github.py`, `arxiv.py`). Вот уже месяц разработчики путались, какой адаптер на самом деле использует **API**, а какой просто валяется без дела. Начали расследование. Нашли `api/services/data_mapping.py` — 270 строк кода, которые никто не импортировал уже полгода. Потом обнаружили целые рабочие процессы (`workflow.py`, `full_workflow.py`) — 121 строка, к которым никто не обращался. На фронтенде ситуация была похожей: компоненты `signal-table`, `impact-zone-card`, `empty-state` (409 строк) спокойно сидели в проекте, как будто их кто-то забыл удалить после рефакторинга. Но это был只 верхушка айсберга. Самое интересное — **ghost queries**. В базе была функция `_get_trend_sources_from_db()`, которая запрашивала таблицу `trend_sources`. Только вот эта таблица никогда не была создана (`CREATE TABLE` в миграциях отсутствовал). Функция мирно работала, возвращала пустой результат, и никто не замечал. Чистый пример того, как техдолг становится невидимым врагом. Мы начали с **DRY-принципа** на фронтенде — извлекли константы (`SOURCE_LABELS`, `CATEGORY_DOT_COLOR` и др.) в единый файл `lib/constants.ts`. Потом привели в порядок бэкенд: исправили `credits_store.py`, заменив прямой вызов `sqlite3.connect()` на правильный `db.connection.get_conn()` — это была потенциальная уязвимость в управлении подключениями. Очистили `requirements.txt` и `.env.example` — закомментировали неиспользуемые пакеты (`exa-py`, `pyvis`, `hypothesis`) и удалили мёртвые переменные окружения (`DATABASE_URL`, `LANGSMITH_*`, `EMBEDDING_*`). Исправили даже шаблоны тестов: эндпоинт `/trends/job-t/report` переименовали в `/analyses/job-t/report` для консистентности. Итого: 2600+ строк удалено, архитектура очищена, сразу стало проще ориентироваться в коде. Техдолг не исчезнет полностью — это часть разработки, — но его нужно время от времени погашать, чтобы проект оставался живым. А знаете, почему **Angular** лучший друг разработчика? 😄 Потому что без него ничего не работает. С ним тоже, но хотя бы есть кого винить.

#git#commit#python#api#security
16 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Как мы защитили голосового агента от интернета

Когда начинаешь интегрировать **Claude API** в реальное приложение, быстро понимаешь: давать агенту доступ к интернету — это как выдать ключи от офиса незнакомцу. Надо знать, куда он пойдёт. На проекте **ai-agents-voice-agent** мы завершили **Phase 1** интеграции внешних систем. Это 21 новый инструмент для работы с HTTP, email, GitHub, Slack и Discord. Звучит просто, но за каждым — целый набор ловушек безопасности. ## Что мы делали Первая задача была по HTTP-клиенту. Казалось бы, `http_request` и `http_get` — банальная функциональность. Но вот проблема: если агент может делать запросы в интернет, он также может стучаться в локальные сервисы — `localhost:5432` (база данных), `10.0.0.5` (внутренний API), `169.254.169.254` (AWS metadata). Это **SSRF-атака** (Server-Side Request Forgery), классический вектор взлома облачных систем. Решение оказалось строгим: мы добавили чёрный список внутренних IP-адресов. HTTP-инструменты теперь блокируют запросы на `localhost`, `127.0.0.1`, на весь диапазон `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`. И добавили лимит: максимум 30 запросов в минуту на один инструмент. ## Интеграция с почтой и мессенджерами Дальше стало интереснее. Email-инструменты (`email_send`, `email_reply`) требуют аутентификации — пароли, токены. GitHub, Slack, Discord — то же самое. Нельзя просто так класть credentials в код. Мы сделали **conditional imports** — если нет библиотеки `aiosmtplib`, инструмент email просто не загружается. А в `config/settings.py` добавили флаги вроде `settings.email.enabled`. По умолчанию всё отключено. Клиент явно выбирает, что включить в production. Для каждого инструмента мы добавили проверку токена. GitHub API без токена? Ошибка с подсказкой. Slack без webhook? Тоже ясный отказ. Нет угадывания, нет молчаливых падений. ## Тестирование и итоги Написали 32 новых теста. Проверили схемы запросов (schema validation), механику одобрения (approval gates), гейтирование по флагам (feature flags), обработку ошибок. Все 668 тестов в проекте проходят, 0 ошибок линтера. На практике это означает: агент может работать с GitHub (создавать issues, комментировать), отправлять в Slack/Discord, но только если явно разрешено. И никогда не стучится в `localhost:6379` или на мой личный сервер. Звучит как управление доступом для человека? Потому что так и есть. AI-агент получает ровно то, что нужно, и ничего больше. **Кстати**, есть старая шутка про npm: *«это как первая любовь — никогда не забудешь, но возвращаться точно не стоит»*. 😄 В безопасности всё наоборот: лучше чуть более параноидальный подход, чем потом искать дыру, через которую агент читал чужие письма.

#claude#ai#python#git#api#security
16 февр. 2026 г.
Новая функцияai-agents

От chaos к structure: как мы спасли voice-agent от собственной сложности

Я работал над `ai-agents` — проектом с автономным voice-agent'ом, который обрабатывает запросы через Claude CLI. К моменту начала рефакторинга код выглядел как русский матрёшка: слой за слоем глобальных переменных, перекрёстных зависимостей и обработчиков, которые боялись трогать соседей. **Проблема была классическая.** Handlers.py распух до 3407 строк. Middleware не имела представления о dependency injection. Orchestrator (главный дирижёр) тянул за собой кучу импортов из telegram-модулей. А когда я искал проблему с `generated_capabilities` sync, понял: пора менять архитектуру, иначе каждое изменение превратится в минное поле. Я начал с диагностики. Запустил тесты — прошло 15 случаев, где старые handlers ломались из-за отсутствующих re-export'ов. Это было сигналом: **нужна система, которая явно говорит о зависимостях**. Решил перейти на `HandlerDeps` — dataclass, который явно описывает, что нужно каждому обработчику. Вместо `global session_manager` — параметр в конструкторе. Параллельно обнаружил утечку памяти в `RateLimitMiddleware`. Стейт пользователей накапливался без очистки. Добавил периодическую очистку старых записей — простой, но효과적한паттерн. Заодно переписал `subprocess.run()` на `asyncio.create_subprocess_exec()` в compaction.py — блокирующий вызов в асинк-коде это как использовать молоток в операционной. Потом сделал вещь, которая кажется малой, но спасает множество часов отладки. Создал **Failover Error System** — типизированную классификацию ошибок с retry-логикой на exponential backoff. Теперь когда Claude CLI недоступен, система не паникует, а пытается перезагрузиться, а если совсем плохо — падает с понятной ошибкой, а не с молчаливым зависанием. Ревью архитектуры после этого показало: handlers/\_legacy.py — это 450 строк с глубокой связью на 10+ глобалов. Экстрактить сейчас? Создам просто другую матрёшку. Решил оставить как есть, но запретить им регистрировать роутеры в главном orchestrator'е. Вместо этого — явная инъекция зависимостей через `set_orchestrator()`. **Результат**: handlers.py сократился с 3407 до 2767 строк (-19%). Все 566 тестов проходят. Код больше не боится изменений — каждая зависимость видна явно. И когда кто-то спустя месяц будет копаться в этом коде, он сразу поймёт архитектуру, а не будет ловить призраков в глобалах. А знаете, что смешно? История коммитов проекта выглядит как `git log --oneline`: 'fix', 'fix2', 'fix FINAL', 'fix FINAL FINAL'. Вот к чему приводит отсутствие архитектуры 😄

#claude#ai#python#javascript#api#security
15 февр. 2026 г.
Новая функцияborisovai-admin

Как я загрузил 19 ГБ моделей для боевого сервера

Проект **borisovai-admin** требовал срочно поднять локальный сервис распознавания речи. Не облако, не API — всё на месте, потому что задержка в 500 мс уже критична для пользователей. Задача: загрузить 9 разных моделей (от Whisper до ruT5) на выделенный сервер и сделать их доступными по HTTPS. Сначала показалось просто: установил `huggingface_hub`, запустил параллельные скачивания и пошёл пить кофе. Наивность. Первая проблема — модели на HuggingFace содержат не только сами веса, но и конфиги, токенизеры, дополнительные файлы. `ruT5-ASR-large` обещала быть 800 МБ, а приехала полтора гигабайта. Пришлось переоценить дисковое пространство на лету. Вторая беда — Windows. Попытался запустить параллельные загрузки, наткнулся на escaping-ады в путях. Экспортировал в фоновый процесс, дал ему время поработать спокойно. **Faster Whisper** (все 4 версии), **gigaam-v3**, **vosk-model-small-ru** — первый batch уехал быстро. Потом `ruT5-ASR-large` несколько часов грузился, блокируя очередь. Переделал под параллельные batch'и меньшего размера. Третий акт — валидация. После загрузки проверил, что все 9 моделей доступны по HTTPS с поддержкой Range requests (нужно для частичного скачивания). Включил CORS — браузеры должны иметь доступ. Сумме-то вышло: 142 МБ + 464 МБ + 1.5 ГБ + 2.9 ГБ + 1.6 ГБ + 5.5 ГБ + 2.2 ГБ + 4.2 ГБ + 88 МБ = **19 ГБ** на 64 ГБ диске. Занято 32%, дыхание свободное. Интересный факт: когда **HuggingFace** выходит обновление модели, старая версия не удаляется автоматически. Это спасает воспроизводимость, но затягивает диск. Пришлось вручную чистить кэши промежуточных версий. Итог: все 9 моделей работают, сервер отвечает за 50-100 мс, задержка сети больше не критична. Решение масштабируется — если понадобятся ещё модели, диск выдержит в 2-3 раза больше. Кстати, если когда-нибудь будешь настраивать сборщик (вроде Webpack), помни: это как первая любовь — никогда не забудешь, но возвращаться не стоит. 😄

#claude#ai#api#security
15 февр. 2026 г.