Files
crewAI/lib/crewai/tests/project/test_crew_base_context_none.py
Devin AI ebcf0c98d5 Fix: Handle context: None in YAML task configuration (#3929)
This commit fixes issue #3929 where setting 'context: None' in a YAML
task configuration would cause errors.

Changes:
1. Updated _map_task_variables in crew_base.py to explicitly handle
   the 'context' key when present in task_info:
   - Preserves explicit None values from YAML
   - Preserves empty list [] from YAML
   - Resolves non-empty lists to Task instances as before

2. Updated process_config in utilities/config.py to allow None values
   from config to override the NOT_SPECIFIED sentinel:
   - Changed condition to only skip override when current value is
     not None AND not NOT_SPECIFIED
   - This preserves the semantic distinction between 'unspecified'
     (NOT_SPECIFIED) and 'explicitly none' (None)

3. Added comprehensive unit tests:
   - test_config.py: Tests for process_config handling None with
     NOT_SPECIFIED sentinel
   - test_crew_base_context_none.py: Tests for _map_task_variables
     handling context: None, context: [], and context: [tasks]

The fix ensures that when users set 'context: None' in YAML, it is
properly preserved as None in the Task instance, rather than being
ignored or causing errors.

Co-Authored-By: João <joao@crewai.com>
2025-11-16 13:58:33 +00:00

221 lines
7.0 KiB
Python

"""Tests for crew_base._map_task_variables handling context: None."""
import pytest
from crewai import Agent, Task
from crewai.project.crew_base import CrewBase
@CrewBase
class TestCrew:
"""Test crew for context: None handling."""
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
class TestMapTaskVariablesContextNone:
"""Test suite for _map_task_variables handling context: None."""
def test_map_task_variables_with_context_none(self):
"""Test that context: None in task_info is preserved in tasks_config."""
from crewai.project.crew_base import _map_task_variables
class MockCrewInstance:
def __init__(self):
self.tasks_config = {
"test_task": {
"description": "Test task",
"expected_output": "Test output",
"context": None
}
}
instance = MockCrewInstance()
task_info = {"context": None}
_map_task_variables(
instance,
task_name="test_task",
task_info=task_info,
agents={},
tasks={},
output_json_functions={},
tool_functions={},
callback_functions={},
output_pydantic_functions={}
)
assert instance.tasks_config["test_task"]["context"] is None
def test_map_task_variables_with_context_empty_list(self):
"""Test that context: [] in task_info is preserved as empty list."""
from crewai.project.crew_base import _map_task_variables
class MockCrewInstance:
def __init__(self):
self.tasks_config = {
"test_task": {
"description": "Test task",
"expected_output": "Test output"
}
}
instance = MockCrewInstance()
task_info = {"context": []}
_map_task_variables(
instance,
task_name="test_task",
task_info=task_info,
agents={},
tasks={},
output_json_functions={},
tool_functions={},
callback_functions={},
output_pydantic_functions={}
)
assert instance.tasks_config["test_task"]["context"] == []
def test_map_task_variables_with_context_list(self):
"""Test that context with task names is resolved to Task instances."""
from crewai.project.crew_base import _map_task_variables
class MockCrewInstance:
def __init__(self):
self.tasks_config = {
"test_task": {
"description": "Test task",
"expected_output": "Test output"
}
}
instance = MockCrewInstance()
task1 = Task(description="Task 1", expected_output="Output 1")
task2 = Task(description="Task 2", expected_output="Output 2")
tasks = {
"task1": lambda: task1,
"task2": lambda: task2
}
task_info = {"context": ["task1", "task2"]}
_map_task_variables(
instance,
task_name="test_task",
task_info=task_info,
agents={},
tasks=tasks,
output_json_functions={},
tool_functions={},
callback_functions={},
output_pydantic_functions={}
)
assert len(instance.tasks_config["test_task"]["context"]) == 2
assert instance.tasks_config["test_task"]["context"][0] is task1
assert instance.tasks_config["test_task"]["context"][1] is task2
def test_map_task_variables_without_context_key(self):
"""Test that missing context key doesn't add context to tasks_config."""
from crewai.project.crew_base import _map_task_variables
class MockCrewInstance:
def __init__(self):
self.tasks_config = {
"test_task": {
"description": "Test task",
"expected_output": "Test output"
}
}
instance = MockCrewInstance()
task_info = {}
_map_task_variables(
instance,
task_name="test_task",
task_info=task_info,
agents={},
tasks={},
output_json_functions={},
tool_functions={},
callback_functions={},
output_pydantic_functions={}
)
assert "context" not in instance.tasks_config["test_task"]
class TestTaskWithContextNoneFromConfig:
"""Integration tests for Task creation with context: None from config."""
def test_task_with_context_none_from_config(self):
"""Test that Task can be created with config containing context: None."""
task = Task(
description="Test task",
expected_output="Test output",
config={"context": None}
)
assert task.context is None
assert task.description == "Test task"
assert task.expected_output == "Test output"
def test_task_with_context_none_direct(self):
"""Test that Task can be created with context=None directly."""
task = Task(
description="Test task",
expected_output="Test output",
context=None
)
assert task.context is None
assert task.description == "Test task"
assert task.expected_output == "Test output"
def test_task_with_context_empty_list_from_config(self):
"""Test that Task can be created with config containing context: []."""
task = Task(
description="Test task",
expected_output="Test output",
config={"context": []}
)
assert task.context == []
assert task.description == "Test task"
assert task.expected_output == "Test output"
def test_task_without_context_uses_default(self):
"""Test that Task without context uses NOT_SPECIFIED default."""
from crewai.utilities.constants import NOT_SPECIFIED
task = Task(
description="Test task",
expected_output="Test output"
)
assert task.context is NOT_SPECIFIED
assert task.description == "Test task"
assert task.expected_output == "Test output"
def test_task_with_context_list_from_config(self):
"""Test that Task can be created with config containing context list."""
context_task = Task(
description="Context task",
expected_output="Context output"
)
task = Task(
description="Test task",
expected_output="Test output",
config={"context": [context_task]}
)
assert isinstance(task.context, list)
assert len(task.context) == 1
assert task.context[0] is context_task