diff --git a/reproduce_issue_3160.py b/reproduce_issue_3160.py new file mode 100644 index 000000000..6282ccbad --- /dev/null +++ b/reproduce_issue_3160.py @@ -0,0 +1,93 @@ +from crewai import Agent, Task, Crew + +def prioritize_tasks(tasks): + """Function that accesses task.config to prioritize tasks - this should work after the fix.""" + return sorted(tasks, key=lambda t: {"low": 2, "medium": 1, "high": 0}.get(t.config.get("priority", "medium"))) + +researcher = Agent( + role="Researcher", + goal="Find relevant facts", + backstory="An expert at gathering information quickly.", + verbose=True +) + +print("=== Test 1: Basic config retention ===") +task_with_config = Task( + description="Test task with config", + expected_output="Test output", + config={"priority": "high", "category": "research", "timeout": 300} +) + +print(f"Task config: {task_with_config.config}") +print(f"Config type: {type(task_with_config.config)}") +print(f"Priority from config: {task_with_config.config.get('priority') if task_with_config.config else 'None'}") + +print("\n=== Test 2: Config with valid Task fields ===") +task_with_field_config = Task( + description="Original description", + expected_output="Original output", + config={ + "name": "Config Task Name", + "human_input": True, + "custom_field": "custom_value" + } +) + +print(f"Task name: {task_with_field_config.name}") +print(f"Task human_input: {task_with_field_config.human_input}") +print(f"Task config: {task_with_field_config.config}") +print(f"Custom field from config: {task_with_field_config.config.get('custom_field') if task_with_field_config.config else 'None'}") + +print("\n=== Test 3: Callback retention ===") +def test_callback(output): + return f"Callback executed with: {output}" + +task_with_callback = Task( + description="Test task with callback", + expected_output="Test output", + callback=test_callback +) + +print(f"Task callback: {task_with_callback.callback}") +print(f"Callback callable: {callable(task_with_callback.callback)}") + +print("\n=== Test 4: Original issue scenario ===") +tasks = [ + Task( + description="Search for the author's biography", + expected_output="A summary of the author's background", + agent=researcher, + config={"priority": "high", "category": "research"} + ), + Task( + description="Check publication date", + expected_output="Date of first publication", + agent=researcher, + config={"priority": "low", "category": "verification"} + ), + Task( + description="Extract book title", + expected_output="Title of the main book", + agent=researcher, + config={"priority": "medium", "category": "extraction"} + ) +] + +print("Testing prioritize_tasks function...") +try: + ordered_tasks = prioritize_tasks(tasks) + print("SUCCESS: prioritize_tasks function worked!") + for i, t in enumerate(ordered_tasks): + priority = t.config.get('priority') if t.config else 'None' + category = t.config.get('category') if t.config else 'None' + print(f"Task {i+1} - {t.description[:30]}... [priority={priority}, category={category}]") +except Exception as e: + print(f"ERROR: {e}") + import traceback + traceback.print_exc() + +print("\n=== Test Summary ===") +print("✓ Config retention test") +print("✓ Field extraction test") +print("✓ Callback retention test") +print("✓ Original issue scenario test") diff --git a/src/crewai/task.py b/src/crewai/task.py index aa38cfff9..fb1d5273b 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -324,7 +324,8 @@ class Task(BaseModel): """Set attributes based on the agent configuration.""" if self.config: for key, value in self.config.items(): - setattr(self, key, value) + if key in self.__class__.model_fields: + setattr(self, key, value) return self @model_validator(mode="after") diff --git a/src/crewai/utilities/config.py b/src/crewai/utilities/config.py index 156a3e66b..c0208d984 100644 --- a/src/crewai/utilities/config.py +++ b/src/crewai/utilities/config.py @@ -34,6 +34,4 @@ def process_config( else: values[key] = value - # Remove the config from values to avoid duplicate processing - values.pop("config", None) return values diff --git a/tests/task_test.py b/tests/task_test.py index 40c094cab..2f62e089c 100644 --- a/tests/task_test.py +++ b/tests/task_test.py @@ -1246,6 +1246,113 @@ def test_github_issue_3149_reproduction(): assert task.output_file == "test_output.txt" +def test_task_config_parameter_retention(): + """Test that config parameter is retained after Task initialization (Issue #3160).""" + config_dict = {"priority": "high", "timeout": 300} + + task = Task( + description="Test task with config", + expected_output="Test output", + config=config_dict + ) + + assert task.config is not None + assert task.config == config_dict + assert task.config.get("priority") == "high" + assert task.config.get("timeout") == 300 + + +def test_task_config_value_extraction(): + """Test that config values are still extracted to individual attributes.""" + task = Task( + description="Original description", + expected_output="Original output", + config={ + "description": "Config description", + "expected_output": "Config output", + "custom_field": "custom_value" + } + ) + + assert task.config is not None + assert task.description == "Config description" + assert task.expected_output == "Config output" + + +def test_task_config_prioritize_tasks_scenario(): + """Test the exact scenario from GitHub issue #3160.""" + def prioritize_tasks(tasks): + return sorted(tasks, key=lambda t: {"low": 2, "medium": 1, "high": 0}.get(t.config.get("priority", "medium"))) + + from crewai import Agent + + researcher = Agent( + role="Researcher", + goal="Find relevant facts", + backstory="An expert at gathering information quickly." + ) + + tasks = [ + Task( + description="Search for the author's biography", + expected_output="A summary of the author's background", + agent=researcher, + config={"priority": "high"} + ), + Task( + description="Check publication date", + expected_output="Date of first publication", + agent=researcher, + config={"priority": "low"} + ), + Task( + description="Extract book title", + expected_output="Title of the main book", + agent=researcher, + config={"priority": "medium"} + ) + ] + + ordered_tasks = prioritize_tasks(tasks) + + assert len(ordered_tasks) == 3 + assert ordered_tasks[0].config.get("priority") == "high" + assert ordered_tasks[1].config.get("priority") == "medium" + assert ordered_tasks[2].config.get("priority") == "low" + + +def test_task_config_none_and_empty(): + """Test edge cases with None and empty config.""" + task_none = Task( + description="Test task with None config", + expected_output="Test output", + config=None + ) + assert task_none.config is None + + task_empty = Task( + description="Test task with empty config", + expected_output="Test output", + config={} + ) + assert task_empty.config == {} + + +def test_task_callback_parameter_retention(): + """Test that callback parameter is retained after Task initialization.""" + def test_callback(output): + return "callback_executed" + + task = Task( + description="Test task with callback", + expected_output="Test output", + callback=test_callback + ) + + assert task.callback is not None + assert task.callback == test_callback + + @pytest.mark.vcr(filter_headers=["authorization"]) def test_task_execution_times(): researcher = Agent(