Repository: crewAIInc/crewAI
Pull request #919 (2024-07-11 20:05:22)

feat: Add planning feature to crew
This PR aims to add the Plan and Execution ability to the Crew.

With Crew(planning=True) before the Crew runs an AgentPlanner will be responsible for creating a step-by-step plan for each task on the crew and that plan is added to each task description to guide and improve the output

joaomdmoura: @pythonbyte could you add docs and link them in `docs/index.md` and  `mkdocs.yml`?
Also jsut ot double check, dos this works on crews using the new yaml format?
pythonbyte: > @pythonbyte could you add docs and link them in `docs/index.md` and `mkdocs.yml`? Also jsut ot double check, dos this works on crews using the new yaml format?

Nice catch @joaomdmoura thanks for reminding me. 
Added the docs and tested them on yaml and worked nicely. 


Patch:
diff --git a/docs/core-concepts/Crews.md b/docs/core-concepts/Crews.md
index b33f33d9..5464f0f5 100644
--- a/docs/core-concepts/Crews.md
+++ b/docs/core-concepts/Crews.md
@@ -32,6 +32,7 @@ A crew in crewAI represents a collaborative group of agents working together to
 | **Manager Agent** _(optional)_        | `manager_agent`        | `manager` sets a custom agent that will be used as a manager.                                                                                                                                                                                             |
 | **Manager Callbacks** _(optional)_    | `manager_callbacks`    | `manager_callbacks` takes a list of callback handlers to be executed by the manager agent when a hierarchical process is used.                                                                                                                            |
 | **Prompt File** _(optional)_          | `prompt_file`          | Path to the prompt JSON file to be used for the crew.                                                                                                                                                                                                     |
+| **Planning** *(optional)*             | `planning`             |  Adds planning ability to the Crew. When activated before each Crew iteration, all Crew data is sent to an AgentPlanner that will plan the tasks and this plan will be added to each task description.
 
 !!! note "Crew Max RPM"
 The `max_rpm` attribute sets the maximum number of requests per minute the crew can perform to avoid rate limits and will override individual agents' `max_rpm` settings if you set it.
@@ -215,7 +216,7 @@ You can now replay from a specific task using our cli command replay.
 
 The replay_from_tasks feature in CrewAI allows you to replay from a specific task using the command-line interface (CLI). By running the command `crewai replay -t <task_id>`, you can specify the `task_id` for the replay process.
 
-Kickoffs will now save the latest kickoffs returned task outputs locally for you to be able to replay from. 
+Kickoffs will now save the latest kickoffs returned task outputs locally for you to be able to replay from.
 
 
 ### Replaying from specific task Using the CLI
@@ -236,4 +237,4 @@ crewai log-tasks-outputs
 crewai replay -t <task_id>
 ```
 
-These commands let you replay from your latest kickoff tasks, still retaining context from previously executed tasks.
\ No newline at end of file
+These commands let you replay from your latest kickoff tasks, still retaining context from previously executed tasks.
diff --git a/docs/core-concepts/Planning.md b/docs/core-concepts/Planning.md
new file mode 100644
index 00000000..81030970
--- /dev/null
+++ b/docs/core-concepts/Planning.md
@@ -0,0 +1,119 @@
+---
+title: crewAI Planning
+description: Learn how to add planning to your crewAI Crew and improve their performance.
+---
+
+## Introduction
+The planning feature in CrewAI allows you to add planning capability to your crew. When enabled, before each Crew iteration, all Crew information is sent to an AgentPlanner that will plan the tasks step by step, and this plan will be added to each task description.
+
+### Using the Planning Feature
+Getting started with the planning feature is very easy, the only step required is to add `planning=True` to your Crew:
+
+```python
+from crewai import Crew, Agent, Task, Process
+
+# Assemble your crew with planning capabilities
+my_crew = Crew(
+    agents=self.agents,
+    tasks=self.tasks,
+    process=Process.sequential,
+    planning=True,
+)
+```
+
+From this point on, your crew will have planning enabled, and the tasks will be planned before each iteration.
+
+### Example
+
+When running the base case example, you will see something like the following output, which represents the output of the AgentPlanner responsible for creating the step-by-step logic to add to the Agents tasks.
+
+```bash
+
+[2024-07-15 16:49:11][INFO]: Planning the crew execution
+**Step-by-Step Plan for Task Execution**
+
+**Task Number 1: Conduct a thorough research about AI LLMs**
+
+**Agent:** AI LLMs Senior Data Researcher
+
+**Agent Goal:** Uncover cutting-edge developments in AI LLMs
+
+**Task Expected Output:** A list with 10 bullet points of the most relevant information about AI LLMs
+
+**Task Tools:** None specified
+
+**Agent Tools:** None specified
+
+**Step-by-Step Plan:**
+
+1. **Define Research Scope:**
+   - Determine the specific areas of AI LLMs to focus on, such as advancements in architecture, use cases, ethical considerations, and performance metrics.
+
+2. **Identify Reliable Sources:**
+   - List reputable sources for AI research, including academic journals, industry reports, conferences (e.g., NeurIPS, ACL), AI research labs (e.g., OpenAI, Google AI), and online databases (e.g., IEEE Xplore, arXiv).
+
+3. **Collect Data:**
+   - Search for the latest papers, articles, and reports published in 2023 and early 2024.
+   - Use keywords like "Large Language Models 2024", "AI LLM advancements", "AI ethics 2024", etc.
+
+4. **Analyze Findings:**
+   - Read and summarize the key points from each source.
+   - Highlight new techniques, models, and applications introduced in the past year.
+
+5. **Organize Information:**
+   - Categorize the information into relevant topics (e.g., new architectures, ethical implications, real-world applications).
+   - Ensure each bullet point is concise but informative.
+
+6. **Create the List:**
+   - Compile the 10 most relevant pieces of information into a bullet point list.
+   - Review the list to ensure clarity and relevance.
+
+**Expected Output:**
+A list with 10 bullet points of the most relevant information about AI LLMs.
+
+---
+
+**Task Number 2: Review the context you got and expand each topic into a full section for a report**
+
+**Agent:** AI LLMs Reporting Analyst
+
+**Agent Goal:** Create detailed reports based on AI LLMs data analysis and research findings
+
+**Task Expected Output:** A fully fledge report with the main topics, each with a full section of information. Formatted as markdown without '```'
+
+**Task Tools:** None specified
+
+**Agent Tools:** None specified
+
+**Step-by-Step Plan:**
+
+1. **Review the Bullet Points:**
+   - Carefully read through the list of 10 bullet points provided by the AI LLMs Senior Data Researcher.
+
+2. **Outline the Report:**
+   - Create an outline with each bullet point as a main section heading.
+   - Plan sub-sections under each main heading to cover different aspects of the topic.
+
+3. **Research Further Details:**
+   - For each bullet point, conduct additional research if necessary to gather more detailed information.
+   - Look for case studies, examples, and statistical data to support each section.
+
+4. **Write Detailed Sections:**
+   - Expand each bullet point into a comprehensive section.
+   - Ensure each section includes an introduction, detailed explanation, examples, and a conclusion.
+   - Use markdown formatting for headings, subheadings, lists, and emphasis.
+
+5. **Review and Edit:**
+   - Proofread the report for clarity, coherence, and correctness.
+   - Make sure the report flows logically from one section to the next.
+   - Format the report according to markdown standards.
+
+6. **Finalize the Report:**
+   - Ensure the report is complete with all sections expanded and detailed.
+   - Double-check formatting and make any necessary adjustments.
+
+**Expected Output:**
+A fully-fledged report with the main topics, each with a full section of information. Formatted as markdown without '```'.
+
+---
+```
diff --git a/docs/index.md b/docs/index.md
index b21c2068..77cdd985 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -43,6 +43,11 @@ Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By
                     Memory
                 </a>
             </li>
+            <li>
+                <a href="./core-concepts/Planning">
+                    Planning
+                </a>
+            </li>
         </ul>
     </div>
     <div style="width:30%">
diff --git a/mkdocs.yml b/mkdocs.yml
index 6e8cae1c..7caac90b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -128,6 +128,7 @@ nav:
     - Collaboration: 'core-concepts/Collaboration.md'
     - Training: 'core-concepts/Training-Crew.md'
     - Memory: 'core-concepts/Memory.md'
+    - Planning: 'core-concepts/Planning.md'
     - Using LangChain Tools: 'core-concepts/Using-LangChain-Tools.md'
     - Using LlamaIndex Tools: 'core-concepts/Using-LlamaIndex-Tools.md'
   - How to Guides:
diff --git a/src/crewai/crew.py b/src/crewai/crew.py
index 29f0ea49..225083ab 100644
--- a/src/crewai/crew.py
+++ b/src/crewai/crew.py
@@ -42,6 +42,7 @@ from crewai.utilities.formatter import (
     aggregate_raw_outputs_from_task_outputs,
     aggregate_raw_outputs_from_tasks,
 )
+from crewai.utilities.planning_handler import CrewPlanner
 from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
 from crewai.utilities.training_handler import CrewTrainingHandler
 
@@ -73,6 +74,7 @@ class Crew(BaseModel):
         task_callback: Callback to be executed after each task for every agents execution.
         step_callback: Callback to be executed after each step for every agents execution.
         share_crew: Whether you want to share the complete crew information and execution with crewAI to make the library better, and allow us to train models.
+        planning: Plan the crew execution and add the plan to the crew.
     """
 
     __hash__ = object.__hash__  # type: ignore
@@ -148,6 +150,10 @@ class Crew(BaseModel):
         default=False,
         description="output_log_file",
     )
+    planning: Optional[bool] = Field(
+        default=False,
+        description="Plan the crew execution and add the plan to the crew.",
+    )
     task_execution_output_json_files: Optional[List[str]] = Field(
         default=None,
         description="List of file paths for task execution JSON files.",
@@ -453,6 +459,9 @@ class Crew(BaseModel):
 
             agent.create_agent_executor()
 
+        if self.planning:
+            self._handle_crew_planning()
+
         metrics = []
 
         if self.process == Process.sequential:
@@ -547,6 +556,14 @@ class Crew(BaseModel):
         self._task_output_handler.reset()
         return results
 
+    def _handle_crew_planning(self):
+        """Handles the Crew planning."""
+        self._logger.log("info", "Planning the crew execution")
+        result = CrewPlanner(self.tasks)._handle_crew_planning()
+
+        for task, step_plan in zip(self.tasks, result.list_of_plans_per_task):
+            task.description += step_plan
+
     def _store_execution_log(
         self,
         task: Task,
diff --git a/src/crewai/task.py b/src/crewai/task.py
index 57a788f4..9e98adb1 100644
--- a/src/crewai/task.py
+++ b/src/crewai/task.py
@@ -254,7 +254,9 @@ class Task(BaseModel):
             content = (
                 json_output
                 if json_output
-                else pydantic_output.model_dump_json() if pydantic_output else result
+                else pydantic_output.model_dump_json()
+                if pydantic_output
+                else result
             )
             self._save_file(content)
 
@@ -326,9 +328,14 @@ class Task(BaseModel):
 
     def _create_converter(self, *args, **kwargs) -> Converter:
         """Create a converter instance."""
-        converter = self.agent.get_output_converter(*args, **kwargs)
-        if self.converter_cls:
+        if self.agent and not self.converter_cls:
+            converter = self.agent.get_output_converter(*args, **kwargs)
+        elif self.converter_cls:
             converter = self.converter_cls(*args, **kwargs)
+
+        if not converter:
+            raise Exception("No output converter found or set.")
+
         return converter
 
     def _export_output(
diff --git a/src/crewai/utilities/planning_handler.py b/src/crewai/utilities/planning_handler.py
new file mode 100644
index 00000000..2c5cd50a
--- /dev/null
+++ b/src/crewai/utilities/planning_handler.py
@@ -0,0 +1,64 @@
+from typing import List
+
+from pydantic import BaseModel
+
+from crewai.agent import Agent
+from crewai.task import Task
+
+
+class PlannerTaskPydanticOutput(BaseModel):
+    list_of_plans_per_task: List[str]
+
+
+class CrewPlanner:
+    def __init__(self, tasks: List[Task]):
+        self.tasks = tasks
+
+    def _handle_crew_planning(self):
+        """Handles the Crew planning by creating detailed step-by-step plans for each task."""
+        planning_agent = self._create_planning_agent()
+        tasks_summary = self._create_tasks_summary()
+
+        planner_task = self._create_planner_task(planning_agent, tasks_summary)
+
+        return planner_task.execute_sync()
+
+    def _create_planning_agent(self) -> Agent:
+        """Creates the planning agent for the crew planning."""
+        return Agent(
+            role="Task Execution Planner",
+            goal=(
+                "Your goal is to create an extremely detailed, step-by-step plan based on the tasks and tools "
+                "available to each agent so that they can perform the tasks in an exemplary manner"
+            ),
+            backstory="Planner agent for crew planning",
+        )
+
+    def _create_planner_task(self, planning_agent: Agent, tasks_summary: str) -> Task:
+        """Creates the planner task using the given agent and tasks summary."""
+        return Task(
+            description=(
+                f"Based on these tasks summary: {tasks_summary} \n Create the most descriptive plan based on the tasks "
+                "descriptions, tools available, and agents' goals for them to execute their goals with perfection."
+            ),
+            expected_output="Step by step plan on how the agents can execute their tasks using the available tools with mastery",
+            agent=planning_agent,
+            output_pydantic=PlannerTaskPydanticOutput,
+        )
+
+    def _create_tasks_summary(self) -> str:
+        """Creates a summary of all tasks."""
+        tasks_summary = []
+        for idx, task in enumerate(self.tasks):
+            tasks_summary.append(
+                f"""
+                Task Number {idx + 1} - {task.description}
+                "task_description": {task.description}
+                "task_expected_output": {task.expected_output}
+                "agent": {task.agent.role if task.agent else "None"}
+                "agent_goal": {task.agent.goal if task.agent else "None"}
+                "task_tools": {task.tools}
+                "agent_tools": {task.agent.tools if task.agent else "None"}
+                """
+            )
+        return " ".join(tasks_summary)


Test patch:
diff --git a/tests/task_test.py b/tests/task_test.py
index 412e42dc..6458ea35 100644
--- a/tests/task_test.py
+++ b/tests/task_test.py
@@ -5,12 +5,13 @@ import json
 from unittest.mock import MagicMock, patch
 
 import pytest
+from pydantic import BaseModel
+from pydantic_core import ValidationError
+
 from crewai import Agent, Crew, Process, Task
 from crewai.tasks.conditional_task import ConditionalTask
 from crewai.tasks.task_output import TaskOutput
 from crewai.utilities.converter import Converter
-from pydantic import BaseModel
-from pydantic_core import ValidationError
 
 
 def test_task_tool_reflect_agent_tools():
diff --git a/tests/utilities/test_planning_handler.py b/tests/utilities/test_planning_handler.py
new file mode 100644
index 00000000..c76879d2
--- /dev/null
+++ b/tests/utilities/test_planning_handler.py
@@ -0,0 +1,68 @@
+from unittest.mock import patch
+
+import pytest
+
+from crewai.agent import Agent
+from crewai.task import Task
+from crewai.utilities.planning_handler import CrewPlanner, PlannerTaskPydanticOutput
+
+
+class TestCrewPlanner:
+    @pytest.fixture
+    def crew_planner(self):
+        tasks = [
+            Task(
+                description="Task 1",
+                expected_output="Output 1",
+                agent=Agent(role="Agent 1", goal="Goal 1", backstory="Backstory 1"),
+            ),
+            Task(
+                description="Task 2",
+                expected_output="Output 2",
+                agent=Agent(role="Agent 2", goal="Goal 2", backstory="Backstory 2"),
+            ),
+            Task(
+                description="Task 3",
+                expected_output="Output 3",
+                agent=Agent(role="Agent 3", goal="Goal 3", backstory="Backstory 3"),
+            ),
+        ]
+        return CrewPlanner(tasks)
+
+    def test_handle_crew_planning(self, crew_planner):
+        with patch.object(Task, "execute_sync") as execute:
+            execute.return_value = PlannerTaskPydanticOutput(
+                list_of_plans_per_task=["Plan 1", "Plan 2", "Plan 3"]
+            )
+            result = crew_planner._handle_crew_planning()
+            assert isinstance(result, PlannerTaskPydanticOutput)
+            assert len(result.list_of_plans_per_task) == len(crew_planner.tasks)
+            execute.assert_called_once()
+
+    def test_create_planning_agent(self, crew_planner):
+        agent = crew_planner._create_planning_agent()
+        assert isinstance(agent, Agent)
+        assert agent.role == "Task Execution Planner"
+
+    def test_create_planner_task(self, crew_planner):
+        planning_agent = Agent(
+            role="Planning Agent",
+            goal="Plan Step by Step Plan",
+            backstory="Master in Planning",
+        )
+        tasks_summary = "Summary of tasks"
+        task = crew_planner._create_planner_task(planning_agent, tasks_summary)
+
+        assert isinstance(task, Task)
+        assert task.description.startswith("Based on these tasks summary")
+        assert task.agent == planning_agent
+        assert (
+            task.expected_output
+            == "Step by step plan on how the agents can execute their tasks using the available tools with mastery"
+        )
+
+    def test_create_tasks_summary(self, crew_planner):
+        tasks_summary = crew_planner._create_tasks_summary()
+        assert isinstance(tasks_summary, str)
+        assert tasks_summary.startswith("\n                Task Number 1 - Task 1")
+        assert tasks_summary.endswith('"agent_tools": []\n                ')