|
import json |
|
import uuid |
|
from typing import Any, Dict, List, Optional, Union |
|
|
|
from pydantic import ( |
|
UUID4, |
|
BaseModel, |
|
ConfigDict, |
|
Field, |
|
InstanceOf, |
|
Json, |
|
field_validator, |
|
model_validator, |
|
) |
|
from pydantic_core import PydanticCustomError |
|
|
|
from crewai.agent import Agent |
|
from crewai.agents.cache import CacheHandler |
|
from crewai.process import Process |
|
from crewai.task import Task |
|
from crewai.tools.agent_tools import AgentTools |
|
|
|
|
|
class Crew(BaseModel): |
|
""" |
|
Represents a group of agents, defining how they should collaborate and the tasks they should perform. |
|
|
|
Attributes: |
|
tasks: List of tasks assigned to the crew. |
|
agents: List of agents part of this crew. |
|
process: The process flow that the crew will follow (e.g., sequential). |
|
verbose: Indicates the verbosity level for logging during execution. |
|
config: Configuration settings for the crew. |
|
cache_handler: Handles caching for the crew's operations. |
|
id: A unique identifier for the crew instance. |
|
""" |
|
|
|
__hash__ = object.__hash__ |
|
model_config = ConfigDict(arbitrary_types_allowed=True) |
|
tasks: List[Task] = Field(default_factory=list) |
|
agents: List[Agent] = Field(default_factory=list) |
|
process: Process = Field(default=Process.sequential) |
|
verbose: Union[int, bool] = Field(default=0) |
|
config: Optional[Union[Json, Dict[str, Any]]] = Field(default=None) |
|
cache_handler: Optional[InstanceOf[CacheHandler]] = Field(default=CacheHandler()) |
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True) |
|
|
|
@field_validator("id", mode="before") |
|
@classmethod |
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None: |
|
"""Prevent manual setting of the 'id' field by users.""" |
|
if v: |
|
raise PydanticCustomError( |
|
"may_not_set_field", "The 'id' field cannot be set by the user.", {} |
|
) |
|
|
|
@classmethod |
|
@field_validator("config", mode="before") |
|
def check_config_type(cls, v: Union[Json, Dict[str, Any]]): |
|
return json.loads(v) if isinstance(v, Json) else v |
|
|
|
@model_validator(mode="after") |
|
def check_config(self): |
|
"""Validates that the crew is properly configured with agents and tasks.""" |
|
if not self.config and not self.tasks and not self.agents: |
|
raise PydanticCustomError( |
|
"missing_keys", |
|
"Either 'agents' and 'tasks' need to be set or 'config'.", |
|
{}, |
|
) |
|
|
|
if self.config: |
|
self._setup_from_config() |
|
|
|
if self.agents: |
|
for agent in self.agents: |
|
agent.set_cache_handler(self.cache_handler) |
|
return self |
|
|
|
def _setup_from_config(self): |
|
"""Initializes agents and tasks from the provided config.""" |
|
if not self.config.get("agents") or not self.config.get("tasks"): |
|
raise PydanticCustomError( |
|
"missing_keys_in_config", "Config should have 'agents' and 'tasks'.", {} |
|
) |
|
|
|
self.agents = [Agent(**agent) for agent in self.config["agents"]] |
|
self.tasks = [self._create_task(task) for task in self.config["tasks"]] |
|
|
|
def _create_task(self, task_config): |
|
"""Creates a task instance from its configuration.""" |
|
task_agent = next( |
|
agt for agt in self.agents if agt.role == task_config["agent"] |
|
) |
|
del task_config["agent"] |
|
return Task(**task_config, agent=task_agent) |
|
|
|
def kickoff(self) -> str: |
|
"""Starts the crew to work on its assigned tasks.""" |
|
for agent in self.agents: |
|
agent.cache_handler = self.cache_handler |
|
|
|
if self.process == Process.sequential: |
|
return self._sequential_loop() |
|
|
|
def _sequential_loop(self) -> str: |
|
"""Executes tasks sequentially and returns the final output.""" |
|
task_output = None |
|
for task in self.tasks: |
|
self._prepare_and_execute_task(task) |
|
task_output = task.execute(task_output) |
|
self._log( |
|
"debug", f"\n\n[{task.agent.role}] Task output: {task_output}\n\n" |
|
) |
|
return task_output |
|
|
|
def _prepare_and_execute_task(self, task): |
|
"""Prepares and logs information about the task being executed.""" |
|
if task.agent.allow_delegation: |
|
task.tools += AgentTools(agents=self.agents).tools() |
|
|
|
self._log("debug", f"Working Agent: {task.agent.role}") |
|
self._log("info", f"Starting Task: {task.description}") |
|
|
|
def _log(self, level, message): |
|
"""Logs a message at the specified verbosity level.""" |
|
level_map = {"debug": 1, "info": 2} |
|
verbose_level = ( |
|
2 if isinstance(self.verbose, bool) and self.verbose else self.verbose |
|
) |
|
if verbose_level and level_map[level] <= verbose_level: |
|
print(message) |