Skip to content

Commit eda146a

Browse files
committed
feat: support to set an empty context to the Task
1 parent 516d45d commit eda146a

File tree

5 files changed

+36
-14
lines changed

5 files changed

+36
-14
lines changed

src/crewai/crew.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
from crewai.tools.base_tool import BaseTool, Tool
5353
from crewai.types.usage_metrics import UsageMetrics
5454
from crewai.utilities import I18N, FileHandler, Logger, RPMController
55-
from crewai.utilities.constants import TRAINING_DATA_FILE
55+
from crewai.utilities.constants import EMPTY, TRAINING_DATA_FILE
5656
from crewai.utilities.evaluators.crew_evaluator_handler import CrewEvaluator
5757
from crewai.utilities.evaluators.task_evaluator import TaskEvaluator
5858
from crewai.utilities.events.crew_events import (
@@ -478,7 +478,7 @@ def validate_async_task_cannot_include_sequential_async_tasks_in_context(self):
478478
separated by a synchronous task.
479479
"""
480480
for i, task in enumerate(self.tasks):
481-
if task.async_execution and task.context:
481+
if task.async_execution and isinstance(task.context, list):
482482
for context_task in task.context:
483483
if context_task.async_execution:
484484
for j in range(i - 1, -1, -1):
@@ -496,7 +496,7 @@ def validate_context_no_future_tasks(self):
496496
task_indices = {id(task): i for i, task in enumerate(self.tasks)}
497497

498498
for task in self.tasks:
499-
if task.context:
499+
if isinstance(task.context, list):
500500
for context_task in task.context:
501501
if id(context_task) not in task_indices:
502502
continue # Skip context tasks not in the main tasks list
@@ -1034,11 +1034,14 @@ def _update_manager_tools(
10341034
)
10351035
return cast(List[BaseTool], tools)
10361036

1037-
def _get_context(self, task: Task, task_outputs: List[TaskOutput]):
1037+
def _get_context(self, task: Task, task_outputs: List[TaskOutput]) -> str:
1038+
if not task.context:
1039+
return ""
1040+
10381041
context = (
1039-
aggregate_raw_outputs_from_tasks(task.context)
1040-
if task.context
1041-
else aggregate_raw_outputs_from_task_outputs(task_outputs)
1042+
aggregate_raw_outputs_from_task_outputs(task_outputs)
1043+
if task.context is EMPTY
1044+
else aggregate_raw_outputs_from_tasks(task.context)
10421045
)
10431046
return context
10441047

@@ -1226,7 +1229,7 @@ def copy(self):
12261229
task_mapping[task.key] = cloned_task
12271230

12281231
for cloned_task, original_task in zip(cloned_tasks, self.tasks):
1229-
if original_task.context:
1232+
if isinstance(original_task.context, list):
12301233
cloned_context = [
12311234
task_mapping[context_task.key]
12321235
for context_task in original_task.context

src/crewai/task.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from crewai.tasks.task_output import TaskOutput
4242
from crewai.tools.base_tool import BaseTool
4343
from crewai.utilities.config import process_config
44+
from crewai.utilities.constants import EMPTY
4445
from crewai.utilities.converter import Converter, convert_to_model
4546
from crewai.utilities.events import (
4647
TaskCompletedEvent,
@@ -97,7 +98,7 @@ class Task(BaseModel):
9798
)
9899
context: Optional[List["Task"]] = Field(
99100
description="Other tasks that will have their output used as context for this task.",
100-
default=None,
101+
default=EMPTY,
101102
)
102103
async_execution: Optional[bool] = Field(
103104
description="Whether the task should be executed asynchronously or not.",
@@ -643,7 +644,7 @@ def copy(
643644

644645
cloned_context = (
645646
[task_mapping[context_task.key] for context_task in self.context]
646-
if self.context
647+
if isinstance(self.context, list)
647648
else None
648649
)
649650

src/crewai/telemetry/telemetry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def operation():
232232
"agent_key": task.agent.key if task.agent else None,
233233
"context": (
234234
[task.description for task in task.context]
235-
if task.context
235+
if isinstance(task.context, list)
236236
else None
237237
),
238238
"tools_names": [
@@ -748,7 +748,7 @@ def operation():
748748
"agent_key": task.agent.key if task.agent else None,
749749
"context": (
750750
[task.description for task in task.context]
751-
if task.context
751+
if isinstance(task.context, list)
752752
else None
753753
),
754754
"tools_names": [

src/crewai/utilities/constants.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,14 @@
55
MAX_LLM_RETRY = 3
66
MAX_FILE_NAME_LENGTH = 255
77
EMITTER_COLOR = "bold_blue"
8+
9+
10+
class _Empty:
11+
def __repr__(self):
12+
return "EMPTY"
13+
14+
15+
# Sentinel value used to detect when no value has been explicitly provided.
16+
# Unlike `None`, which might be a valid value from the user, `EMPTY` allows
17+
# us to distinguish between "not passed at all" and "explicitly passed None" or "[]".
18+
EMPTY = _Empty()

src/crewai/utilities/formatter.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import re
2-
from typing import TYPE_CHECKING, List
2+
from typing import TYPE_CHECKING, List, Type
3+
4+
from crewai.utilities.constants import EMPTY
35

46
if TYPE_CHECKING:
57
from crewai.task import Task
@@ -17,6 +19,11 @@ def aggregate_raw_outputs_from_task_outputs(task_outputs: List["TaskOutput"]) ->
1719

1820
def aggregate_raw_outputs_from_tasks(tasks: List["Task"]) -> str:
1921
"""Generate string context from the tasks."""
20-
task_outputs = [task.output for task in tasks if task.output is not None]
22+
23+
task_outputs = (
24+
[task.output for task in tasks if task.output is not None]
25+
if isinstance(tasks, list)
26+
else []
27+
)
2128

2229
return aggregate_raw_outputs_from_task_outputs(task_outputs)

0 commit comments

Comments
 (0)