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.

python
from praisonaiagents import Agent

agent = Agent(name="assistant", instructions="You are a helpful assistant.")
agent.start("Run my task once a framework is available.")
python
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.

Diagram

Framework Availability Detection

Hit

Miss

🔍 Check Request

💾 Cached?

✅ Return Result

🔬 Probe Import

💾 Store Result

How It Works

Diagram
Framework Availability DetectionAgentUserFramework Availability DetectionAgentUserRequestProcess requestResult Agent–>>User: Response

Quick Start

  1. 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)
  2. 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

FunctionSignatureBehaviour
is_available(name: str) -> boolRaises ValueError on unknown nameCached on first call (double-checked locking under threading.Lock)
`invalidate(name: str \None = None) -> None`Pass None to clear the whole cacheUseful in tests that monkey-patch importlib

Known Probe Names

The following framework names are supported:

NameDetection MethodNotes
crewaiimportlib.util.find_spec("crewai")Standard probe
autogenimportlib.util.find_spec("autogen")AutoGen v0.2 package
autogen_v4find_spec("autogen_agentchat") + find_spec("autogen_ext")AutoGen v0.4 packages
ag2Distribution + namespace checkSee AG2 Detection below
praisonaiagentsimport praisonaiagentsPraisonAI agents framework
praisonai_toolsimport praisonai_toolsPraisonAI tools package
agentopsimport agentopsAgentOps observability
litellmimport litellmLiteLLM package
openaiimport openaiOpenAI Python client
chromadbimportlib.util.find_spec("chromadb")Vector store backend; used by ChromaVectorStore
pineconeimportlib.util.find_spec("pinecone")Vector store backend; used by PineconeVectorStore
qdrant_clientimportlib.util.find_spec("qdrant_client")Vector store backend
weaviateimportlib.util.find_spec("weaviate")Vector store backend
langgraphfind_spec("langgraph") + find_spec("langgraph.prebuilt")LangGraph framework
openai_agentsDistribution check + from agents import RunnerOpenAI Agents SDK
google_adk_google_adk_probe() — three-step: dist check → google.adk spec → Agent importGoogle 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:

QuestionAPI
“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.

python
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-readiness

AG2 Detection Quirk

AG2 ships under the autogen namespace, so is_available("ag2") checks both:

  1. importlib.metadata.distribution('ag2') – Ensures AG2 distribution is installed
  2. importlib.util.find_spec("autogen") – Ensures autogen namespace is importable

This prevents false positives when only the legacy AutoGen package is installed.

python
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:

python
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 results

Cache 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 outcomeCache lifetimeReason
Returned TrueWhole processThe framework is installed; nothing changes until restart.
Returned False (built-in is_available() check via find_spec)30 s cool-down, then reprobeAn operator may pip install an optional extra mid-life; the negative expires so praisonai serve / daemons pick it up without a restart.
Raised ImportError30 s cool-down, then reprobeSame as above — a missing optional dep is transient.
Raised ValueError / TypeErrorWhole processStructural — a mis-registered adapter can’t be fixed by reprobing.
Raised any other exception30 s cool-down, then reprobeTransient — racing entry-point scan, boot-time network probe timeout, in-flight config write.
Diagram

True

False via find_spec

ImportError

ValueError / TypeError

Other exception

🔬 registry.is_available

Outcome?

💾 Cache for process

⏱️ 30 s cool-down then reprobe

⏱️ 30 s cool-down then reprobe

💾 Cache for process

⏱️ 30 s cool-down then reprobe

✅ Return

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.

python
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:

python
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:

python
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 detection

Testing Support

Use invalidate() in tests that mock importlib:

python
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.

python
from praisonai.agents_generator import CREWAI_AVAILABLE, AUTOGEN_AVAILABLE
ConstantEquivalent to
PRAISONAI_TOOLS_AVAILABLEis_available("praisonai_tools")
CREWAI_AVAILABLEis_available("crewai")
AUTOGEN_AVAILABLEis_available("autogen")
AG2_AVAILABLEis_available("ag2")
PRAISONAI_AVAILABLEis_available("praisonaiagents")
AGENTOPS_AVAILABLEis_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.


Related