Ежедневный конвейер с GitHub на локальную LLM, который пишет мои проектные карточки
Раздел "Проекты" на этом сайте не поддерживается вручную. Раз в день задача просматривает мои репозитории GitHub, определяет изменения, запрашивает у локальной языковой модели написание краткого описания каждого измененного проекта и сохраняет результат. Я публикую те, которые стоит показать; остальное остается черновиками.
Вот как это устроено — и, что более важно, три правила, которые не дают ему делать ничего глупого.
Поток работы
cron (03:30)
→ GET /api/internal/repos (что я уже знаю: repo → последний SHA)
→ GET api.github.com/user/repos (тонкий, ТОЛЬКО ЧТЕНИЕ токен)
→ diff by default-branch SHA (дальше идут только измененные репозитории)
→ fetch README + languages
→ POST {OLLAMA}/api/chat (gemma, формат: json, think: false)
→ POST /api/internal/projects (слияние в SQLite)
LLM локален — это экземпляр Ollama, запущенный Gemma в локальной сети. Никакой код, README или метаданные репозитория никогда не покидают сеть. Модели задается строгий системный промпт с требованием вести себя как "точный технический копирайтер" и возвращать только JSON-объект:
{ "title": "…", "blurb": "…", "tags": ["…"], "highlight": true }
format: "json" от Ollama гарантирует парсируемый вывод, а think: false отключает трассировку рассуждений модели — это быстрее (~5–6 с/репо) и чище.
Правило 1 — трогать только то, что изменилось
Каждый просмотренный мной репозиторий записан в таблицу repo_state с его последним SHA ветки по умолчанию. Каждый запуск сравнивает текущий SHA с сохраненным; измененные репозитории пропускаются до вызова модели. LLM — это дорогой шаг, поэтому он выполняется только для тех репозиториев, которые действительно изменились. Второй запуск через несколько минут ничего не делает и вызывает модель ноль раз.
Правило 2 — никогда не перезаписывать мои правки
Вывод модели — это автоматические данные. В SQLite каждый проект имеет два параллельных набора полей: автоматические значения (из GitHub/LLM) и значения переопределения (то, что я ввел в админке). На публичном сайте отображается значение переопределения, если оно существует, иначе — автоматическое.
Конечная точка слияния это обеспечивает. Когда репозиторий меняется и конвейер отправляет новые данные, он обновляет столбцы auto и оставляет *_override, published и sort_order нетронутыми. Таким образом, я могу вручную переписать описание, а запуск завтра обновит метаданные GitHub под ним, ни прикоснувшись к моим словам. LLM — это движок предложений, а не источник истины.
Правило 3 — приватное остается приватным
Все мои исходные репозитории частные, и в приватном README может содержаться контекст клиента или внутренний контекст, который я никогда не захочу видеть на публичной странице. Поэтому импорт конфиденциален по умолчанию: вновь обнаруженный репозиторий вставляется как неопубликованный черновик (published = 0). Ничто, что создает конвейер, не видно на сайте, пока я явно не переключу тумблер в админке. Сам токен только для чтения (Contents + Metadata, без прав записи) — он может смотреть, но не может отправлять.
Что делать, если модель недоступна
Поскольку LLM находится в этом офлайн-пакетном режиме, а не в пути запроса, сбой не является проблемой для посетителей — сайт продолжает работать из SQLite. Конвейер также обрабатывает это корректно: если Ollama недоступен, этот репозиторий пропускается (его SHA не продвигается, поэтому он повторяется при следующем запуске), и я получаю одно уведомление в Telegram со списком пропущенных элементов. Никаких сбоев, никаких полусобранных данных, никаких тихих пробелов.
Зачем это нужно
Я постоянно выпускаю небольшие инструменты. Поддержание портфолио в актуальном состоянии вручную — это та самая рутина, которая тихо съедает силы. Это переносит скучную часть — "что я создал и какое однострочное описание" — на машину, работающую каждую ночь, сохраняя при этом все редакционные и конфиденциальные решения твердо за мной.
Интересная автоматизация никогда не стоит между вами и этой страницей. В этом вся задумка.
Прозрачность: большинство постов здесь написаны с помощью ИИ, а неанглийские версии переведены локальной LLM и вычитаны. Заметили ошибку? Напишите мне.