Compare commits

...

8 Commits

Author SHA1 Message Date
Lucas Gomide
c96301fe0d Merge branch 'main' into lg-pytest-improvements 2025-05-01 11:10:47 -03:00
Lucas Gomide
ea52e6fc8f fix: prevent crash when event handler raises exception
Previously, if a registered event handler raised an exception during execution,
it could crash the entire application or interrupt the event dispatch process.
This change wraps handler execution in a try/except block within the `emit` method,
ensuring that exceptions are caught and logged without affecting other handlers or flow.

This improves the resilience of the event bus, especially when handling third-party
or temporary listeners.
2025-04-30 13:43:51 -03:00
Lucas Gomide
bf11f30f7a test: fix flaky test ValueError: Circular reference detected (id repeated) 2025-04-30 13:43:51 -03:00
Lucas Gomide
b453fd3995 test: increase tests timeout on CI 2025-04-30 13:20:18 -03:00
Lucas Gomide
e1961dccae test: adding missing cassettes
We notice that those cassettes are missing after enabling block-network on CI
2025-04-30 13:20:18 -03:00
Lucas Gomide
7b903414cd test: block external requests in CI and set default 10s timeout per test 2025-04-30 13:20:18 -03:00
Lucas Gomide
133e5892b9 build(dev): add pytest-timeout
This will prevent a test from running indefinitely
2025-04-30 13:20:18 -03:00
Lucas Gomide
e3ab999a57 build(dev): add pytest-randomly dependency
By randomizing the test execution order, this helps identify tests
that unintentionally depend on shared state or specific execution
order, which can lead to flaky or unreliable test behavior.
2025-04-30 13:20:18 -03:00
9 changed files with 5349 additions and 67 deletions

View File

@@ -31,4 +31,4 @@ jobs:
run: uv sync --dev --all-extras run: uv sync --dev --all-extras
- name: Run tests - name: Run tests
run: uv run pytest tests -vv run: uv run pytest --block-network --timeout=60 -vv

View File

@@ -85,6 +85,8 @@ dev-dependencies = [
"pytest-asyncio>=0.23.7", "pytest-asyncio>=0.23.7",
"pytest-subprocess>=1.5.2", "pytest-subprocess>=1.5.2",
"pytest-recording>=0.13.2", "pytest-recording>=0.13.2",
"pytest-randomly>=3.16.0",
"pytest-timeout>=2.3.1",
] ]
[project.scripts] [project.scripts]

View File

@@ -70,7 +70,12 @@ class CrewAIEventsBus:
for event_type, handlers in self._handlers.items(): for event_type, handlers in self._handlers.items():
if isinstance(event, event_type): if isinstance(event, event_type):
for handler in handlers: for handler in handlers:
handler(source, event) try:
handler(source, event)
except Exception as e:
print(
f"[EventBus Error] Handler '{handler.__name__}' failed for event '{event_type.__name__}': {e}"
)
self._signal.send(source, event=event) self._signal.send(source, event=event)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -42,29 +42,38 @@ from crewai.utilities.events.event_listener import EventListener
from crewai.utilities.rpm_controller import RPMController from crewai.utilities.rpm_controller import RPMController
from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
ceo = Agent(
role="CEO",
goal="Make sure the writers in your company produce amazing content.",
backstory="You're an long time CEO of a content creation agency with a Senior Writer on the team. You're now working on a new project and want to make sure the content produced is amazing.",
allow_delegation=True,
)
researcher = Agent( @pytest.fixture
role="Researcher", def ceo():
goal="Make the best research and analysis on content about AI and AI agents", return Agent(
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", role="CEO",
allow_delegation=False, goal="Make sure the writers in your company produce amazing content.",
) backstory="You're an long time CEO of a content creation agency with a Senior Writer on the team. You're now working on a new project and want to make sure the content produced is amazing.",
allow_delegation=True,
writer = Agent( )
role="Senior Writer",
goal="Write the best content about AI and AI agents.",
backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.",
allow_delegation=False,
)
def test_crew_with_only_conditional_tasks_raises_error(): @pytest.fixture
def researcher():
return Agent(
role="Researcher",
goal="Make the best research and analysis on content about AI and AI agents",
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
)
@pytest.fixture
def writer():
return Agent(
role="Senior Writer",
goal="Write the best content about AI and AI agents.",
backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.",
allow_delegation=False,
)
def test_crew_with_only_conditional_tasks_raises_error(researcher):
"""Test that creating a crew with only conditional tasks raises an error.""" """Test that creating a crew with only conditional tasks raises an error."""
def condition_func(task_output: TaskOutput) -> bool: def condition_func(task_output: TaskOutput) -> bool:
@@ -146,7 +155,9 @@ def test_crew_config_conditional_requirement():
] ]
def test_async_task_cannot_include_sequential_async_tasks_in_context(): def test_async_task_cannot_include_sequential_async_tasks_in_context(
researcher, writer
):
task1 = Task( task1 = Task(
description="Task 1", description="Task 1",
async_execution=True, async_execution=True,
@@ -194,7 +205,7 @@ def test_async_task_cannot_include_sequential_async_tasks_in_context():
pytest.fail("Unexpected ValidationError raised") pytest.fail("Unexpected ValidationError raised")
def test_context_no_future_tasks(): def test_context_no_future_tasks(researcher, writer):
task2 = Task( task2 = Task(
description="Task 2", description="Task 2",
expected_output="output", expected_output="output",
@@ -258,7 +269,7 @@ def test_crew_config_with_wrong_keys():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_creation(): def test_crew_creation(researcher, writer):
tasks = [ tasks = [
Task( Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
@@ -290,7 +301,7 @@ def test_crew_creation():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_sync_task_execution(): def test_sync_task_execution(researcher, writer):
from unittest.mock import patch from unittest.mock import patch
tasks = [ tasks = [
@@ -331,7 +342,7 @@ def test_sync_task_execution():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_process(): def test_hierarchical_process(researcher, writer):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -352,7 +363,7 @@ def test_hierarchical_process():
) )
def test_manager_llm_requirement_for_hierarchical_process(): def test_manager_llm_requirement_for_hierarchical_process(researcher, writer):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -367,7 +378,7 @@ def test_manager_llm_requirement_for_hierarchical_process():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_manager_agent_delegating_to_assigned_task_agent(): def test_manager_agent_delegating_to_assigned_task_agent(researcher, writer):
""" """
Test that the manager agent delegates to the assigned task agent. Test that the manager agent delegates to the assigned task agent.
""" """
@@ -419,7 +430,7 @@ def test_manager_agent_delegating_to_assigned_task_agent():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_manager_agent_delegating_to_all_agents(): def test_manager_agent_delegating_to_all_agents(researcher, writer):
""" """
Test that the manager agent delegates to all agents when none are specified. Test that the manager agent delegates to all agents when none are specified.
""" """
@@ -529,7 +540,7 @@ def test_manager_agent_delegates_with_varied_role_cases():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_delegating_agents(): def test_crew_with_delegating_agents(ceo, writer):
tasks = [ tasks = [
Task( Task(
description="Produce and amazing 1 paragraph draft of an article about AI Agents.", description="Produce and amazing 1 paragraph draft of an article about AI Agents.",
@@ -553,7 +564,7 @@ def test_crew_with_delegating_agents():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_delegating_agents_should_not_override_task_tools(): def test_crew_with_delegating_agents_should_not_override_task_tools(ceo, writer):
from typing import Type from typing import Type
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -615,7 +626,7 @@ def test_crew_with_delegating_agents_should_not_override_task_tools():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_delegating_agents_should_not_override_agent_tools(): def test_crew_with_delegating_agents_should_not_override_agent_tools(ceo, writer):
from typing import Type from typing import Type
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -679,7 +690,7 @@ def test_crew_with_delegating_agents_should_not_override_agent_tools():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_task_tools_override_agent_tools(): def test_task_tools_override_agent_tools(researcher):
from typing import Type from typing import Type
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -734,7 +745,7 @@ def test_task_tools_override_agent_tools():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_task_tools_override_agent_tools_with_allow_delegation(): def test_task_tools_override_agent_tools_with_allow_delegation(researcher, writer):
""" """
Test that task tools override agent tools while preserving delegation tools when allow_delegation=True Test that task tools override agent tools while preserving delegation tools when allow_delegation=True
""" """
@@ -817,7 +828,7 @@ def test_task_tools_override_agent_tools_with_allow_delegation():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_verbose_output(capsys): def test_crew_verbose_output(researcher, writer, capsys):
tasks = [ tasks = [
Task( Task(
description="Research AI advancements.", description="Research AI advancements.",
@@ -877,7 +888,7 @@ def test_crew_verbose_output(capsys):
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_cache_hitting_between_agents(): def test_cache_hitting_between_agents(researcher, writer, ceo):
from unittest.mock import call, patch from unittest.mock import call, patch
from crewai.tools import tool from crewai.tools import tool
@@ -1050,7 +1061,7 @@ def test_agents_rpm_is_never_set_if_crew_max_RPM_is_not_set():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_sequential_async_task_execution_completion(): def test_sequential_async_task_execution_completion(researcher, writer):
list_ideas = Task( list_ideas = Task(
description="Give me a list of 5 interesting ideas to explore for an article, what makes them unique and interesting.", description="Give me a list of 5 interesting ideas to explore for an article, what makes them unique and interesting.",
expected_output="Bullet point list of 5 important events.", expected_output="Bullet point list of 5 important events.",
@@ -1204,7 +1215,7 @@ async def test_crew_async_kickoff():
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
async def test_async_task_execution_call_count(): async def test_async_task_execution_call_count(researcher, writer):
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
list_ideas = Task( list_ideas = Task(
@@ -1707,7 +1718,7 @@ def test_agents_do_not_get_delegation_tools_with_there_is_only_one_agent():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_sequential_crew_creation_tasks_without_agents(): def test_sequential_crew_creation_tasks_without_agents(researcher):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -1757,7 +1768,7 @@ def test_agent_usage_metrics_are_captured_for_hierarchical_process():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_crew_creation_tasks_with_agents(): def test_hierarchical_crew_creation_tasks_with_agents(researcher, writer):
""" """
Agents are not required for tasks in a hierarchical process but sometimes they are still added Agents are not required for tasks in a hierarchical process but sometimes they are still added
This test makes sure that the manager still delegates the task to the agent even if the agent is passed in the task This test makes sure that the manager still delegates the task to the agent even if the agent is passed in the task
@@ -1810,7 +1821,7 @@ def test_hierarchical_crew_creation_tasks_with_agents():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_crew_creation_tasks_with_async_execution(): def test_hierarchical_crew_creation_tasks_with_async_execution(researcher, writer, ceo):
""" """
Tests that async tasks in hierarchical crews are handled correctly with proper delegation tools Tests that async tasks in hierarchical crews are handled correctly with proper delegation tools
""" """
@@ -1867,7 +1878,7 @@ def test_hierarchical_crew_creation_tasks_with_async_execution():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_crew_creation_tasks_with_sync_last(): def test_hierarchical_crew_creation_tasks_with_sync_last(researcher, writer, ceo):
""" """
Agents are not required for tasks in a hierarchical process but sometimes they are still added Agents are not required for tasks in a hierarchical process but sometimes they are still added
This test makes sure that the manager still delegates the task to the agent even if the agent is passed in the task This test makes sure that the manager still delegates the task to the agent even if the agent is passed in the task
@@ -2170,7 +2181,7 @@ def test_tools_with_custom_caching():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_task_uses_last_output(): def test_conditional_task_uses_last_output(researcher, writer):
"""Test that conditional tasks use the last task output for condition evaluation.""" """Test that conditional tasks use the last task output for condition evaluation."""
task1 = Task( task1 = Task(
description="First task", description="First task",
@@ -2244,7 +2255,7 @@ def test_conditional_task_uses_last_output():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_tasks_result_collection(): def test_conditional_tasks_result_collection(researcher, writer):
"""Test that task outputs are properly collected based on execution status.""" """Test that task outputs are properly collected based on execution status."""
task1 = Task( task1 = Task(
description="Normal task that always executes", description="Normal task that always executes",
@@ -2325,7 +2336,7 @@ def test_conditional_tasks_result_collection():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_multiple_conditional_tasks(): def test_multiple_conditional_tasks(researcher, writer):
"""Test that having multiple conditional tasks in sequence works correctly.""" """Test that having multiple conditional tasks in sequence works correctly."""
task1 = Task( task1 = Task(
description="Initial research task", description="Initial research task",
@@ -2560,7 +2571,7 @@ def test_disabled_memory_using_contextual_memory():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_log_file_output(tmp_path): def test_crew_log_file_output(tmp_path, researcher):
test_file = tmp_path / "logs.txt" test_file = tmp_path / "logs.txt"
tasks = [ tasks = [
Task( Task(
@@ -2658,7 +2669,7 @@ def test_crew_output_file_validation_failures():
Crew(agents=[agent], tasks=[task]).kickoff() Crew(agents=[agent], tasks=[task]).kickoff()
def test_manager_agent(): def test_manager_agent(researcher, writer):
from unittest.mock import patch from unittest.mock import patch
task = Task( task = Task(
@@ -2696,7 +2707,7 @@ def test_manager_agent():
mock_execute_sync.assert_called() mock_execute_sync.assert_called()
def test_manager_agent_in_agents_raises_exception(): def test_manager_agent_in_agents_raises_exception(researcher, writer):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -2718,7 +2729,7 @@ def test_manager_agent_in_agents_raises_exception():
) )
def test_manager_agent_with_tools_raises_exception(): def test_manager_agent_with_tools_raises_exception(researcher, writer):
from crewai.tools import tool from crewai.tools import tool
@tool @tool
@@ -2755,7 +2766,7 @@ def test_manager_agent_with_tools_raises_exception():
@patch("crewai.crew.TaskEvaluator") @patch("crewai.crew.TaskEvaluator")
@patch("crewai.crew.Crew.copy") @patch("crewai.crew.Crew.copy")
def test_crew_train_success( def test_crew_train_success(
copy_mock, task_evaluator, crew_training_handler, kickoff_mock copy_mock, task_evaluator, crew_training_handler, kickoff_mock, researcher, writer
): ):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
@@ -2831,7 +2842,7 @@ def test_crew_train_success(
assert isinstance(received_events[1], CrewTrainCompletedEvent) assert isinstance(received_events[1], CrewTrainCompletedEvent)
def test_crew_train_error(): def test_crew_train_error(researcher, writer):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article", description="Come up with a list of 5 interesting ideas to explore for an article",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -2850,7 +2861,7 @@ def test_crew_train_error():
) )
def test__setup_for_training(): def test__setup_for_training(researcher, writer):
researcher.allow_delegation = True researcher.allow_delegation = True
writer.allow_delegation = True writer.allow_delegation = True
agents = [researcher, writer] agents = [researcher, writer]
@@ -2881,7 +2892,7 @@ def test__setup_for_training():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_replay_feature(): def test_replay_feature(researcher, writer):
list_ideas = Task( list_ideas = Task(
description="Generate a list of 5 interesting ideas to explore for an article, where each bulletpoint is under 15 words.", description="Generate a list of 5 interesting ideas to explore for an article, where each bulletpoint is under 15 words.",
expected_output="Bullet point list of 5 important events. No additional commentary.", expected_output="Bullet point list of 5 important events. No additional commentary.",
@@ -2918,7 +2929,7 @@ def test_replay_feature():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_replay_error(): def test_crew_replay_error(researcher, writer):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article", description="Come up with a list of 5 interesting ideas to explore for an article",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -3314,7 +3325,7 @@ def test_replay_setup_context():
assert crew.tasks[1].prompt_context == "context raw output" assert crew.tasks[1].prompt_context == "context raw output"
def test_key(): def test_key(researcher, writer):
tasks = [ tasks = [
Task( Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
@@ -3383,7 +3394,9 @@ def test_key_with_interpolated_inputs():
assert crew.key == curr_key assert crew.key == curr_key
def test_conditional_task_requirement_breaks_when_singular_conditional_task(): def test_conditional_task_requirement_breaks_when_singular_conditional_task(
researcher, writer
):
def condition_fn(output) -> bool: def condition_fn(output) -> bool:
return output.raw.startswith("Andrew Ng has!!") return output.raw.startswith("Andrew Ng has!!")
@@ -3401,7 +3414,7 @@ def test_conditional_task_requirement_breaks_when_singular_conditional_task():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_task_last_task_when_conditional_is_true(): def test_conditional_task_last_task_when_conditional_is_true(researcher, writer):
def condition_fn(output) -> bool: def condition_fn(output) -> bool:
return True return True
@@ -3428,7 +3441,7 @@ def test_conditional_task_last_task_when_conditional_is_true():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_task_last_task_when_conditional_is_false(): def test_conditional_task_last_task_when_conditional_is_false(researcher, writer):
def condition_fn(output) -> bool: def condition_fn(output) -> bool:
return False return False
@@ -3452,7 +3465,7 @@ def test_conditional_task_last_task_when_conditional_is_false():
assert result.raw == "Hi" assert result.raw == "Hi"
def test_conditional_task_requirement_breaks_when_task_async(): def test_conditional_task_requirement_breaks_when_task_async(researcher, writer):
def my_condition(context): def my_condition(context):
return context.get("some_value") > 10 return context.get("some_value") > 10
@@ -3477,7 +3490,7 @@ def test_conditional_task_requirement_breaks_when_task_async():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_should_skip(): def test_conditional_should_skip(researcher, writer):
task1 = Task(description="Return hello", expected_output="say hi", agent=researcher) task1 = Task(description="Return hello", expected_output="say hi", agent=researcher)
condition_mock = MagicMock(return_value=False) condition_mock = MagicMock(return_value=False)
@@ -3509,7 +3522,7 @@ def test_conditional_should_skip():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_conditional_should_execute(): def test_conditional_should_execute(researcher, writer):
task1 = Task(description="Return hello", expected_output="say hi", agent=researcher) task1 = Task(description="Return hello", expected_output="say hi", agent=researcher)
condition_mock = MagicMock( condition_mock = MagicMock(
@@ -3542,7 +3555,7 @@ def test_conditional_should_execute():
@mock.patch("crewai.crew.CrewEvaluator") @mock.patch("crewai.crew.CrewEvaluator")
@mock.patch("crewai.crew.Crew.copy") @mock.patch("crewai.crew.Crew.copy")
@mock.patch("crewai.crew.Crew.kickoff") @mock.patch("crewai.crew.Crew.kickoff")
def test_crew_testing_function(kickoff_mock, copy_mock, crew_evaluator): def test_crew_testing_function(kickoff_mock, copy_mock, crew_evaluator, researcher):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -3592,7 +3605,7 @@ def test_crew_testing_function(kickoff_mock, copy_mock, crew_evaluator):
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_verbose_manager_agent(): def test_hierarchical_verbose_manager_agent(researcher, writer):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -3613,7 +3626,7 @@ def test_hierarchical_verbose_manager_agent():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_hierarchical_verbose_false_manager_agent(): def test_hierarchical_verbose_false_manager_agent(researcher, writer):
task = Task( task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.", expected_output="5 bullet points with a paragraph for each idea.",
@@ -4186,7 +4199,7 @@ def test_before_kickoff_without_inputs():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_knowledge_sources_works_with_copy(): def test_crew_with_knowledge_sources_works_with_copy(researcher, writer):
content = "Brandon's favorite color is red and he likes Mexican food." content = "Brandon's favorite color is red and he likes Mexican food."
string_source = StringKnowledgeSource(content=content) string_source = StringKnowledgeSource(content=content)
@@ -4195,7 +4208,6 @@ def test_crew_with_knowledge_sources_works_with_copy():
tasks=[Task(description="test", expected_output="test", agent=researcher)], tasks=[Task(description="test", expected_output="test", agent=researcher)],
knowledge_sources=[string_source], knowledge_sources=[string_source],
) )
crew_copy = crew.copy() crew_copy = crew.copy()
assert crew_copy.knowledge_sources == crew.knowledge_sources assert crew_copy.knowledge_sources == crew.knowledge_sources

View File

@@ -547,6 +547,7 @@ def test_excel_knowledge_source(mock_vector_db, tmpdir):
mock_vector_db.query.assert_called_once() mock_vector_db.query.assert_called_once()
@pytest.mark.vcr
def test_docling_source(mock_vector_db): def test_docling_source(mock_vector_db):
docling_source = CrewDoclingSource( docling_source = CrewDoclingSource(
file_paths=[ file_paths=[
@@ -567,6 +568,7 @@ def test_docling_source(mock_vector_db):
mock_vector_db.query.assert_called_once() mock_vector_db.query.assert_called_once()
@pytest.mark.vcr
def test_multiple_docling_sources(): def test_multiple_docling_sources():
urls: List[Union[Path, str]] = [ urls: List[Union[Path, str]] = [
"https://lilianweng.github.io/posts/2024-11-28-reward-hacking/", "https://lilianweng.github.io/posts/2024-11-28-reward-hacking/",

View File

@@ -32,3 +32,16 @@ def test_wildcard_event_handler():
crewai_event_bus.emit("source_object", event) crewai_event_bus.emit("source_object", event)
mock_handler.assert_called_once_with("source_object", event) mock_handler.assert_called_once_with("source_object", event)
def test_event_bus_error_handling(capfd):
@crewai_event_bus.on(BaseEvent)
def broken_handler(source, event):
raise ValueError("Simulated handler failure")
event = TestEvent(type="test_event")
crewai_event_bus.emit("source_object", event)
out, err = capfd.readouterr()
assert "Simulated handler failure" in out
assert "Handler 'broken_handler' failed" in out

28
uv.lock generated
View File

@@ -811,8 +811,10 @@ dev = [
{ name = "pre-commit" }, { name = "pre-commit" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-asyncio" }, { name = "pytest-asyncio" },
{ name = "pytest-randomly" },
{ name = "pytest-recording" }, { name = "pytest-recording" },
{ name = "pytest-subprocess" }, { name = "pytest-subprocess" },
{ name = "pytest-timeout" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "ruff" }, { name = "ruff" },
] ]
@@ -867,8 +869,10 @@ dev = [
{ name = "pre-commit", specifier = ">=3.6.0" }, { name = "pre-commit", specifier = ">=3.6.0" },
{ name = "pytest", specifier = ">=8.0.0" }, { name = "pytest", specifier = ">=8.0.0" },
{ name = "pytest-asyncio", specifier = ">=0.23.7" }, { name = "pytest-asyncio", specifier = ">=0.23.7" },
{ name = "pytest-randomly", specifier = ">=3.16.0" },
{ name = "pytest-recording", specifier = ">=0.13.2" }, { name = "pytest-recording", specifier = ">=0.13.2" },
{ name = "pytest-subprocess", specifier = ">=1.5.2" }, { name = "pytest-subprocess", specifier = ">=1.5.2" },
{ name = "pytest-timeout", specifier = ">=2.3.1" },
{ name = "python-dotenv", specifier = ">=1.0.0" }, { name = "python-dotenv", specifier = ">=1.0.0" },
{ name = "ruff", specifier = ">=0.8.2" }, { name = "ruff", specifier = ">=0.8.2" },
] ]
@@ -4228,6 +4232,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 }, { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 },
] ]
[[package]]
name = "pytest-randomly"
version = "3.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/68/d221ed7f4a2a49a664da721b8e87b52af6dd317af2a6cb51549cf17ac4b8/pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26", size = 13367 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/70/b31577d7c46d8e2f9baccfed5067dd8475262a2331ffb0bfdf19361c9bde/pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6", size = 8396 },
]
[[package]] [[package]]
name = "pytest-recording" name = "pytest-recording"
version = "0.13.2" version = "0.13.2"
@@ -4254,6 +4270,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/10/77/a80e8f9126b95ffd5ad4d04bd14005c68dcbf0d88f53b2b14893f6cc7232/pytest_subprocess-1.5.2-py3-none-any.whl", hash = "sha256:23ac7732aa8bd45f1757265b1316eb72a7f55b41fb21e2ca22e149ba3629fa46", size = 20886 }, { url = "https://files.pythonhosted.org/packages/10/77/a80e8f9126b95ffd5ad4d04bd14005c68dcbf0d88f53b2b14893f6cc7232/pytest_subprocess-1.5.2-py3-none-any.whl", hash = "sha256:23ac7732aa8bd45f1757265b1316eb72a7f55b41fb21e2ca22e149ba3629fa46", size = 20886 },
] ]
[[package]]
name = "pytest-timeout"
version = "2.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/93/0d/04719abc7a4bdb3a7a1f968f24b0f5253d698c9cc94975330e9d3145befb/pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9", size = 17697 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/03/27/14af9ef8321f5edc7527e47def2a21d8118c6f329a9342cc61387a0c0599/pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e", size = 14148 },
]
[[package]] [[package]]
name = "python-bidi" name = "python-bidi"
version = "0.6.3" version = "0.6.3"