Приложение может успешно собраться внутри компании и всё равно упасть без интернета на строке, где нет ни одного сетевого вызова. Причина в том, что для многих ML-библиотек установка кода и доставка рабочих артефактов остаются двумя разными процессами. Значит, зелёный пайплайн подтверждает только наличие Python-пакетов, но не готовность системы работать автономно.
Сеть спряталась за удобным API
На Habr описан воспроизведённый сценарий: AI-приложение локально читает файлы, считает эмбеддинги и пишет их в локальную базу, но во внутреннем контуре падает при инициализации ML-клиента. Под капотом библиотека пытается скачать недостающий файл из внешней сети.
Так работает lazy loading. Через pip, conda или uv устанавливается код, а модели, веса, словари и конфигурации приезжают при первом использовании и складываются в кэш. Для разработчика с интернетом схема почти незаметна: первый вызов занимает чуть больше времени, последующие используют локальную копию. В закрытой сети тот же первый вызов превращается в аварию.
У Hugging Face обращения вроде AutoModel.from_pretrained(...) и pipeline(...) при отсутствии файлов в кэше почти всегда ведут на Hugging Face Hub. sentence-transformers наследует эту механику через transformers, поэтому создание SentenceTransformer("all-MiniLM-L6-v2") фактически требует заранее доставленной модели.
С FastEmbed ловушка менее очевидна. Библиотека выглядит как локальное решение на ONNX Runtime, но при первом использовании загружает ONNX-модель. В зависимости от версии источник может находиться на Hugging Face Hub или в бакете Qdrant в Google Cloud Storage. Поэтому зеркало одного только Hugging Face не гарантирует, что стек перестал смотреть наружу.
Ещё коварнее tiktoken. Файлы кодировок вроде cl100k_base и o200k_base не входят в пакет и скачиваются с openaipublic.blob.core.windows.net. Прямого вызова tiktoken в приложении при этом может не быть: LangChain способен привести к нему через интеграцию с OpenAI. Аналогично FastEmbedEmbeddings приводит к FastEmbed и ONNX-модели, а HuggingFaceEmbeddings к sentence-transformers и Hugging Face Hub.
Вот почему ревью собственного кода здесь почти бесполезно. Сетевой запрос может находиться через несколько слоёв абстракций, а внешне безобидное создание текстового сплиттера заканчивается загрузкой таблицы кодировок.
Кэш не равен поставке
Ещё одна ложная гарантия появляется после удачного прогрева. Большинство библиотек на Linux и macOS используют каталоги внутри ~/.cache, но tiktoken по умолчанию хранит данные в $TMPDIR/data-gym-cache, причём имя файла представляет собой SHA-1 от URL. Python-версия FastEmbed тоже по умолчанию может использовать временный каталог.
Такой кэш способен пережить перезапуск процесса, но исчезнуть после перезапуска пода или очистки /tmp. Вчера приложение работало автономно, сегодня снова пытается выйти в интернет. Это не каприз инфраструктуры, а следствие того, что временный кэш приняли за часть дистрибутива.
Для экосистемы Hugging Face расположение кэша можно задавать через HF_HOME и HF_HUB_CACHE. Переменные TRANSFORMERS_CACHE и HUGGINGFACE_HUB_CACHE, как отмечает автор на Habr, признаны устаревшими в пользу новых. Но сама настройка каталога решает только вопрос хранения. Артефакты всё равно сначала нужно получить, проверить и доставить.
Как проверять стек до переноса
Практическая процедура начинается не со SBOM, а с холодного запуска. Поднимите чистую среду без пользовательских кэшей, установите зафиксированные версии пакетов и запишите сетевые обращения во время установки, импорта, инициализации и первого реального вызова. Проверять только pip install недостаточно: скрытая загрузка часто начинается позже.
Затем повторите тот же сценарий с запрещённым egress. Прогон должен включать создание токенизаторов, загрузку моделей, расчёт эмбеддингов и все обёртки фреймворка, которыми пользуется приложение. После этого перезапустите контейнер или под с очищенным временным каталогом. Если система снова ищет сеть, автономной сборки пока нет.
Обнаруженные wheels, модели, ONNX-файлы, токенизаторы, таблицы кодировок и конфигурации нужно хранить в контролируемых зеркалах или включать в поставку. Версии библиотек и артефактов стоит фиксировать вместе: пример FastEmbed показывает, что между версиями может меняться даже внешний источник загрузки.
Главный маркер на следующем деплое простой: полный сценарий приложения должен проходить с пустыми кэшами и технически заблокированным исходящим трафиком. Если для успеха нужен заранее прогретый ноутбук разработчика или однажды удачно переживший запуск под, закрытого контура у вас ещё нет.