Registry Dependency Injection

Inject custom plugin registries for multi-tenant isolation, sandboxed tests, or per-run overrides

python
from praisonaiagents import Agent

agent = Agent(name="di-agent", instructions="Use dependency injection via the agent registry.")
agent.start("Resolve and inject the database service for this task.")

The user runs generation with a custom adapter registry so tenants or tests stay isolated.

Inject a custom adapter registry when you need tenant isolation, parallel test safety, or per-run plugin overrides.

python
from praisonai.framework_adapters.registry import FrameworkAdapterRegistry
from praisonai.agents_generator import AgentsGenerator

registry = FrameworkAdapterRegistry()
registry.register("crewai", CustomCrewAIAdapter)

generator = AgentsGenerator(
    agent_file="agents.yaml",
    framework="crewai",
    config_list=[...],
    adapter_registry=registry,
)
Diagram

Tenant B

Tenant A

Default

get_default_registry

Shared Registry

Custom Registry A

Isolated Adapters

Custom Registry B

Isolated Adapters

AgentsGenerator

AgentsGenerator tenant_a

AgentsGenerator tenant_b

Quick Start

  1. Simple Usage

    Use the process-default registry for normal applications:

    python
    from praisonai.agents_generator import AgentsGenerator
    
    # adapter_registry=None → uses get_default_registry()
    generator = AgentsGenerator("agents.yaml", "crewai", config_list=[...])
  2. With Configuration

    Inject a custom registry for tenant isolation or testing:

    python
    from praisonai.framework_adapters.registry import FrameworkAdapterRegistry
    from praisonai.agents_generator import AgentsGenerator
    
    tenant_registry = FrameworkAdapterRegistry()
    tenant_registry.register("crewai", CustomCrewAIAdapter)
    
    generator = AgentsGenerator(
        agent_file="agents.yaml",
        framework="crewai",
        config_list=[...],
        adapter_registry=tenant_registry,
    )

How It Works

Diagram
AdapterRegistryGeneratorAppAdapterRegistryGeneratorAppAgentsGenerator(adapter_registry=custom)registry.create(“crewai”)CustomCrewAIAdapter()adapter instanceadaptergenerator ready
RegistryModuleDocs
Framework adapterspraisonai.framework_adapters.registryFramework Adapter Plugins
Bot platformspraisonai.bots._registryBot Platform Plugins
Sandbox backendspraisonai.sandbox._registrySandbox Backends
Search providerspraisonai.cli.features._search_registrySearch Providers

All plugin registries expose Registry.default() or get_default_registry() unless noted.

When a SandboxConfig‘s sandbox_type is unknown to the built-in list, SandboxManager now resolves it through praisonai.sandbox._registry automatically — no manual registry.resolve() call is needed for typical use.


Common Patterns

Multi-tenant isolation

python
def create_tenant_generator(tenant_id: str):
    registry = FrameworkAdapterRegistry()
    adapter = EnterpriseCrewAIAdapter if tenant_id == "enterprise" else StandardCrewAIAdapter
    registry.register("crewai", adapter)

    return AgentsGenerator(
        agent_file=f"tenants/{tenant_id}/agents.yaml",
        framework="crewai",
        config_list=[...],
        adapter_registry=registry,
    )

Test isolation

python
def test_custom_adapter():
    test_registry = FrameworkAdapterRegistry()
    test_registry.register("crewai", MockCrewAIAdapter)

    generator = AgentsGenerator(
        agent_file="test_agents.yaml",
        framework="crewai",
        config_list=[...],
        adapter_registry=test_registry,
    )
    # Parallel tests do not share registrations

Custom default via the injected registry

Omit framework= and the resolved backend comes from your registry’s pick_default():

python
from praisonai.framework_adapters.registry import FrameworkAdapterRegistry
from praisonai.agents_generator import AgentsGenerator

registry = FrameworkAdapterRegistry(discover_entry_points=False)
registry.unregister("praisonai")
registry.register("my_adapter", MyAdapter)

# framework omitted → resolved via registry.pick_default() → "my_adapter"
gen = AgentsGenerator(
    agent_file="agents.yaml",
    framework=None,
    config_list=[{"model": "openai/gpt-4o-mini"}],
    adapter_registry=registry,
)

AutoGenerator with custom registry

python
from praisonai.auto import AutoGenerator

reg = FrameworkAdapterRegistry()
auto = AutoGenerator(
    topic="...",
    agent_file="test.yaml",
    framework="crewai",
    config_list=[...],
    adapter_registry=reg,
)

# The resolved adapter is retained after construction
print(auto._adapter.name)   # canonical dispatch object
print(auto.framework)       # authoritative name from adapter.name

After construction, AutoGenerator retains the resolved adapter as self._adapter (mirroring AgentsGenerator.framework_adapter), and self.framework is derived from adapter.name — the authoritative name, not the raw constructor argument. This is a stable observable side effect; the underscore prefix marks it private-by-convention, so treat the attribute name itself as an implementation detail.

Injecting a tool-timeout executor

Like adapter_registry=, the tool_timeout_executor= constructor parameter lets you supply a shared thread-pool instead of letting each AgentsGenerator lazily create its own.

python
import concurrent.futures
from praisonai.agents_generator import AgentsGenerator

# One pool, many sessions
shared_pool = concurrent.futures.ThreadPoolExecutor(max_workers=16)

for cfg in tenant_configs:
    with AgentsGenerator(
        agent_file=cfg.path,
        framework=cfg.framework,
        config_list=cfg.llms,
        tool_timeout_executor=shared_pool,  # borrowed
    ) as gen:
        gen.generate_crew_and_kickoff()

The injected executor is treated as borrowed — gen.close() and exiting the with block will not shut it down. You stay responsible for shared_pool.shutdown().


Concurrent runs on one AgentsGenerator

generate_crew_and_kickoff() and agenerate_crew_and_kickoff() are safe to call from overlapping tasks on the same instance. Each call’s per-run state (tool-timeout wrap, agent-wrap resolver, native tool-timeout resolver) travels in that call’s own prep dict and is passed explicitly to the adapter — there is no shared self._run_ctx to race on.

Diagram

🧩 One AgentsGenerator

▶️ Run 1 → own run_ctx

▶️ Run 2 → own run_ctx

✅ Adapter gets Run 1 timeout wrap

✅ Adapter gets Run 2 timeout wrap

python
import asyncio
from praisonai.agents_generator import AgentsGenerator

async def main():
    gen = AgentsGenerator(
        agent_file="agents.yaml",
        framework="praisonai",
        config_list=[{"model": "gpt-4o-mini"}],
    )
    # Each run carries its own tool-timeout context; neither can clobber the other.
    await asyncio.gather(
        gen.agenerate_crew_and_kickoff(),
        gen.agenerate_crew_and_kickoff(),
    )

asyncio.run(main())

Fixed in PraisonAI PR #5905. Before that release, a second overlapping call could overwrite self._run_ctx, so the first run’s adapter received the second run’s tool-timeout wrap. self._run_ctx is still written for backward compatibility, so existing code that inspects it keeps working.


Best Practices

Prefer DI in library code

Accept registries as parameters rather than calling defaults inside helpers:

python
# Good — accepts registry parameter
def process_with_framework(framework: str, registry: FrameworkAdapterRegistry):
    return registry.create(framework).run(...)

# Risky — hardcoded to default
def process_with_framework(framework: str):
    from praisonai.framework_adapters.registry import get_default_registry
    return get_default_registry().create(framework).run(...)
Give each thread its own registry

Registry operations are thread-safe, but adapter instances may not be. Prefer one registry per worker or test case.

Reuse one generator for concurrent runs

You do not need a separate AgentsGenerator per concurrent run just to isolate tool_timeout or framework-adapter closures — per-run state is carried per call. See Concurrent runs on one AgentsGenerator.

Use the default registry for single-tenant CLI apps

Custom registries add complexity. Reach for DI only when you need tenant overrides, test isolation, or sandboxed experimental adapters.


Related