Skip to content

Commit

Permalink
feat: Add planning feature to crew (#919)
Browse files Browse the repository at this point in the history
* feat: add planning feature to crew

* feat: add test to planning handler and change to execute_async method

* docs: add planning parameter to the Core documentation

* docs: add planning docs

* fix: fix type checking issue

* fix: test and logic
  • Loading branch information
pythonbyte authored Jul 18, 2024
1 parent 5b442e4 commit 61a1963
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 7 deletions.
5 changes: 3 additions & 2 deletions docs/core-concepts/Crews.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
These commands let you replay from your latest kickoff tasks, still retaining context from previously executed tasks.
119 changes: 119 additions & 0 deletions docs/core-concepts/Planning.md
Original file line number Diff line number Diff line change
@@ -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 '```'.

---
```
5 changes: 5 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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%">
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
17 changes: 17 additions & 0 deletions src/crewai/crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.",
Expand Down Expand Up @@ -453,6 +459,9 @@ def kickoff(

agent.create_agent_executor()

if self.planning:
self._handle_crew_planning()

metrics = []

if self.process == Process.sequential:
Expand Down Expand Up @@ -547,6 +556,14 @@ async def run_crew(crew, input_data):
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,
Expand Down
13 changes: 10 additions & 3 deletions src/crewai/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ def _execute_core(
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)

Expand Down Expand Up @@ -326,9 +328,14 @@ def get_agent_by_role(role: str) -> Union["BaseAgent", None]:

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(
Expand Down
64 changes: 64 additions & 0 deletions src/crewai/utilities/planning_handler.py
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 3 additions & 2 deletions tests/task_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
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():
Expand Down
68 changes: 68 additions & 0 deletions tests/utilities/test_planning_handler.py
Original file line number Diff line number Diff line change
@@ -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 ')

0 comments on commit 61a1963

Please sign in to comment.