> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Framework Availability Detection"
        Check[🔍 Check Request] --> Cache{💾 Cached?}
        Cache -->|Hit| Result[✅ Return Result]
        Cache -->|Miss| Probe[🔬 Probe Import]
        Probe --> Store[💾 Store Result]
        Store --> Result
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class Check input
    class Cache,Probe,Store process
    class Result output

```

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Feature as Framework Availability Detection

    User->>Agent: Request
    Agent->>Feature: Process request
    Feature-->>Agent: Result    Agent-->>User: Response
```

## Quick Start

<Steps>
  <Step title="Run YAML — framework auto-detected">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai import PraisonAI

    praison = PraisonAI(agent_file="agents.yaml")
    praison.run()  # uses first available framework (crewai → praisonai → autogen)
    ```

    <Note>
      `ag2` is registered and can be requested explicitly via `--framework ag2` or `framework: ag2` in YAML, but it is not part of the auto-detect chain because its adapter is an unimplemented stub. Selecting it explicitly will raise `NotImplementedError` at run time until a full implementation lands.
    </Note>

    <Note>
      When **none** of the supported framework adapters are installed (e.g. a container that ships only `praisonai` without the `crewai` / `autogen` / `praisonaiagents` extras), the auto-detect chain raises `RuntimeError: No supported framework installed. Available adapters: []`. This is the intended, actionable failure — install one of the adapters (`pip install praisonai[crewai]`, `pip install praisonai[autogen]`, or `pip install praisonaiagents`) and re-run.
    </Note>
  </Step>

  <Step title="Probe before selecting a framework">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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}")
    ```
  </Step>
</Steps>

***

## 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 |

<Note>
  `google_adk` detection uses a three-step probe in `_google_adk_probe()`: (1) `importlib.metadata.distribution("google-adk")` — the PyPI dist must be installed; (2) `importlib.util.find_spec("google.adk")` — the namespace must be discoverable; (3) `from google.adk.agents import Agent` with fallback to `from google.adk import Agent`. All three must succeed for `is_available("google_adk")` to return `True`. Install with `pip install 'praisonai-frameworks[google-adk]'` or `pip install 'praisonai[google-adk]'`.
</Note>

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.

<Note>
  `autogen_v2` is an **adapter name** registered in `FrameworkAdapterRegistry`, not a probe name in `_framework_availability`. The probe name remains `autogen` (it imports the `autogen` v0.2 package). Use the adapter's `.is_available()` method to test whether a specific adapter will dispatch successfully — it folds in the `implemented` marker.
</Note>

***

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

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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](https://github.com/MervinPraison/PraisonAI/pull/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. |

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Probe[🔬 registry.is_available] --> Result{Outcome?}
    Result -->|True| Perm1[💾 Cache for process]
    Result -->|False via find_spec| TTL1[⏱️ 30 s cool-down then reprobe]
    Result -->|ImportError| TTL2[⏱️ 30 s cool-down then reprobe]
    Result -->|ValueError / TypeError| Perm2[💾 Cache for process]
    Result -->|Other exception| TTL3[⏱️ 30 s cool-down then reprobe]
    Perm1 --> Return[✅ Return]
    Perm2 --> Return
    TTL1 --> Return
    TTL2 --> Return
    TTL3 --> Return

    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff
    classDef ttl fill:#6366F1,stroke:#7C90A0,color:#fff

    class Probe process
    class Result decision
    class Perm1,Perm2 result
    class TTL1,TTL2,TTL3 ttl
    class Return result
```

<Note>
  **Mid-life install recovery:** `praisonai serve` / long-running daemons no longer need a restart after `pip install 'praisonai[crewai]'`. The registry re-probes any transient negative after \~30 s (`_avail_retry_cooldown`) and invalidates the process-wide `_framework_availability` cache before re-probing, so both cache tiers see the newly-installed dep on the next call.
</Note>

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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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
```

<Warning>
  Third-party adapters cannot extend the known probe names at runtime. The `_PROBES` dict is module-level and not extensible via public API.
</Warning>

***

## Testing Support

Use `invalidate()` in tests that mock `importlib`:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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.

<Note>
  The parallel wrapper accessor `praisonai.inbuilt_tools.PRAISONAI_TOOLS_AVAILABLE` also delegates to `is_available("praisonai_tools")` (PR #5318 aligned it with this single source of truth; prior to that PR the wrapper always returned `True`).
</Note>

***

## Best Practices

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="Avoid heavy imports inside is_available()">
    Custom adapters should delegate to `is_available()` rather than importing large packages on every CLI startup probe.
  </Accordion>

  <Accordion title="Use pick_default() instead of manual priority lists">
    For default framework selection, call `FrameworkAdapterRegistry.pick_default()` rather than hard-coding probe tuples.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Framework Adapter Plugins" icon="plug" href="/docs/features/framework-adapter-plugins">
    Creating custom framework adapters
  </Card>

  <Card title="AutoGen Framework" icon="robot" href="/docs/framework/autogen">
    AutoGen framework integration
  </Card>

  <Card title="Google ADK Framework" icon="robot" href="/docs/framework/google-adk">
    Google ADK framework integration
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.