This directory is used to package the entire backend service into a standalone executable, suitable for local deployment, offline use, desktop integration, etc.
deploy/
└── pyinstaller/
├── argo.spec # Main PyInstaller entry file
└── hooks/ # Custom hooks (to fix dynamic import issues)
└── hooks.py
Recommended installation via Poetry:
poetry add --dev pyinstallerOr using system pip:
pip install pyinstallerBefore packaging, make sure the following are met to ensure the program runs correctly before packaging:
-
✅ Backend entry is
backend/main.pyand can start the service successfully -
✅ Backend dependencies are installed:
make install
-
✅ If the project includes frontend code, ensure the frontend has been built (e.g., Vue, React app):
make build-web
-
✅ The unbundled program runs correctly locally (verify it responds to requests):
make run
💡 If
make runfails or the frontend build doesn't succeed, fix issues before packaging to avoid producing a broken executable.
It's recommended to use make build-exe for one-click packaging:
# From project root
make build-exeEquivalent to manually executing:
cd backend && poetry run pyinstaller ../deploy/pyinstaller/argo_build.spec \
--distpath ../build/output \
--workpath ../buildUse:
make cleanup cleanThis will delete:
build/__pycache__/
You can use get_data_files() in utils.py to include resources in the executable:
def get_data_files():
return [
("resources", "backend/build/pyinstaller/resources"),
("configs", "backend/configs"),
("templates", "backend/templates"),
]These resources will be extracted by PyInstaller at runtime and accessed like this:
from sys import _MEIPASS
import os
resource_path = os.path.join(getattr(sys, "_MEIPASS", "."), "resources", "node", "bin", "node")The deploy/pyinstaller/hooks/ directory contains runtime and import hook scripts needed for PyInstaller to ensure all dependencies load correctly after packaging.
Since PyInstaller can't read .env files after packaging, it's recommended to inject necessary env variables in a runtime hook.
File path: deploy/pyinstaller/hooks/runtime_env_hook.py
import os
import sys
# Enable or disable features
os.environ["ENABLE_MULTI_USER"] = "false"
os.environ["USE_ARGO_OLLAMA"] = "true"
os.environ["USE_ARGO_TRACKING"] = "true"
os.environ["USE_REMOTE_MODELS"] = "true"
# Bind resources to _MEIPASS
os.environ["HUGGINGFACE_HUB_CACHE"] = os.path.join(sys._MEIPASS, "resources", "huggingface", "hub")
os.environ["TIKTOKEN_CACHE_DIR"] = os.path.join(sys._MEIPASS, "resources", "tiktoken_cache")
os.environ["LLAMA_CPP_LIB_PATH"] = os.path.join(sys._MEIPASS, "llama_cpp", "lib")
# Prevent proxy interference for localhost
os.environ["NO_PROXY"] = "http://127.0.0.1,localhost"In Analysis(...) config, add:
runtime_hooks=[
os.path.join(spec_dir, 'hooks', 'runtime_env_hook.py'),
]Go to the build directory:
cd build/output/argo-darwin_arm64
./argo # Linux/macOS
# On Windows
argo.exe- Use
--cleanto avoid cache issues - Use
--log-level=DEBUGfor detailed logs - Check
_MEIPASSpath to ensure resources are copied correctly - If
importfails, consider adding a hook script for that module
| Problem Description | Possible Cause | Solution |
|---|---|---|
❌ ModuleNotFoundError after startup |
PyInstaller missed dynamically imported modules | Add a hook file using collect_submodules to specify hiddenimports |
| ❌ Missing resources (like provider.yaml, frontend dist) | datas not set or files not copied |
Ensure .spec uses datas = get_data_files() and resources are included |
| ❌ Node.js not executable | Permissions not set | Use os.chmod(path, 0o775) in utils.prepare_node() |
❌ .env not working |
.env path changes after packaging |
Load from ARGO_STORAGE_PATH at runtime or load manually in startup |
| ❌ llama.cpp library not found | Missing LLAMA_CPP_LIB_PATH env var |
Set it in hook script and ensure path exists |
| ❌ Slow or failed build | Unclean cache | Run make cleanup then retry |
| ❌ HuggingFace models not loading | Cache not mapped | Set HUGGINGFACE_HUB_CACHE to _MEIPASS/resources/huggingface/hub |