Framework Availability Detection
Thread-safe, cached framework availability checking for PraisonAI adapters
Check whether optional frameworks and dependencies are installed before you run agents or YAML workflows.
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="You are a helpful assistant.")
agent.start("Run my task once a framework is available.")from praisonai import PraisonAI
# PraisonAI picks the first available framework automatically
praison = PraisonAI(agent_file="agents.yaml")
praison.run()The user runs YAML without naming a framework; PraisonAI probes installed adapters and picks the first available.
How It Works
Quick Start
- Run YAML — framework auto-detected
python from praisonai import PraisonAI praison = PraisonAI(agent_file="agents.yaml") praison.run() # uses first available framework (crewai → praisonai → autogen) - Probe before selecting a framework
python from praisonai._framework_availability import is_available if is_available("crewai"): print("CrewAI is installed") frameworks = ["autogen", "autogen_v4", "crewai", "ag2", "langgraph", "openai_agents", "google_adk"] available = [fw for fw in frameworks if is_available(fw)] print(f"Available: {available}")
API Reference
| Function | Signature | Behaviour | |
|---|---|---|---|
is_available(name: str) -> bool | Raises ValueError on unknown name | Cached on first call (double-checked locking under threading.Lock) | |
| `invalidate(name: str \ | None = None) -> None` | Pass None to clear the whole cache | Useful in tests that monkey-patch importlib |
Known Probe Names
The following framework names are supported:
| Name | Detection Method | Notes |
|---|---|---|
crewai | importlib.util.find_spec("crewai") | Standard probe |
autogen | importlib.util.find_spec("autogen") | AutoGen v0.2 package |
autogen_v4 | find_spec("autogen_agentchat") + find_spec("autogen_ext") | AutoGen v0.4 packages |
ag2 | Distribution + namespace check | See AG2 Detection below |
praisonaiagents | import praisonaiagents | PraisonAI agents framework |
praisonai_tools | import praisonai_tools | PraisonAI tools package |
agentops | import agentops | AgentOps observability |
litellm | import litellm | LiteLLM package |
openai | import openai | OpenAI Python client |
chromadb | importlib.util.find_spec("chromadb") | Vector store backend; used by ChromaVectorStore |
pinecone | importlib.util.find_spec("pinecone") | Vector store backend; used by PineconeVectorStore |
qdrant_client | importlib.util.find_spec("qdrant_client") | Vector store backend |
weaviate | importlib.util.find_spec("weaviate") | Vector store backend |
langgraph | find_spec("langgraph") + find_spec("langgraph.prebuilt") | LangGraph framework |
openai_agents | Distribution check + from agents import Runner | OpenAI Agents SDK |
google_adk | _google_adk_probe() — three-step: dist check → google.adk spec → Agent import | Google ADK framework; True only when all three steps pass |
Vector store backends use the same centralized probe registry — praisonai.adapters.vector_stores calls is_available("chromadb") / is_available("pinecone") rather than maintaining its own _check_* helpers.
Adapter Availability vs Framework Availability
Two distinct concepts affect whether an AutoGen-family adapter will actually work:
| Question | API |
|---|---|
| “Is the underlying package importable?” | is_available("autogen") from _framework_availability |
“Will calling adapter.run(...) actually work?” | AutoGenV4Adapter().is_available() (folds implemented flag) |
| “Is the adapter usable right now?” | get_default_registry().is_available("<name>") — folds in implemented markers AND applies the 30 s transient-negative cool-down |
For AutoGen v0.4 and AG2 today: the package may import successfully, but the adapter still reports False because implemented = False. This is the safety-by-default behaviour added in PR #2086.
from praisonai._framework_availability import is_available
# Package-level check: is the v0.4 package installed?
pkg_available = is_available("autogen_v4") # True if autogen-agentchat is installed
# Adapter-level check: will dispatch actually work?
from praisonai.framework_adapters.autogen_adapter import AutoGenV4Adapter
adapter_available = AutoGenV4Adapter().is_available() # False — implemented = False
# pkg_available may be True while adapter_available is False
# Always use adapter.is_available() to test dispatch-readinessAG2 Detection Quirk
AG2 ships under the autogen namespace, so is_available("ag2") checks both:
importlib.metadata.distribution('ag2')– Ensures AG2 distribution is installedimportlib.util.find_spec("autogen")– Ensuresautogennamespace is importable
This prevents false positives when only the legacy AutoGen package is installed.
from praisonai._framework_availability import is_available
# This checks both AG2 distribution AND autogen namespace
if is_available("ag2"):
print("AG2 is properly installed and autogen namespace is available")Cache Management (module-level probes)
These semantics apply to praisonai._framework_availability.is_available() — the low-level, per-package probe. The higher-level FrameworkAdapterRegistry.is_available() used by pick_default() and list_available_frameworks() follows the TTL rules in the next section.
Results are cached indefinitely until explicitly invalidated:
from praisonai._framework_availability import is_available, invalidate
# First call - performs actual import check
result1 = is_available("crewai") # Slow: actual import
# Subsequent calls - returns cached result
result2 = is_available("crewai") # Fast: cached
# Invalidate specific framework
invalidate("crewai")
result3 = is_available("crewai") # Slow: re-checks import
# Invalidate entire cache
invalidate() # Clear all cached resultsCache Management (adapter registry)
FrameworkAdapterRegistry.is_available(name) caches results so hot-path callers (pick_default, list_available_frameworks) do not re-probe. Only positives and structural failures are pinned for the process; every other negative is transient and expires after a 30-second cool-down (_avail_retry_cooldown) so a mid-life pip install is picked up without a restart (PraisonAI PR #5307).
| Probe outcome | Cache lifetime | Reason |
|---|---|---|
Returned True | Whole process | The framework is installed; nothing changes until restart. |
Returned False (built-in is_available() check via find_spec) | 30 s cool-down, then reprobe | An operator may pip install an optional extra mid-life; the negative expires so praisonai serve / daemons pick it up without a restart. |
Raised ImportError | 30 s cool-down, then reprobe | Same as above — a missing optional dep is transient. |
Raised ValueError / TypeError | Whole process | Structural — a mis-registered adapter can’t be fixed by reprobing. |
| Raised any other exception | 30 s cool-down, then reprobe | Transient — racing entry-point scan, boot-time network probe timeout, in-flight config write. |
The cool-down is anchored to after the probe returns, so a probe that itself blocks for ≥ 30 s does not write an already-expired deadline. Call registry.invalidate_availability(name) (or with no argument) to force a reprobe sooner.
from praisonai.framework_adapters import get_default_registry
registry = get_default_registry()
# Transient negative (missing optional dep): the next call after ~30s reprobes automatically.
available = registry.is_available("crewai")
# Or force an immediate reprobe:
registry.invalidate_availability("crewai")Thread Safety
The availability checker uses double-checked locking under threading.Lock for thread-safe caching:
import threading
from praisonai._framework_availability import is_available
def worker():
# Safe to call from multiple threads
return is_available("crewai")
threads = [threading.Thread(target=worker) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()Custom Adapter Usage
Plugin authors can use _framework_availability for their is_available() implementations:
from praisonai._framework_availability import is_available
from praisonai.framework_adapters.base import BaseFrameworkAdapter
class MyAdapter(BaseFrameworkAdapter):
name = "myframework"
def is_available(self) -> bool:
return is_available("myframework") # Uses centralized detectionTesting Support
Use invalidate() in tests that mock importlib:
import unittest.mock
from praisonai._framework_availability import is_available, invalidate
def test_framework_detection():
# Clear any cached results
invalidate()
with unittest.mock.patch('importlib.util.find_spec', return_value=None):
assert not is_available("crewai")
# Clear cache again for clean test state
invalidate()Backward-compat constants on praisonai.agents_generator
Six module-level constants are available for backward compatibility and can be imported directly from praisonai.agents_generator.
from praisonai.agents_generator import CREWAI_AVAILABLE, AUTOGEN_AVAILABLE| Constant | Equivalent to |
|---|---|
PRAISONAI_TOOLS_AVAILABLE | is_available("praisonai_tools") |
CREWAI_AVAILABLE | is_available("crewai") |
AUTOGEN_AVAILABLE | is_available("autogen") |
AG2_AVAILABLE | is_available("ag2") |
PRAISONAI_AVAILABLE | is_available("praisonaiagents") |
AGENTOPS_AVAILABLE | is_available("agentops") |
For new code, use is_available("crewai") directly rather than importing these constants.
Best Practices
Prefer adapter.is_available() for dispatch decisions
Package probes (is_available("autogen_v4")) only confirm importability. Use AutoGenV4Adapter().is_available() when you need to know whether execution will actually succeed.
Use invalidate() in tests that mock importlib
Cached results persist for the process lifetime. Call invalidate() before and after monkey-patching importlib so probes re-run cleanly.
Avoid heavy imports inside is_available()
Custom adapters should delegate to is_available() rather than importing large packages on every CLI startup probe.
Use pick_default() instead of manual priority lists
For default framework selection, call FrameworkAdapterRegistry.pick_default() rather than hard-coding probe tuples.