Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
53 changes: 53 additions & 0 deletions FRAMEWORK_DEFAULTS_CHANGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Framework Default Change Notice

## Breaking Change: Default Framework Changed from `crewai` to `praisonai`

**Effective**: v1.0.0+

### What Changed

When no explicit `--framework` flag is provided and no `framework` key is specified in your YAML configuration file, PraisonAI now defaults to using the `praisonai` framework instead of `crewai`.

### Migration Required

**Before (v0.x):**
```bash
praisonai agents.yaml # Used crewai framework by default
```

**After (v1.0.0+):**
```bash
praisonai agents.yaml # Now uses praisonai framework by default
```

### How to Maintain Previous Behavior

If you need to continue using `crewai` as the default:

1. **Option 1**: Specify framework in YAML file
```yaml
framework: crewai
input: Your task description
roles:
# ... your agents
```

2. **Option 2**: Use CLI flag
```bash
praisonai --framework crewai agents.yaml
```

### Rationale

This change improves the onboarding experience for new users by:
- Providing a more consistent default framework aligned with PraisonAI's core capabilities
- Reducing confusion for users following the README quickstart guide
- Enabling better OpenAI integration out of the box

### Technical Details

- **Issue**: #1877 - Fixed YAML framework initialization bug
- **PR**: #1881 - Defer framework adapter creation until YAML is loaded
- **Files Changed**: `src/praisonai/praisonai/agents_generator.py`

This change affects both synchronous (`generate_crew_and_kickoff()`) and asynchronous (`agenerate_crew_and_kickoff()`) execution paths.
8 changes: 4 additions & 4 deletions src/praisonai/praisonai/agents_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,9 @@ def __init__(self, agent_file, framework, config_list, log_level=None, agent_cal
# CLI users get the process default.
self._adapter_registry = adapter_registry or get_default_registry()

# Get framework adapter (availability already validated at CLI entry)
self.framework_adapter = self._get_framework_adapter(framework)
# Defer framework adapter creation until YAML is loaded
# This fixes the issue where empty framework string fails before YAML framework is read
self.framework_adapter = None

def _get_framework_adapter(self, framework: str) -> FrameworkAdapter:
"""
Expand Down Expand Up @@ -601,7 +602,7 @@ def _prepare(self, config):
if tools_dict:
self.logger.debug("tools folder exists in the root directory")

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

# AutoGen version selection logic
if framework == "autogen":
Expand All @@ -623,7 +624,6 @@ def _prepare(self, config):
use_v4 = autogen_v4_adapter.is_available() and not autogen_v2_adapter.is_available()

framework = "autogen_v4" if use_v4 else "autogen"

# Validate cli_backend compatibility
self._validate_cli_backend_compatibility(config, framework)

Expand Down
159 changes: 159 additions & 0 deletions src/praisonai/tests/unit/test_agents_generator_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,162 @@ def _get_framework_adapter(framework: str):
assert result == "ok"
assert gen.framework == "praisonaiagents"
assert selected_frameworks == ["praisonaiagents"]


@pytest.mark.asyncio
async def test_arun_framework_defaults_to_praisonai_when_no_framework_specified(monkeypatch):
"""Test that AgentsGenerator with YAML file and no --framework flag defaults to praisonai (fixes #1877)"""
from praisonai.agents_generator import AgentsGenerator

class _MockAdapter:
def __init__(self, name):
self.name = name
self.last_tools_dict = None

def resolve(self):
return self

def setup(self, framework_tag=None):
pass

async def arun(
self,
config,
llm_config,
topic,
*,
tools_dict=None,
agent_callback=None,
task_callback=None,
cli_config=None,
):
self.last_tools_dict = tools_dict or {}
return "ok"

praisonai_adapter = _MockAdapter("praisonai")
selected_frameworks = []

def _get_framework_adapter(framework: str):
selected_frameworks.append(framework)
return praisonai_adapter

gen = object.__new__(AgentsGenerator)
gen.framework = None # No --framework flag specified
gen.framework_adapter = None # Deferred initialization
gen.config_list = []
gen.tools = []
gen.agent_callback = None
gen.task_callback = None
gen.cli_config = {}
gen.logger = logging.getLogger("test-agents-generator-async")
gen.tool_resolver = _DummyResolver()
gen._validate_agents_config = lambda cfg: None
gen._get_framework_adapter = _get_framework_adapter
gen._validate_cli_backend_compatibility = lambda cfg, framework: None

# Mock observability and validation
monkeypatch.setattr("praisonai.agents_generator.is_available", lambda _: True)
monkeypatch.setattr(
"praisonai.framework_adapters.validators.assert_framework_available",
lambda _: None,
)
monkeypatch.setattr(
"praisonai.observability.hooks.init_observability",
lambda _: None,
)

# Test YAML config without explicit framework (should default to praisonai)
result = await gen._arun_framework(
{
"input": "test topic",
"roles": {
"writer": {
"role": "Writer",
"goal": "Write content",
"backstory": "Writes",
"tasks": {},
}
},
# No 'framework' key in YAML
}
)

assert result == "ok"
assert gen.framework == "praisonai" # Should default to praisonai
assert selected_frameworks == ["praisonai"] # Should select praisonai, not crewai
assert gen.framework_adapter.name == "praisonai"


@pytest.mark.asyncio
async def test_async_path_adapter_resolve_and_observability_parity(monkeypatch):
"""Test that async path follows same adapter.resolve() + init_observability() pattern as sync path"""
from praisonai.agents_generator import AgentsGenerator

class _MockAdapter:
def __init__(self, name):
self.name = name
self.resolved = False
self.setup_called = False

def resolve(self):
"""Mock resolve() call to track it was called"""
self.resolved = True
return self

def setup(self, framework_tag=None):
"""Mock setup() call to track it was called"""
self.setup_called = True

async def arun(self, config, llm_config, topic, **kwargs):
return "async_result"

mock_adapter = _MockAdapter("testframework")
observability_called = []
validator_called = []

def _get_framework_adapter(framework: str):
return mock_adapter

def mock_init_observability(framework_name):
observability_called.append(framework_name)

def mock_assert_framework_available(framework_name):
validator_called.append(framework_name)

gen = object.__new__(AgentsGenerator)
gen.framework = "testframework"
gen.framework_adapter = None # Deferred initialization
gen.config_list = []
gen.tools = []
gen.agent_callback = None
gen.task_callback = None
gen.cli_config = {}
gen.logger = logging.getLogger("test-async-parity")
gen.tool_resolver = _DummyResolver()
gen._validate_agents_config = lambda cfg: None
gen._get_framework_adapter = _get_framework_adapter
gen._validate_cli_backend_compatibility = lambda cfg, framework: None

# Mock the hooks and validation
monkeypatch.setattr("praisonai.agents_generator.is_available", lambda _: True)
monkeypatch.setattr(
"praisonai.framework_adapters.validators.assert_framework_available",
mock_assert_framework_available,
)
monkeypatch.setattr(
"praisonai.observability.hooks.init_observability",
mock_init_observability,
)

result = await gen._arun_framework({
"input": "test",
"roles": {}
})

# Verify all key initialization steps are called in async path (same as sync)
assert result == "async_result"
assert mock_adapter.resolved == True, "adapter.resolve() should be called"
assert mock_adapter.setup_called == True, "adapter.setup() should be called"
assert observability_called == ["testframework"], "init_observability() should be called with adapter name"
assert validator_called == ["testframework"], "assert_framework_available() should be called with adapter name"
assert gen.framework_adapter == mock_adapter, "framework_adapter should be set to resolved adapter"
Loading