Lazy Imports & Fast Startup
Optimize import time and memory usage with lazy loading
Lazy Imports & Fast Startup
PraisonAI Agents v0.5.0+ uses lazy imports to dramatically reduce startup time and memory usage. Heavy dependencies like litellm, requests, and chromadb are only loaded when actually needed.
from praisonaiagents import Agent
agent = Agent(name="MyAgent") # litellm loads on first LLM call
agent.start("Hello")The user imports PraisonAI and starts an agent instantly; heavy libraries load only on first use.
How It Works
Quick Start
- Import without heavy deps
python from praisonaiagents import Agent agent = Agent(name="MyAgent") # litellm loads on first LLM call - Verify lazy loading
python import sys import praisonaiagents assert "litellm" not in sys.modules print("Heavy dependencies not loaded at import time")
Performance Benefits
| Metric | Before | After | Improvement |
|---|---|---|---|
| Import Time | 820ms | 18ms | 97.8% faster |
| Memory Usage | 93.3MB | 33.0MB | 64.6% reduction |
How It Works
Lazy Module Loading
Core modules are loaded on-demand using Python’s __getattr__ mechanism:
# These imports are fast - modules loaded lazily
from praisonaiagents import Agent, Session, Memory, Knowledge
# Agent is only fully loaded when you use it
agent = Agent(name="MyAgent") # litellm loaded hereHeavy Dependencies
The following dependencies are NOT loaded at import time:
- litellm – Only loaded when LLM calls are made
- requests – Only loaded when HTTP calls are needed
- chromadb – Only loaded when vector stores are used
- mem0 – Only loaded when memory features are used
Wrapper CLI Lazy Loading
The praisonai CLI and praisonai.run(...) used to eagerly import the framework adapter tree (praisonaiagents.frameworks.*) on every invocation, wasting the ~420 ms deferral that agent.py already had. As of PR #4821 the agents_generator module is fully lazy — framework adapters load only when a framework is actually selected.
- Runtime access goes through the lazy
_get_default_adapter_registry(); framework-adapter imports live underTYPE_CHECKING, so importingpraisonai.agents_generatornever loads the framework tree.
import sys
import praisonai.agents_generator # noqa: F401
assert not any(m.startswith("praisonaiagents.frameworks") for m in sys.modules), (
"agents_generator must not eagerly load the framework tree"
)As of PR #5892 the wrapper bootstrap (praisonai._bootstrap) also defers its pathlib import. _bootstrap runs on every import praisonai, but Path is only needed for the monorepo-dev sibling fallback, so the import now lives inside _ensure_sibling, past the find_spec early return. This saves roughly 26 ms on the installed path (pathlib plus urllib.parse, ipaddress, re, fnmatch) with no change in behaviour. The actual saving depends on whether another library you import already loads pathlib.
# pathlib should no longer appear under praisonai._bootstrap
python -X importtime -c "import praisonai" 2>&1 | grep -E "pathlib|_bootstrap"Training & Vision Module Lazy Loading
Modules affected: praisonai.train.llm.trainer (the TrainModel class) and praisonai.upload_vision (the UploadVisionModel class) now use lazy loading to defer heavy ML dependencies.
These modules use a _lazy_import_*_deps() helper called from __init__, mirroring train.py / train_vision.py patterns.
Dependencies deferred:
- torch – CUDA/GPU computation framework
- transformers (
TextStreamer,TrainingArguments) – Hugging Face transformers - unsloth (
FastLanguageModel,FastVisionModel,is_bfloat16_supported,standardize_sharegpt,get_chat_template) – Fast training optimization - trl (
SFTTrainer) – Transformer Reinforcement Learning - datasets (
load_dataset,concatenate_datasets) – Dataset loading utilities - psutil (
virtual_memory) – System memory monitoring
Impact: Importing praisonai.upload_vision or praisonai.train.llm.trainer is now near-instant; CUDA / ~2 GB of ML libs only load when you instantiate UploadVisionModel(...) or TrainModel(...).
# Fast — no torch/unsloth load
from praisonai.upload_vision import UploadVisionModel
# Heavy deps load here, not at import time
uploader = UploadVisionModel(config_path="config.yaml")ImportError messages now include install hints:
- Vision upload:
pip install torch unsloth - Training:
pip install torch transformers unsloth datasets trl psutil
Opt-in-only Agent-path imports
As of PR #6170 (fixes #6167), three opt-in-only modules that previously loaded on every from praisonaiagents import Agent are now deferred to their first use. The public API is unchanged — only the import graph is tidier.
| Module | Loaded only when | Opt-in surface |
|---|---|---|
praisonaiagents.agent.server_registry | You start the HTTP server | Agent.launch(...), PraisonAIAgents.launch(...) |
praisonaiagents.goal.judge | The goal gate runs for the first time | agent.run_goal(...), agent.run_goal_async(...), agent.run_until(..., goal=...) |
praisonaiagents.session.encrypted_store | You import or construct EncryptedSessionStore | from praisonaiagents.session import EncryptedSessionStore |
Verify none of them is in sys.modules after a bare Agent import:
import sys
from praisonaiagents import Agent # noqa: F401
for mod in (
"praisonaiagents.agent.server_registry",
"praisonaiagents.goal.judge",
"praisonaiagents.session.encrypted_store",
):
assert mod not in sys.modules, modEach still works exactly as before — the module loads the first time you reach into the feature:
from praisonaiagents import Agent
agent = Agent(instructions="You are a helpful assistant")
# Goal loop — judge loads on the first gate evaluation
agent.run_goal("Tidy the repo and open a PR", goal="PR exists", max_turns=10)
# Encrypted session — encrypted_store loads on this import
from praisonaiagents.session import EncryptedSessionStore
# HTTP launch — server_registry loads on this call, not at import (blocks while serving)
agent.launch(path="/ask", port=8000)Verifying Lazy Imports
You can verify lazy imports are working:
import sys
# Import the package
import praisonaiagents
# Check that heavy deps are NOT loaded
assert 'litellm' not in sys.modules
assert 'requests' not in sys.modules
assert 'chromadb' not in sys.modules
# Check training/vision modules are lazy loaded
from praisonai.upload_vision import UploadVisionModel # noqa
assert "torch" not in sys.modules
assert "unsloth" not in sys.modules
print("✓ All heavy dependencies are lazy loaded")Configuration
Lazy imports are enabled by default. You can check the configuration:
from praisonaiagents._config import LAZY_IMPORTS
print(f"Lazy imports enabled: {LAZY_IMPORTS}")Shared lazy cache for optional-dep loading
praisonai._lazy_cache is the single source of truth for thread-safe optional-dependency loading — lazy_get(key, factory) returns a memoised value (including memoised exceptions), and lazy_reset(key) clears it.
# praisonaiagents-side (illustrative of the pattern PraisonAI uses internally)
from praisonai._lazy_cache import lazy_get, lazy_reset
def _get_workflow_models():
def _create():
from pydantic import BaseModel
# ... class definitions ...
return {"WorkflowStructure": WorkflowStructure, ...}
return lazy_get("workflow_models", _create)As of PR #4861, all three lazy-model factories in praisonai/auto.py (team, workflow, job workflow) route through the shared lazy_get, so exception caching and lazy_reset behave identically across them — no more ad-hoc _models_cache with divergent semantics. A broken pydantic install now fails fast and identically for every factory, and lazy_reset clears all three caches at once.
Transient vs structural failures
LazyCache(retry_cooldown: float = 30.0) splits cached loader failures by type, so a one-off environment blip no longer poisons the cache for the whole process lifetime.
| Failure type | Examples | Cache lifetime |
|---|---|---|
| Structural | ImportError, ValueError, AttributeError, RuntimeError | Process lifetime — fails fast on every subsequent call |
| Transient | OSError, ConnectionError, TimeoutError | retry_cooldown seconds (default 30s), then the loader is retried |
| Control-flow | KeyboardInterrupt, SystemExit | Never cached — propagate immediately |
A transient failure is timestamped when cached. Inside the cool-down window get(key, loader) re-raises the cached error; once the cool-down expires the next get retries the loader, and a success clears the cached failure. reset(key) clears both the value cache and the failure timestamp; reset() with no key clears everything.
Use a scoped instance for multi-tenant callers instead of sharing the module-level singleton.
from praisonai._lazy_cache import LazyCache
# Scoped cache with a shorter cool-down for a tenant that expects
# transient errors to clear quickly.
cache = LazyCache(retry_cooldown=5.0)The cool-down mirrors the one PraisonAIDB._init_stores already uses for the same failure mode. It closes a real failure: a long-lived process (praisonai serve) that hit a transient OSError on first litellm load previously returned the cached error for the entire process lifetime — every subsequent request in every tenant. With the cool-down, the process recovers automatically once the underlying condition clears. See PR #4948 (fixes #4945).
Best Practices
Import at point of use
Defer optional modules until a feature is invoked — keeps CLI and server startup fast.
Install extras only when needed
Use pip install praisonaiagents[memory] rather than pulling every optional dependency upfront.
Measure import time in CI
Track cold import duration to catch accidental module-level heavy imports in PRs.
Prefer lite for embedded use
Use the lite package when you bring your own LLM client and need minimal footprint.
Use a scoped LazyCache in multi-tenant callers
Construct a scoped LazyCache instance per tenant so a reset in one tenant does not stomp another, and tune retry_cooldown to how quickly transient failures are expected to clear.
# Good - specific imports
from praisonaiagents import Agent, Task
# Avoid - loads all modules
from praisonaiagents import *Measuring Performance
Use the built-in benchmarks to measure import time:
import time
start = time.perf_counter()
import praisonaiagents
end = time.perf_counter()
print(f"Import time: {(end - start) * 1000:.1f}ms")