Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion src/praisonai/praisonai/_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,59 @@ def list_all_names(self) -> list[str]:
Sorted list of all registered names and aliases
"""
with self._lock:
return sorted(list(self._items.keys()) + list(self._aliases.keys()))
return sorted(list(self._items.keys()) + list(self._aliases.keys()))

def get_by_attr(self, module_name: str, attr_name: str) -> Type[T]:
"""Get a plugin by attribute name for __getattr__ dispatch.

Args:
module_name: Module name requesting the attribute (for error messages)
attr_name: Attribute name to resolve

Returns:
Plugin class

Raises:
AttributeError: If plugin is not found
"""
try:
return self.resolve(attr_name)
except ValueError:
raise AttributeError(f"module {module_name!r} has no attribute {attr_name!r}")


def create_lazy_getattr(registry: PluginRegistry[T]) -> Callable[[str], T]:
"""Create a __getattr__ function backed by a PluginRegistry.

This replaces manual if/elif ladders in __init__.py files with a data-driven
approach using the plugin registry.

Args:
registry: The plugin registry to use for resolution

Returns:
Function that can be used as __getattr__ in a module

Example:
# In __init__.py:
from ._registry import create_lazy_getattr

# Assuming you have a registry instance
__getattr__ = create_lazy_getattr(my_registry)
"""
def __getattr__(name: str) -> T:
try:
plugin_class = registry.resolve(name)
return plugin_class
except ValueError:
# Get the calling module name for error context
import inspect
frame = inspect.currentframe()
if frame and frame.f_back:
module_name = frame.f_back.f_globals.get('__name__', 'unknown')
else:
module_name = 'unknown'

raise AttributeError(f"module {module_name!r} has no attribute {name!r}")

return __getattr__
53 changes: 18 additions & 35 deletions src/praisonai/praisonai/agents_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,45 +639,28 @@ def generate_crew_and_kickoff(self):
self.logger.debug("tools folder exists in the root directory")

framework = self.framework or config.get('framework', 'crewai')

# Determine AutoGen version if needed (keeping compatibility logic)
if framework == "autogen":
autogen_version = os.environ.get("AUTOGEN_VERSION", "auto").lower()
autogen_v4_adapter = self._get_framework_adapter("autogen_v4")
autogen_v2_adapter = self._get_framework_adapter("autogen")

use_v4 = False
if autogen_version == "v0.4" and autogen_v4_adapter.is_available():
use_v4 = True
elif autogen_version == "v0.2" and autogen_v2_adapter.is_available():
use_v4 = False
elif autogen_version == "auto":
use_v4 = autogen_v4_adapter.is_available()
else:
use_v4 = autogen_v4_adapter.is_available() and not autogen_v2_adapter.is_available()

framework = "autogen_v4" if use_v4 else "autogen"

# Initialize AgentOps if available
try:
import agentops
agentops_api_key = os.getenv("AGENTOPS_API_KEY")
if agentops_api_key:
agentops.init(agentops_api_key, default_tags=[framework])
except ImportError:
pass

# Update framework adapter if framework changed (e.g., AutoGen version selection)
if framework != self.framework:
self.framework = framework
self.framework_adapter = self._get_framework_adapter(framework)

# Get initial adapter and resolve to concrete variant
initial_adapter = self._get_framework_adapter(framework)
adapter = initial_adapter.resolve()

# Initialize observability hooks
from .observability.hooks import init_observability
init_observability(adapter.name)

# Run adapter setup hooks
adapter.setup(framework_tag=adapter.name)

# Update framework reference if resolution changed it
self.framework = adapter.name
self.framework_adapter = adapter

# Validate framework availability for non-CLI callers
from .framework_adapters.validators import assert_framework_available
assert_framework_available(framework)
assert_framework_available(adapter.name)
Comment on lines +647 to +660
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate resolved framework availability before init_observability() and adapter.setup().

adapter.setup() can execute framework-specific initialization before assert_framework_available(...) runs, which can produce less actionable errors than the centralized validator. Move the availability assertion immediately after resolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai/praisonai/agents_generator.py` around lines 647 - 660, After
resolving the adapter, call the centralized validator
assert_framework_available(adapter.name) immediately before invoking
init_observability(...) and adapter.setup(...); specifically, move the
assert_framework_available(adapter.name) call to just after adapter resolution
(and before init_observability and adapter.setup) so framework availability is
guaranteed prior to running init_observability and adapter.setup.


self.logger.info(f"Using framework: {framework}")
return self.framework_adapter.run(
self.logger.info(f"Using framework: {adapter.name}")
return adapter.run(
config,
self.config_list,
topic,
Expand Down
34 changes: 33 additions & 1 deletion src/praisonai/praisonai/framework_adapters/autogen_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
"""

import logging
import os
from typing import Dict, List, Any, Optional, Callable
from .base import BaseFrameworkAdapter

logger = logging.getLogger(__name__)


class AutoGenAdapter(BaseFrameworkAdapter):
"""Adapter for AutoGen v0.2 framework."""
"""Adapter for AutoGen v0.2 framework with version resolution."""

name = "autogen"
install_hint = 'pip install "praisonai[autogen]"' # v0.2 only
Expand All @@ -26,6 +27,37 @@ def is_available(self) -> bool:
except ImportError:
return False

def resolve(self) -> "BaseFrameworkAdapter":
"""Pick the concrete AutoGen adapter variant based on environment and availability."""
autogen_version = os.environ.get("AUTOGEN_VERSION", "auto").lower()

# Import the specific adapters
v4_adapter = AutoGenV4Adapter()
v2_adapter = self # Current instance is v0.2

if autogen_version == "v0.4" and v4_adapter.is_available():
logger.info("AutoGen version resolution: Using v0.4 (explicitly requested)")
return v4_adapter
elif autogen_version == "v0.2" and v2_adapter.is_available():
logger.info("AutoGen version resolution: Using v0.2 (explicitly requested)")
return v2_adapter
elif autogen_version == "auto":
# Auto-detect: prefer v0.4 if available, fallback to v0.2
if v4_adapter.is_available():
logger.info("AutoGen version resolution: Using v0.4 (auto-detected)")
return v4_adapter
else:
logger.info("AutoGen version resolution: Using v0.2 (auto-detected fallback)")
return v2_adapter
else:
# Invalid version or neither available, try both
if v4_adapter.is_available() and not v2_adapter.is_available():
logger.info("AutoGen version resolution: Using v0.4 (only available version)")
return v4_adapter
else:
logger.info("AutoGen version resolution: Using v0.2 (default fallback)")
return v2_adapter

def run(
self,
config: Dict[str, Any],
Expand Down
24 changes: 24 additions & 0 deletions src/praisonai/praisonai/framework_adapters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ def is_available(self) -> bool:
"""Check if the framework is available for import."""
...

def resolve(self) -> "FrameworkAdapter":
"""Pick the concrete adapter variant (e.g. autogen v0.2 vs v0.4).

Returns:
The resolved adapter instance (self or a different adapter)
"""
...

def setup(self, *, framework_tag: str) -> None:
"""Framework-specific pre-run hooks (observability, sdk init, etc.).

Args:
framework_tag: Framework name for observability tagging
"""
...

def run(
self,
config: Dict[str, Any],
Expand Down Expand Up @@ -87,6 +103,14 @@ def _format_template(self, template: str, **kwargs) -> str:
logger.warning("Template formatting error: %s; returning original template", e)
return template

def resolve(self) -> "FrameworkAdapter":
"""Default implementation returns self."""
return self

def setup(self, *, framework_tag: str) -> None:
"""Default implementation does nothing."""
pass

def cleanup(self) -> None:
"""Clean up resources - default implementation does nothing."""
pass
Expand Down
74 changes: 4 additions & 70 deletions src/praisonai/praisonai/integrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,73 +56,7 @@
]


def __getattr__(name):
"""Lazy load integrations to minimize import overhead."""
if name == 'BaseCLIIntegration':
from .base import BaseCLIIntegration
return BaseCLIIntegration
elif name == 'CLIExecutionError':
from .base import CLIExecutionError
return CLIExecutionError
elif name == 'ClaudeCodeIntegration':
from .claude_code import ClaudeCodeIntegration
return ClaudeCodeIntegration
elif name == 'GeminiCLIIntegration':
from .gemini_cli import GeminiCLIIntegration
return GeminiCLIIntegration
elif name == 'CodexCLIIntegration':
from .codex_cli import CodexCLIIntegration
return CodexCLIIntegration
elif name == 'CursorCLIIntegration':
from .cursor_cli import CursorCLIIntegration
return CursorCLIIntegration
elif name in ('ManagedAgent', 'ManagedAgentIntegration'):
from .managed_agents import ManagedAgent
return ManagedAgent
elif name == 'AnthropicManagedAgent':
from .managed_agents import AnthropicManagedAgent
return AnthropicManagedAgent
elif name == 'LocalManagedAgent':
from .managed_local import LocalManagedAgent
return LocalManagedAgent
elif name == 'LocalManagedConfig':
from .managed_local import LocalManagedConfig
return LocalManagedConfig
elif name == 'SandboxedAgent':
from .sandboxed_agent import SandboxedAgent
return SandboxedAgent
elif name == 'SandboxedAgentConfig':
from .sandboxed_agent import SandboxedAgentConfig
return SandboxedAgentConfig
elif name in ('ManagedConfig', 'ManagedBackendConfig'):
from .managed_agents import ManagedConfig
return ManagedConfig
elif name == 'get_available_integrations':
from .base import get_available_integrations
return get_available_integrations
elif name == 'ExternalAgentRegistry':
from .registry import ExternalAgentRegistry
return ExternalAgentRegistry
elif name == 'get_registry':
from .registry import get_registry
return get_registry
elif name == 'register_integration':
from .registry import register_integration
return register_integration
elif name == 'create_integration':
from .registry import create_integration
return create_integration
# New canonical agent backends
elif name == 'HostedAgent':
from .hosted_agent import HostedAgent
return HostedAgent
elif name == 'HostedAgentConfig':
from .hosted_agent import HostedAgentConfig
return HostedAgentConfig
elif name == 'LocalAgent':
from .local_agent import LocalAgent
return LocalAgent
elif name == 'LocalAgentConfig':
from .local_agent import LocalAgentConfig
return LocalAgentConfig
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def __getattr__(name: str):
"""Lazy load integrations using unified registry."""
from ._unified_registry import INTEGRATIONS_REGISTRY
return INTEGRATIONS_REGISTRY.get_by_attr(__name__, name)
Loading
Loading