1.4 · Jupyter-ноутбуки в VS Code
Неделя 1 · Подготовка · ⏱ ~1,5 ч · Практика
Зачем это AI-инженеру
Заголовок раздела «Зачем это AI-инженеру»Почти все курсы, cookbook-и провайдеров (Anthropic, OpenAI), примеры LlamaIndex и LangGraph и статьи про evals публикуются как ноутбуки. Ноутбук — лучший инструмент, чтобы попробовать промпт, посмотреть на эмбеддинги, разобрать ошибки evals в таблице. Но продакшен-код в ноутбуке не живёт, и умение вовремя перенести код в модуль — тоже навык, который видно в портфолио.
В этой главе подключаем ноутбук к проекту learn-python из главы 1.3: разбираем, как ноутбук устроен и где он подводит, запускаем его на окружении uv, а потом решаем, что из ноутбука переносить в модули и как не утащить в git лишнего.
1. Что такое ноутбук
Заголовок раздела «1. Что такое ноутбук»Ноутбук (.ipynb) — JSON-файл со списком ячеек: кодовых и Markdown. Код выполняется ядром (kernel) — отдельным процессом Python, который живёт, пока его не перезапустят. Все ячейки делят одно пространство имён этого процесса.
VS Code (UI) Kernel (python from .venv)┌───────────────────────┐ run ┌───────────────────────────┐│ [1] import httpx │ ───────▶ │ globals: httpx, df, orders││ [2] orders = load() │ ◀─────── │ state survives between ││ [3] orders[:5] │ output │ cells until restart │└───────────────────────┘ └───────────────────────────┘Ближайшая аналогия — интерактивная консоль (REPL) Python или любого другого языка, у которой ввод разбит на сохраняемые блоки, а вывод (таблицы, графики, HTML) сохраняется рядом с кодом.
2. Главное свойство и главная ловушка — состояние
Заголовок раздела «2. Главное свойство и главная ловушка — состояние»Ячейки можно выполнять в любом порядке и сколько угодно раз. Номер [5] слева — порядок выполнения, а не позиция. Отсюда классические баги:
- удалили ячейку, где определялась переменная, — а она всё ещё в памяти ядра, и ноутбук «работает»; после перезапуска сломается;
- поменяли функцию выше, но не перезапустили ячейку — внизу вызывается старая версия;
- ячейка
x = x + 1при повторном запуске даёт новый результат.
Правило: прежде чем считать ноутбук готовым или коммитить, делаем Restart Kernel → Run All. Если сверху вниз проходит — ноутбук честный.
Чтобы ядро вообще запустилось и видело пакеты проекта, его нужно правильно выбрать — это следующий шаг.
3. Ядро из окружения uv
Заголовок раздела «3. Ядро из окружения uv»Ядро — это конкретный интерпретатор Python. Чтобы ноутбук видел пакеты проекта, он должен работать на .venv проекта, а в этом окружении должен стоять пакет ipykernel (через него VS Code говорит с интерпретатором):
cd exercises/learn-pythonuv add --dev ipykernelДальше в VS Code: открываем .ipynb → кнопка Select Kernel справа сверху → Python Environments → .venv проекта. Если .venv не видно — сначала «Python: Select Interpreter» для рабочей папки или открываем VS Code именно в папке проекта.
Ставить пакеты через %pip install в ячейке не нужно: окружения uv по умолчанию создаются без pip, а установка мимо uv add не попадёт в pyproject.toml и lock. Новая зависимость — uv add x в терминале, затем Restart Kernel.
4. Что умеет VS Code с ноутбуками
Заголовок раздела «4. Что умеет VS Code с ноутбуками»Ядро подключено; дальше — приёмы работы, которые понадобятся в практике:
- Выполнить ячейку: Shift+Enter (и перейти к следующей), Ctrl+Enter (остаться).
- Переключение типа ячейки: в командном режиме (Esc)
M— Markdown,Y— код;A/B— вставить выше/ниже;DD— удалить. - Jupyter Variables (кнопка Variables на панели ноутбука) — список переменных ядра с типами и размерами; DataFrame (таблица библиотеки pandas) открывается в Data Viewer.
- Отладка ячейки: Debug Cell в меню рядом с кнопкой запуска, breakpoints работают как в обычном файле.
- Restart / Interrupt kernel — кнопки на панели. Interrupt — если ячейка зависла на сетевом запросе.
- Interactive Window: в обычном
.pyможно размечать ячейки комментарием# %%и запускать их по одной. Код остаётся нормальным модулем, который диффится в git, — хороший компромисс между ноутбуком и скриптом.
5. Async в ноутбуке
Заголовок раздела «5. Async в ноутбуке»Ядро Jupyter — это IPython, расширенная интерактивная оболочка Python. Оно поддерживает await на верхнем уровне ячейки, потому что ядро уже крутит свой event loop:
# cell — works in Jupyter, would be a SyntaxError in a plain .pyimport httpxasync with httpx.AsyncClient() as client: r = await client.get("https://httpbin.org/get")r.status_codeСледствие из гл. 1.3: asyncio.run(main()) в ячейке падает с RuntimeError (цикл уже запущен) — пишем await main().
6. Импорт своего кода и autoreload
Заголовок раздела «6. Импорт своего кода и autoreload»Ноутбук удобен для экспериментов, но функции, написанные прямо в ячейках, нельзя протестировать и переиспользовать. Поэтому логику держим в .py-модулях, а в ноутбуке только вызываем и смотрим результаты. Чтобы правки модуля подхватывались без перезапуска ядра:
%load_ext autoreload%autoreload 2from models import OrderItem # module in the same project%-команды — «магии» IPython, в обычном Python их нет. Полезные: %time / %timeit (замер), %env (лучше не использовать для ключей — см. ниже).
7. Ноутбук или скрипт
Заголовок раздела «7. Ноутбук или скрипт»Разделение «логика в модуле, эксперимент в ноутбуке» подводит к общему правилу выбора:
| Ноутбук | Скрипт / модуль |
|---|---|
| исследование, «а что если» | код, который запускается регулярно |
| промпт-эксперименты, просмотр ответов модели | бот, сервер, MCP-сервер |
| графики, таблицы результатов evals | тесты, CI, cron |
| объяснение для читателя (туториал, отчёт) | всё, что импортируют другие файлы |
| один раз, руками | нужно ревью в git-диффе |
Типичный путь в AI-проекте: эксперимент в ноутбуке → функция переезжает в модуль с тестом → ноутбук её только импортирует. Для evals (недели 6–8) ноутбук — удобная «витрина» результатов, а сам прогон — скрипт.
8. Ноутбуки и git
Заголовок раздела «8. Ноутбуки и git»Остался вопрос, который возникает при первом же коммите ноутбука. .ipynb — JSON, в который сохраняются выводы ячеек и счётчики выполнения. Последствия:
- диффы шумные: перезапуск меняет все
execution_count; - в выводе может оказаться что угодно: распечатанные заказы с данными клиентов, ответ API с личными данными, ключ, если в ячейке был
print(os.environ). Это попадёт в git; - картинки в выводе раздувают репозиторий.
Решения: перед коммитом — Clear All Outputs (если вывод не нужен для читателя), либо инструмент nbstripout, который чистит вывод при коммите автоматически (перед установкой его стоит проверить, как любой сторонний пакет). Секреты в ноутбуке читаются только из окружения (os.environ["DEEPSEEK_API_KEY"] после загрузки .env через load_dotenv() — глава 1.5), никогда не печатаются и не вписываются в ячейку. Папка .ipynb_checkpoints/ уже в .gitignore из гл. 1.2.
DeepSeek и Ollama: как это сделать у нас
Заголовок раздела «DeepSeek и Ollama: как это сделать у нас»В этой книге модели в ноутбуке — DeepSeek (облако, дёшево, OpenAI-совместимый API, серверы в Китае) и локальная Ollama; Claude и OpenAI — для сравнения. Сам Jupyter от провайдера не зависит, меняется немногое (ключ и установка — в главе 1.5 · Видео Карпатого + ключи API; проверено по документации 13.11.2026):
- Клиенты в ноутбуке — асинхронные, раз top-level
awaitработает:AsyncOpenAI(base_url="https://api.deepseek.com", api_key=os.environ["DEEPSEEK_API_KEY"])для DeepSeek иAsyncOpenAI(base_url="http://localhost:11434/v1", api_key="ollama")для Ollama (ключ Ollama игнорирует, но SDK требует строку). Создаём их в одной ячейке вверху и переиспользуем. - Вывод ноутбука — это тоже данные. Ответ локальной модели о заказах и клиентах никуда не ушёл по сети, но сохранится в
.ipynbи попадёт в git, если не почистить вывод. Ноутбуки с данными клиентов — вне репозитория или в игнорируемой папке. - Выбор модели в ноутбуке = выбор, куда уйдут данные. В одной сессии легко по ошибке передать DataFrame с заказами и данными клиентов в клиента DeepSeek вместо локального. Такие данные держим в отдельном ноутбуке, где создан только клиент Ollama.
- Повторный запуск ячейки у DeepSeek — копейки, у Ollama — бесплатно, но минуты ожидания на большой модели. Совет «запрос и анализ — в разных ячейках» остаётся в силе.
- Thinking у DeepSeek включён по умолчанию: ответ содержит рассуждение в поле
reasoning_contentрядом сcontent. В ноутбуке на него удобно смотреть, но печатать его целиком в коммитимый вывод не стоит — он длинный.
| Claude | OpenAI | DeepSeek | Ollama | |
|---|---|---|---|---|
| Async-клиент в ячейке | AsyncAnthropic() |
AsyncOpenAI() |
AsyncOpenAI(base_url=...) |
AsyncOpenAI(base_url=...) или ollama.AsyncClient() |
Что в .env |
ключ | ключ | ключ | только адрес и имя модели |
| Повторный прогон ячейки | платно | платно | очень дёшево | бесплатно, но долго |
| Данные клиентов в ячейке | никогда | никогда | никогда | да, но вывод не коммитить |
Для практики этой главы модели не нужны; если Ollama уже стоит — бонус ниже на ней.
Маршрут изучения
Заголовок раздела «Маршрут изучения»- Открываем Jupyter Notebooks in VS Code → разделы про создание ноутбука, выбор ядра, запуск ячеек, Variables Explorer и Data Viewer, отладку (≈30 мин). Особое внимание — выбору ядра и кнопкам Restart/Interrupt.
- Открываем гайд uv Using uv with Jupyter → раздел про VS Code и про установку
ipykernelв проект (≈10 мин). Берём оттуда, почему%pipв uv-окружении ведёт себя иначе. - (По желанию) Python Interactive window → про ячейки
# %%в.py(≈10 мин). - Если нужен расширенный контекст про экосистему — jupyter.org (≈5 мин), только чтобы понимать разницу Notebook / JupyterLab / ядро.
- (≈10 мин, к бонусу) Ollama: OpenAI compatibility — какой
base_urlи что ключ не проверяется; DeepSeek Quick Start —base_urlи пример наopenai. Вывод: в ноутбуке меняется только адрес и ключ клиента.
На что обратить внимание
Заголовок раздела «На что обратить внимание»- Скрытое состояние. Ноутбук, который работает только при определённом порядке ячеек, — не результат. Restart + Run All перед коммитом.
- Не то ядро.
ModuleNotFoundError: No module named 'pydantic'в ноутбуке, хотяuv runработает, почти всегда значит, что выбрано системное ядро, а не.venv. %pip installв ячейке ставит пакет мимо lock-файла (или не работает вовсе в uv-окружении). Толькоuv add+ Restart Kernel.- Секреты и данные в выводе. Ключи и переменные окружения не печатаем; выводы с данными клиентов и заказов не коммитим. Для экспериментов с базой магазина — только копия через
VACUUM INTOвне репозитория, только чтение. Данные в учебной базе выдуманы, но обращаемся с ними как с настоящими — эта привычка понадобится на работе. - Долгие ячейки с сетевыми вызовами. Повторный запуск ячейки со 100 запросами к LLM — это повторная оплата. Результаты кешируем в переменную или файл и разделяем «запрос» и «анализ» на разные ячейки.
- Ноутбук как продакшен. Если код из ноутбука нужно запускать по расписанию или из бота — его место в модуле с тестами.
Практика
Заголовок раздела «Практика»- Устанавливаем расширение Jupyter в VS Code (обычно ставится вместе с Python; проверяем, что оно от Microsoft).
- В
exercises/learn-python/выполняемuv add --dev ipykernel. - Создаём
exercises/learn-python/explore.ipynb, выбираем ядро.venv. Первая ячейка:import syssys.executable # must point into .../learn-python/.venv/ - Повторяем упражнение из гл. 1.3 интерактивно:
- ячейка с
%load_ext autoreload/%autoreload 2и импортомOrderItemизmodels.py; - ячейка, которая читает
order_items.jsonlи складывает валидные позиции заказов в список, невалидные — в список ошибок; - Markdown-ячейка с выводами: какие ошибки нашлись;
- ячейка с
OrderItem.model_json_schema().
- ячейка с
- Меняем валидатор в
models.py(например, своё правило для количества) и, не перезапуская ядро, перезапускаем ячейку валидации — изменение должно подхватиться благодаря autoreload. - Async-ячейка:
from fetch_compare import parallelиawait parallel()прямо в ячейке (еслиasyncio.run(...)в файле стоит подif __name__ == "__main__":, импорт его не запустит — гл. 1.1); затем убеждаемся, чтоasyncio.run(parallel())здесь падает, и читаем ошибку. - Открываем Jupyter Variables и находим список позиций; ставим breakpoint и запускаем Debug Cell.
- Устраиваем ловушку состояния: определяем
fee = 0.025, используем её ниже, удаляем ячейку с определением. Ноутбук продолжит работать. Restart Kernel → Run All — видимNameError. Исправляем. - Создаём
scratch.pyс двумя ячейками# %%и запускаем их в Interactive Window. - (Бонус, после главы 1.5, когда Ollama установлена) Ячейка с
AsyncOpenAI(base_url="http://localhost:11434/v1", api_key="ollama")иawaitодного вызова к локальной модели: «Объясни в одном предложении, что такое средний чек». Ниже — отдельная ячейка, которая только печатает сохранённый ответ иusage(сколько токенов ушло на запрос и ответ; токены — глава 1.5). Перезапускаем вторую ячейку несколько раз — запрос не повторяется. - (Для сравнения, по желанию) Тот же вопрос в DeepSeek в соседней ячейке; смотрим
reasoning_contentи сравниваемcompletion_tokensс локальной моделью. Только учебный вопрос, никаких данных магазина. - Перед коммитом: Restart + Run All, Clear All Outputs (или оставляем вывод, если в нём только вымышленные данные),
git diff --statглазами.
Готово, когда
Заголовок раздела «Готово, когда»- Ноутбук открывается в VS Code и работает на ядре из
.venvпроекта (sys.executableэто подтверждает). - Упражнение из гл. 1.3 повторено в ноутбуке и проходит Restart + Run All.
- Можем объяснить, когда писать ноутбук, а когда модуль, и почему
asyncio.runв ячейке не работает.
Тест для самопроверки
Заголовок раздела «Тест для самопроверки»Вопрос 1
Что такое ядро ноутбука и сколько его состояние живёт?
Отдельный процесс Python, который выполняет ячейки; его переменные живут, пока ядро не перезапущено или не закрыто, независимо от того, какие ячейки есть в файле.
Вопрос 2
Номер [7] слева от ячейки означает:
b. Порядок выполнения; ячейки могли запускаться не сверху вниз.
Вопрос 3
В ноутбуке import pydantic даёт ModuleNotFoundError, а uv run python -c "import pydantic" работает. Что проверить первым?
Выбранное ядро: скорее всего, это системный Python, а не .venv проекта. Select Kernel → .venv, проверить sys.executable.
Вопрос 4
Как правильно добавить новую библиотеку для ноутбука в uv-проекте?
uv add <пакет> в терминале проекта, затем Restart Kernel. Не %pip install: установка пройдёт мимо pyproject.toml и lock (или не сработает вовсе).
Вопрос 5
Что не так в этой ячейке?
import osprint(os.environ)resp = call_llm(os.environ["OPENAI_API_KEY"], prompt)print(os.environ) выводит все переменные окружения, включая ключи, — они сохранятся в выводе ноутбука и могут попасть в git. Ключи никогда не печатают; SDK обычно сам читает их из окружения.
Вопрос 6
Почему в ячейке можно писать await main(), а в .py-файле на верхнем уровне нельзя?
IPython-ядро уже держит запущенный event loop и разрешает top-level await. В обычном скрипте цикла нет, его надо запустить через asyncio.run.
Вопрос 7
Что делает %autoreload 2?
Перед каждым выполнением ячейки перезагружает изменённые модули, так что правки в .py подхватываются без перезапуска ядра.
Вопрос 8
Какие два признака говорят, что код пора переносить из ноутбука в модуль?
Например: его надо запускать регулярно или из другого кода (бот, cron); его импортируют другие файлы; ему нужны тесты; нужен нормальный ревью в git-диффе.
Вопрос 9
Зачем перед коммитом делать Restart Kernel → Run All?
Чтобы убедиться, что ноутбук не зависит от скрытого состояния и порядка запусков и воспроизводим сверху вниз.
Вопрос 10
Чем ячейки # %% в .py-файле лучше .ipynb для git?
Это обычный Python: чистые диффы, нет сохранённых выводов и счётчиков, его можно импортировать, линтовать и тестировать.
Вопрос 11
Ячейка отправляет 200 запросов к LLM и сразу строит по ним таблицу. Что здесь не так с точки зрения стоимости?
Запрос и анализ в одной ячейке: каждая правка таблицы перезапускает 200 платных запросов. Результаты нужно сохранить (в переменную или файл) в отдельной ячейке и анализировать уже их.
Вопрос 12
Копия базы магазина с данными клиентов разбирается в ноутбуке, в котором вверху созданы два клиента — deepseek и local. Чем это опасно и как устроить работу безопаснее?
Одна опечатка в имени клиента — и данные клиентов уходят в облако (в случае DeepSeek — на серверы в Китае). Для таких данных — отдельный ноутбук, где создан только клиент локальной Ollama.
Вопрос 13
Локальная модель Ollama ответила по заказам из копии базы с данными клиентов; данные никуда не отправлялись. Можно ли коммитить ноутбук с этим выводом?
Нет. Вывод сохраняется в .ipynb и пересказывает данные заказов и клиентов; в git он не должен попасть. Clear All Outputs или держать такой ноутбук вне репозитория / в игнорируемой папке.
Что дальше
Заголовок раздела «Что дальше»Глава 1.5 · Видео Карпатого + ключи API: интуиция о том, как устроены LLM, ключ DeepSeek и установка Ollama (ключи Anthropic и OpenAI — по желанию, для сравнения). Ноутбук из этой главы станет местом для первых вызовов модели в конце недели.