Repository: crewAIInc/crewAI
Pull request #1199 (2024-08-15 14:30:26)

[CRE-170] Add name and expected_output to TaskOutput
This commit adds task information to the TaskOutput class. This is useful to provide extra context to callbacks.

linear[bot]: <p><a href="https://linear.app/crewai/issue/CRE-170/webhooks-return-task-object">CRE-170 Webhooks return task object</a></p>

Patch:
diff --git a/.github/workflows/security-checker.yml b/.github/workflows/security-checker.yml
new file mode 100644
index 00000000..d0d309b4
--- /dev/null
+++ b/.github/workflows/security-checker.yml
@@ -0,0 +1,23 @@
+name: Security Checker
+
+on: [pull_request]
+
+jobs:
+  security-check:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: "3.11.9"
+
+      - name: Install dependencies
+        run: pip install bandit
+
+      - name: Run Bandit
+        run: bandit -c pyproject.toml -r src/ -lll
+
diff --git a/pyproject.toml b/pyproject.toml
index c0239bcb..e438f657 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -62,6 +62,9 @@ ignore_missing_imports = true
 disable_error_code = 'import-untyped'
 exclude = ["cli/templates"]
 
+[tool.bandit]
+exclude_dirs = ["src/crewai/cli/templates"]
+
 [build-system]
 requires = ["poetry-core"]
 build-backend = "poetry.core.masonry.api"
diff --git a/src/crewai/agents/agent_builder/base_agent.py b/src/crewai/agents/agent_builder/base_agent.py
index 8604ed08..22790827 100644
--- a/src/crewai/agents/agent_builder/base_agent.py
+++ b/src/crewai/agents/agent_builder/base_agent.py
@@ -170,7 +170,7 @@ class BaseAgent(ABC, BaseModel):
     @property
     def key(self):
         source = [self.role, self.goal, self.backstory]
-        return md5("|".join(source).encode()).hexdigest()
+        return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
 
     @abstractmethod
     def execute_task(
diff --git a/src/crewai/crew.py b/src/crewai/crew.py
index 5f58e312..8125f025 100644
--- a/src/crewai/crew.py
+++ b/src/crewai/crew.py
@@ -363,7 +363,7 @@ class Crew(BaseModel):
         source = [agent.key for agent in self.agents] + [
             task.key for task in self.tasks
         ]
-        return md5("|".join(source).encode()).hexdigest()
+        return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
 
     def _setup_from_config(self):
         assert self.config is not None, "Config should not be None."
diff --git a/src/crewai/project/annotations.py b/src/crewai/project/annotations.py
index 6b2c64fb..030341c3 100644
--- a/src/crewai/project/annotations.py
+++ b/src/crewai/project/annotations.py
@@ -6,12 +6,20 @@ def task(func):
         task.registration_order = []
 
     func.is_task = True
-    wrapped_func = memoize(func)
+    memoized_func = memoize(func)
 
     # Append the function name to the registration order list
     task.registration_order.append(func.__name__)
 
-    return wrapped_func
+    def wrapper(*args, **kwargs):
+        result = memoized_func(*args, **kwargs)
+
+        if not result.name:
+            result.name = func.__name__
+
+        return result
+
+    return wrapper
 
 
 def agent(func):
diff --git a/src/crewai/task.py b/src/crewai/task.py
index 8efaee5f..3c8987b6 100644
--- a/src/crewai/task.py
+++ b/src/crewai/task.py
@@ -185,7 +185,7 @@ class Task(BaseModel):
         expected_output = self._original_expected_output or self.expected_output
         source = [description, expected_output]
 
-        return md5("|".join(source).encode()).hexdigest()
+        return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
 
     def execute_async(
         self,
@@ -240,7 +240,9 @@ class Task(BaseModel):
         pydantic_output, json_output = self._export_output(result)
 
         task_output = TaskOutput(
+            name=self.name,
             description=self.description,
+            expected_output=self.expected_output,
             raw=result,
             pydantic=pydantic_output,
             json_dict=json_output,
diff --git a/src/crewai/tasks/task_output.py b/src/crewai/tasks/task_output.py
index 604dab6e..b0e8aecd 100644
--- a/src/crewai/tasks/task_output.py
+++ b/src/crewai/tasks/task_output.py
@@ -10,6 +10,10 @@ class TaskOutput(BaseModel):
     """Class that represents the result of a task."""
 
     description: str = Field(description="Description of the task")
+    name: Optional[str] = Field(description="Name of the task", default=None)
+    expected_output: Optional[str] = Field(
+        description="Expected output of the task", default=None
+    )
     summary: Optional[str] = Field(description="Summary of the task", default=None)
     raw: str = Field(description="Raw output of the task", default="")
     pydantic: Optional[BaseModel] = Field(


Test patch:
diff --git a/tests/project_test.py b/tests/project_test.py
index d487bdb3..188ba024 100644
--- a/tests/project_test.py
+++ b/tests/project_test.py
@@ -14,6 +14,14 @@ class SimpleCrew:
     def simple_task(self):
         return Task(description="Simple Description", expected_output="Simple Output")
 
+    @task
+    def custom_named_task(self):
+        return Task(
+            description="Simple Description",
+            expected_output="Simple Output",
+            name="Custom",
+        )
+
 
 def test_agent_memoization():
     crew = SimpleCrew()
@@ -33,3 +41,15 @@ def test_task_memoization():
     assert (
         first_call_result is second_call_result
     ), "Task memoization is not working as expected"
+
+
+def test_task_name():
+    simple_task = SimpleCrew().simple_task()
+    assert (
+        simple_task.name == "simple_task"
+    ), "Task name is not inferred from function name as expected"
+
+    custom_named_task = SimpleCrew().custom_named_task()
+    assert (
+        custom_named_task.name == "Custom"
+    ), "Custom task name is not being set as expected"
diff --git a/tests/task_test.py b/tests/task_test.py
index 5bfdaf4a..e4ee23e9 100644
--- a/tests/task_test.py
+++ b/tests/task_test.py
@@ -98,6 +98,7 @@ def test_task_callback():
     task_completed = MagicMock(return_value="done")
 
     task = Task(
+        name="Brainstorm",
         description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
         expected_output="Bullet point list of 5 interesting ideas.",
         agent=researcher,
@@ -109,6 +110,10 @@ def test_task_callback():
         task.execute_sync(agent=researcher)
         task_completed.assert_called_once_with(task.output)
 
+        assert task.output.description == task.description
+        assert task.output.expected_output == task.expected_output
+        assert task.output.name == task.name
+
 
 def test_task_callback_returns_task_output():
     from crewai.tasks.output_format import OutputFormat
@@ -149,6 +154,8 @@ def test_task_callback_returns_task_output():
             "json_dict": None,
             "agent": researcher.role,
             "summary": "Give me a list of 5 interesting ideas to explore...",
+            "name": None,
+            "expected_output": "Bullet point list of 5 interesting ideas.",
             "output_format": OutputFormat.RAW,
         }
         assert output_dict == expected_output