Перейти к содержимому

1.2 · Репозиторий для учёбы: git, notes/, структура

Неделя 1 · Подготовка · ⏱ ~1,5 ч · Практика

К концу курса этот репозиторий станет портфолио: проекты, таблицы evals, заметки, по которым видно, как мы думаем. Работодатель смотрит GitHub раньше резюме, и аккуратная история коммитов с понятной структурой говорит больше, чем строчка «знаю RAG». Кроме того, в AI-проектах много секретов (ключи API) и больших данных (базы, эмбеддинги — векторы, в которые модель превращает тексты, неделя 4), и привычку не коммитить лишнее лучше выработать в первый день. В этой главе превращаем папку с упражнениями главы 1.1 в git-репозиторий: договариваемся о структуре, настраиваем, что git должен игнорировать, и отправляем первый коммит в приватный репозиторий на GitHub.

Варианты: репозиторий на каждый проект или один на всё. Для учёбы удобнее один (ai-engineering-journey), где каждый крупный проект — отдельная папка со своим окружением (изолированный набор библиотек проекта; как его создаёт uv, разберём в главе 1.3). Позже лучшие проекты можно вынести в отдельные публичные репозитории для портфолио (или сделать этот публичным, предварительно проверив историю на секреты и личные данные).

ai-engineering-journey/
├── README.md # the plan + progress log
├── .gitignore
├── .env.example # variable NAMES only, no values
├── notes/
│ ├── python-cheatsheet.md # AIL-1
│ ├── llm-basics.md # AIL-3
│ └── week-01.md # daily notes, Friday review
├── exercises/
│ ├── week-01/
│ │ ├── pricing.py
│ │ ├── orders.csv
│ │ └── stats.py
│ └── week-02/
└── projects/
├── 01-shop-analyst-bot/ # own pyproject.toml, own README
├── 02-support-kb-rag/
└── 04-order-investigator-agent/

Принципы:

  • notes/ — заметки своими словами. Это не мусор: перед собеседованием перечитывать будем именно их.
  • exercises/week-NN/ — короткие упражнения, можно с одним общим окружением на неделю или на весь exercises/.
  • projects/NN-name/ — у каждого проекта свой pyproject.toml (описание проекта и зависимостей), uv.lock (точные версии библиотек), README.md, tests/. Номер в имени сохраняет порядок.

Структура готова; дальше решаем, что из этих папок git хранить не должен.

.gitignore — список шаблонов путей, которые git не предлагает добавить. Синтаксис:

.venv/ # directory anywhere
*.db # by extension, anywhere
/build # only at repo root
!keep.db # exception from a previous rule

Важно: .gitignore не действует на файлы, которые уже отслеживаются. Если .env уже закоммичен, добавление его в .gitignore ничего не убирает из истории. Нужно git rm --cached .env (перестать отслеживать, файл на диске остаётся), а если там был ключ — отозвать ключ (подробно в гл. 1.5), потому что из истории он никуда не делся.

Проверка, что файл игнорируется и каким правилом: git check-ignore -v .env.

Зная синтаксис, составим список. В Python/AI-проекте лишнее бывает трёх видов: то, что генерируется автоматически (окружение, кеши), то, что опасно (секреты, данные), и то, что просто велико (базы, веса моделей):

Категория Шаблоны Почему
Окружение .venv/, venv/ создаётся uv sync, как node_modules в JavaScript
Кеши Python __pycache__/, *.pyc, .pytest_cache/, .ruff_cache/, .mypy_cache/ генерируются автоматически
Секреты .env, .env.*, но !.env.example ключи API
Базы данных *.db, *.sqlite, *.sqlite3, *.db-wal, *.db-shm снимки базы магазина, векторные индексы; WAL-файлы рядом с базой
Данные data/raw/, *.parquet (по ситуации) большие файлы, могут быть личными
Ноутбуки .ipynb_checkpoints/ автосохранения Jupyter (гл. 1.4)
Система/IDE .DS_Store, .idea/ мусор macOS и IDE

uv.lock коммитить нужно — это lock-файл, как package-lock.json или go.sum: он делает окружение воспроизводимым. .vscode/ — по вкусу: settings.json с выбором интерпретатора полезно хранить, личное — нет.

Готовый шаблон GitHub: Python.gitignore. Можно взять его и дописать снизу свой блок (секреты, базы).

Ключи API и настройки, которые отличаются от машины к машине, программа читает из переменных окружения. Чтобы не задавать их руками в каждом терминале, их держат в файле .env в корне проекта — строки ИМЯ=значение, которые библиотека загружает при старте программы (как именно — в главе 1.5). .env в git не попадает (правило выше), но тогда коллега не узнает, какие переменные нужны. Эту проблему решает .env.example:

# .env.example — copy to .env and fill in
# main: DeepSeek (cloud, cheap) + Ollama (local, free)
DEEPSEEK_API_KEY=
DEEPSEEK_MODEL=
OLLAMA_BASE_URL=http://localhost:11434/v1
OLLAMA_MODEL=
# optional, for comparison
ANTHROPIC_API_KEY=
OPENAI_API_KEY=

В git лежит только список имён переменных (и несекретные значения по умолчанию вроде адреса локальной Ollama). Коллега (или мы сами через полгода) клонирует репозиторий, копирует файл в .env и вписывает свои значения. Сами ключи появятся в главе 1.5 · Видео Карпатого + ключи API.

В проектах курса (бот-аналитик на неделе 3, MCP-сервер на неделе 9) мы работаем с базой учебного интернет-магазина «Полка»: заказы, товары, оплаты, клиенты с именами и телефонами. Это SQLite — база в одном файле .db. Базу мы сгенерируем в главе 3.1 и положим на уровень выше репозитория, в ../shop-live/; она играет роль боевой (production — та, с которой работает «настоящий» магазин). Данные выдуманы, но обращаемся с ними как с настоящими — эта привычка понадобится на работе. Правила с первого дня:

  • в учебный репозиторий никогда не попадает ни «боевая» база из ../shop-live/, ни её снимки, ни выгрузки заказов и клиентов;
  • для работы берётся снимок базы через VACUUM INTO — команду SQLite, которая записывает целостную копию базы в новый файл. Обычный cp не подходит: база работает в режиме WAL (свежие изменения сначала пишутся в соседний файл -wal и лишь потом переносятся в основной), и копия одного файла может оказаться несогласованной. Лежит снимок вне репозитория или в игнорируемой папке (data/);
  • для публичного портфолио — только выдуманные демо-данные без личных данных, сгенерированные скриптом (в git — скрипт, а не база).

Если есть свои данные и право их использовать, можно работать с ними вместо базы «Полки» — правила те же. Правило *.db в .gitignore — страховка, а не замена этой дисциплине.

Когда понятно, что в репозиторий не попадает, остаётся договориться, как в нём фиксировать работу.

  • Один коммит — одна логическая единица: «week-01: pricing and csv stats exercises», а не «update».
  • Коммитим каждый день в конце сессии, в пятницу — обзор недели в notes/week-NN.md.
  • Перед каждым коммитом: git status и git diff --staged — глазами, что именно уходит. Это 10 секунд, которые ловят случайный .env или дамп базы.

Создать можно в веб-интерфейсе GitHub (New repository → Private) или через GitHub CLI: gh repo create ai-engineering-journey --private --source=. --remote=origin. Ветки в учебном репозитории не обязательны — работаем в main; ветка пригодится для экспериментов, которые могут не взлететь.

Учимся мы на DeepSeek (облако, дёшево, OpenAI-совместимый API, серверы в Китае) и Ollama (локальные модели); Claude и OpenAI — для сравнения. Для репозитория это меняет три мелочи:

  • .env.example перечисляет DEEPSEEK_API_KEY и имена моделей как основные, ключи Anthropic/OpenAI — как необязательные (шаблон выше). Имена моделей держим в окружении, не в коде: у DeepSeek они меняются.
  • Веса моделей в репозиторий не попадают. Веса — файлы с параметрами модели, гигабайты чисел. Ollama хранит модели в ~/.ollama, вне репозитория, — так и оставляем. Но на неделе 11 (LoRA) появятся свои файлы весов и адаптеров: *.gguf, *.safetensors и папку models/ стоит добавить в игнор заранее — это гигабайты.
  • Граница данных проходит и через заметки. Ответы DeepSeek на учебные вопросы можно сохранять в notes/. А всё, что касается личных данных клиентов магазина (и, на работе, рабочего кода и всего, что нельзя показывать посторонним), идёт только через локальную Ollama — ни в одну облачную модель. Результаты таких прогонов (даже «просто вывод модели») — в игнорируемую папку, потому что модель пересказывает имена, телефоны и суммы.
Claude / OpenAI DeepSeek Ollama
Секрет в .env ANTHROPIC_API_KEY, OPENAI_API_KEY DEEPSEEK_API_KEY нет (адрес localhost — не секрет)
Что может попасть в git по ошибке ключ ключ веса модели, вывод по реальным данным
Что игнорировать .env .env *.gguf, *.safetensors, models/, outputs/ с реальными данными
  1. Если git уверенно знаком — пропускаем. Иначе открываем Pro Git на русском → глава 2 «Основы Git» (разделы 2.1–2.5: запись изменений, игнорирование файлов, просмотр истории, удалённые репозитории) (≈40 мин). Особенно важны раздел про .gitignore и git rm --cached.
  2. Открываем шаблон Python.gitignore → пробегаем глазами (≈5 мин): там уже есть .venv, __pycache__, .env, а баз данных нет.
  3. (По желанию) Pro Git, глава 3 «Ветвление в Git», разделы 3.1–3.2 (≈20 мин) — пригодится для экспериментов на неделе 8.
  4. Ollama на macOS → раздел о расположении файлов (≈5 мин; для Linux и Windows в документации есть такие же разделы). Берём оттуда: модели и логи лежат в ~/.ollama, вне репозитория; модели весят от гигабайта до сотен гигабайт. Саму установку делаем в главе 1.5 (гл. 1.5).
  • Сначала .gitignore, потом git add. Если сделать git add . до создания .gitignore, в индекс попадут .venv и всё остальное.
  • git add . — опасная привычка в репозитории с данными. Добавлять стоит осознанно или хотя бы смотреть git status перед коммитом.
  • Уже закоммиченный секрет не исправляется новым коммитом. История хранит всё; даже переписанная история могла уйти в форк или кеш. Единственная надёжная мера — отозвать ключ.
  • Большие файлы. GitHub отклоняет файлы больше 100 МБ, и даже меньшие раздувают историю навсегда. Датасеты и индексы — в игнор, в README — команда, как их пересоздать.
  • Вывод ноутбуков может содержать данные и даже значения переменных окружения, если их напечатали. Об этом в гл. 1.4.
  • Личные данные в заметках. Если репозиторий станет публичным, в notes/ не должно быть реальных сумм, имён клиентов, телефонов, названий аккаунтов.
  • Инициализируем git в папке из главы 1.1 (если её ещё нет, команда её создаст):
    Окно терминала
    mkdir -p ~/ai-engineering-journey && cd $_ # $_ = last argument of the previous command
    git init -b main
    mkdir -p notes exercises/week-01 projects
  • Создаём .gitignore: берём содержимое Python.gitignore и дописываем снизу свой блок:
    # --- secrets ---
    .env
    .env.*
    !.env.example
    # --- databases (never commit shop data or its copies) ---
    *.db
    *.sqlite
    *.sqlite3
    *.db-wal
    *.db-shm
    # --- model weights and outputs on real data ---
    *.gguf
    *.safetensors
    models/
    outputs/
    # --- notebooks, OS ---
    .ipynb_checkpoints/
    .DS_Store
  • Создаём .env.example по шаблону из теории: основные DEEPSEEK_API_KEY=, DEEPSEEK_MODEL=, OLLAMA_BASE_URL=http://localhost:11434/v1, OLLAMA_MODEL=; необязательные (для сравнения) ANTHROPIC_API_KEY= и OPENAI_API_KEY=.
  • Переносим план: в README.md в корне репозитория выписываем недели курса (по оглавлению книги) и добавляем в начало раздел «Прогресс» — список недель с галочками.
  • Проверяем, что шпаргалка notes/python-cheatsheet.md и упражнения из гл. 1.1 лежат в notes/ и exercises/week-01/; если они создавались в другом месте — переносим.
  • Проверка игнора — создаём ловушки и убеждаемся, что git их не видит:
    Окно терминала
    echo "DEEPSEEK_API_KEY=fake" > .env
    touch test.db model.gguf
    git status # .env, test.db, model.gguf must NOT be listed
    git check-ignore -v .env test.db model.gguf
    rm test.db model.gguf
  • Первый коммит: git add .gitignore .env.example README.md notes exercises, затем git status, git diff --staged --stat глазами, git commit -m "init: structure, gitignore, plan".
  • Создаём приватный репозиторий на GitHub, привязываем origin, отправляем main. Проверяем в веб-интерфейсе, что .env там нет.
  • Заводим notes/week-01.md с шаблоном дня: «Что сделано / Что понято / Что не получилось / Вопросы».
  • Приватный репозиторий ai-engineering-journey есть на GitHub, первый коммит отправлен.
  • Структура notes/, exercises/week-01/, projects/ на месте, README.md с планом в корне.
  • git status при наличии .env и *.db в папке их не показывает.

Вопрос 1

.env с ключом закоммитили, потом добавили .env в .gitignore и закоммитили снова. Ключ в безопасности?

Нет. Ключ остался в истории первого коммита (и, если был push, на GitHub). Нужно немедленно отозвать ключ в консоли провайдера и выпустить новый.

Вопрос 2

Какая команда перестаёт отслеживать файл, не удаляя его с диска?

  • a) git rm .env
  • b) git rm --cached .env
  • c) git reset .env
  • d) git clean .env

b. --cached убирает файл из индекса; git rm без флага удалит и с диска.

Вопрос 3

Нужно ли коммитить uv.lock? Почему?

Да. Это lock-файл, фиксирующий точные версии всех зависимостей (как package-lock.json или go.sum); без него окружение на нашей машине и на CI может отличаться.

Вопрос 4

Зачем !.env.example после .env.*?

Правило .env.* поймало бы и .env.example; ! делает исключение, чтобы шаблон с именами переменных попадал в git.

Вопрос 5

Почему в .gitignore стоит *.db-wal и *.db-shm?

SQLite в режиме WAL держит рядом с базой файлы журнала и общей памяти; в них могут быть свежие данные, и в git им не место, как и самой базе.

Вопрос 6

Что не так в последовательности команд?

Окно терминала
git init
git add .
git commit -m "init"
echo ".venv/" > .gitignore

.gitignore создан после git add .: в первый коммит уже попали .venv и всё остальное, включая возможные секреты. Сначала .gitignore, потом add.

Вопрос 7

Как узнать, каким именно правилом игнорируется файл?

git check-ignore -v <путь> — покажет файл и строку правила.

Вопрос 8

Почему снимок базы магазина нужно делать через VACUUM INTO, а не cp?

База в режиме WAL: часть данных может лежать в -wal, и простое копирование файла во время работы даёт несогласованную копию. VACUUM INTO делает целостный снимок средствами самого SQLite.

Вопрос 9

Какие данные можно класть в публичную версию проекта-аналитика магазина?

  • a) копию «боевой» базы из ../shop-live/
  • b) снимок data/shop.db
  • c) сгенерированные вымышленные заказы
  • d) выгрузку заказов без имён и телефонов

c. Только вымышленные данные. Даже снимок и «обезличенная» выгрузка раскрывают реальную активность (суммы, состав заказов, города) и не нужны для демонстрации.

Вопрос 10

Что делать с датасетом на 300 МБ, который нужен проекту?

Не коммитить: положить в игнорируемую папку data/ и описать в README, откуда его скачать или каким скриптом сгенерировать.

Вопрос 11

Какие из этих строк в .env.example нормальны, а какие нет: DEEPSEEK_API_KEY=, OLLAMA_BASE_URL=http://localhost:11434/v1, DEEPSEEK_API_KEY=sk-...? Почему у Ollama нет ключа?

Пустой DEEPSEEK_API_KEY= и адрес локальной Ollama — нормально: это имена и несекретное значение по умолчанию. Строка с настоящим ключом — утечка, ключ надо отозвать. У локальной Ollama нет аутентификации: она слушает только localhost, SDK openai требует какую-то строку в api_key, но Ollama её игнорирует.

Вопрос 12

Локальную модель Ollama прогнали по снимку базы магазина и сохранили её ответы в notes/orders-summary.md. Данные никуда не отправлялись. Можно это коммитить?

Нет. Локальная модель защищает данные от отправки наружу, но её ответ пересказывает заказы и личные данные клиентов; в git (а потом, возможно, в публичный репозиторий) он попадать не должен. Такие выводы — в игнорируемую папку (outputs/).

Глава 1.3 · Python: uv, async, Pydantic: в exercises/ появится первый настоящий Python-проект с окружением, lock-файлом, тестами и линтером. Потом глава 1.4 · Jupyter-ноутбуки в VS Code подключит к нему Jupyter, а в главе 1.5 (гл. 1.5) мы заполним .env ключом DeepSeek и поставим Ollama.