Registry Dependency Injection
Inject custom plugin registries for multi-tenant isolation, sandboxed tests, or per-run overrides
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.
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,
)Quick Start
- 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=[...]) - 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
| Registry | Module | Docs |
|---|---|---|
| Framework adapters | praisonai.framework_adapters.registry | Framework Adapter Plugins |
| Bot platforms | praisonai.bots._registry | Bot Platform Plugins |
| Sandbox backends | praisonai.sandbox._registry | Sandbox Backends |
| Search providers | praisonai.cli.features._search_registry | Search 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
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
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 registrationsCustom default via the injected registry
Omit framework= and the resolved backend comes from your registry’s pick_default():
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
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.nameAfter 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.
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.
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:
# 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.