mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-10-19 08:25:09 +02:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
835f0065f1 | ||
|
|
5865a2b899 | ||
|
|
9629337f17 | ||
|
|
2186f5c968 | ||
|
|
d34c2a2672 | ||
|
|
2520f389f2 | ||
|
|
a958b31768 | ||
|
|
5de494c99b | ||
|
|
208c3a780c | ||
|
|
1e112fa50a |
No files matched your search
@@ -19,14 +19,13 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install Requirements
|
||||
run: |
|
||||
sudo apt-get update &&
|
||||
pip install poetry &&
|
||||
pip install poetry
|
||||
poetry lock &&
|
||||
poetry install
|
||||
|
||||
- name: Run tests
|
||||
run: poetry run pytest
|
||||
run: poetry run pytest tests
|
||||
@@ -2,6 +2,8 @@ repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.4
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
args: ["--fix"]
|
||||
exclude: "templates"
|
||||
- id: ruff-format
|
||||
exclude: "templates"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "crewai"
|
||||
version = "0.30.10"
|
||||
version = "0.30.11"
|
||||
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
|
||||
authors = ["Joao Moura <joao@crewai.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import click
|
||||
import pkg_resources
|
||||
|
||||
from .create_crew import create_crew
|
||||
from .train_crew import train_crew
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -15,5 +17,36 @@ def create(project_name):
|
||||
create_crew(project_name)
|
||||
|
||||
|
||||
@crewai.command()
|
||||
@click.option(
|
||||
"--tools", is_flag=True, help="Show the installed version of crewai tools"
|
||||
)
|
||||
def version(tools):
|
||||
"""Show the installed version of crewai."""
|
||||
crewai_version = pkg_resources.get_distribution("crewai").version
|
||||
click.echo(f"crewai version: {crewai_version}")
|
||||
|
||||
if tools:
|
||||
try:
|
||||
tools_version = pkg_resources.get_distribution("crewai-tools").version
|
||||
click.echo(f"crewai tools version: {tools_version}")
|
||||
except pkg_resources.DistributionNotFound:
|
||||
click.echo("crewai tools not installed")
|
||||
|
||||
|
||||
@crewai.command()
|
||||
@click.option(
|
||||
"-n",
|
||||
"--n_iterations",
|
||||
type=int,
|
||||
default=5,
|
||||
help="Number of iterations to train the crew",
|
||||
)
|
||||
def train(n_iterations: int):
|
||||
"""Train the crew."""
|
||||
click.echo(f"Training the crew for {n_iterations} iterations")
|
||||
train_crew(n_iterations)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
crewai()
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
from {{folder_name}}.crew import {{crew_name}}Crew
|
||||
|
||||
|
||||
@@ -7,4 +8,15 @@ def run():
|
||||
inputs = {
|
||||
'topic': 'AI LLMs'
|
||||
}
|
||||
{{crew_name}}Crew().crew().kickoff(inputs=inputs)
|
||||
{{crew_name}}Crew().crew().kickoff(inputs=inputs)
|
||||
|
||||
|
||||
def train():
|
||||
"""
|
||||
Train the crew for a given number of iterations.
|
||||
"""
|
||||
try:
|
||||
{{crew_name}}Crew().crew().train(n_iterations=int(sys.argv[1]))
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"An error occurred while training the crew: {e}")
|
||||
@@ -6,11 +6,12 @@ authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10,<=3.13"
|
||||
crewai = {extras = ["tools"], version = "^0.30.9"}
|
||||
crewai = { extras = ["tools"], version = "^0.30.11" }
|
||||
|
||||
[tool.poetry.scripts]
|
||||
{{folder_name}} = "{{folder_name}}.main:run"
|
||||
train = "{{folder_name}}.main:train"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -0,0 +1,29 @@
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def train_crew(n_iterations: int) -> None:
|
||||
"""
|
||||
Train the crew by running a command in the Poetry environment.
|
||||
|
||||
Args:
|
||||
n_iterations (int): The number of iterations to train the crew.
|
||||
"""
|
||||
command = ["poetry", "run", "train", str(n_iterations)]
|
||||
|
||||
try:
|
||||
if n_iterations <= 0:
|
||||
raise ValueError("The number of iterations must be a positive integer.")
|
||||
|
||||
result = subprocess.run(command, capture_output=False, text=True, check=True)
|
||||
|
||||
if result.stderr:
|
||||
click.echo(result.stderr, err=True)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
click.echo(f"An error occurred while training the crew: {e}", err=True)
|
||||
click.echo(e.output, err=True)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"An unexpected error occurred: {e}", err=True)
|
||||
+8
-72
@@ -1,9 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
import uuid
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.callbacks import BaseCallbackHandler
|
||||
from pydantic import (
|
||||
UUID4,
|
||||
@@ -29,6 +27,7 @@ from crewai.telemetry import Telemetry
|
||||
from crewai.tools.agent_tools import AgentTools
|
||||
from crewai.utilities import I18N, FileHandler, Logger, RPMController
|
||||
|
||||
|
||||
class Crew(BaseModel):
|
||||
"""
|
||||
Represents a group of agents, defining how they should collaborate and the tasks they should perform.
|
||||
@@ -102,15 +101,6 @@ class Crew(BaseModel):
|
||||
config: Optional[Union[Json, Dict[str, Any]]] = Field(default=None)
|
||||
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
||||
share_crew: Optional[bool] = Field(default=False)
|
||||
autonomous_llm: Optional[Any] = Field(
|
||||
default_factory=lambda: ChatOpenAI(
|
||||
model=os.environ.get("OPENAI_MODEL_NAME", "gpt-4")
|
||||
),
|
||||
description="Language model that will used for agents dinamycally created.",
|
||||
)
|
||||
autonomous_tools: Optional[List[Any]] = Field(
|
||||
default_factory=list, description="Tools at agents disposal when dinamically generated."
|
||||
)
|
||||
step_callback: Optional[Any] = Field(
|
||||
default=None,
|
||||
description="Callback to be executed after each step for all agents execution.",
|
||||
@@ -174,7 +164,9 @@ class Crew(BaseModel):
|
||||
"""Set private attributes."""
|
||||
if self.memory:
|
||||
self._long_term_memory = LongTermMemory()
|
||||
self._short_term_memory = ShortTermMemory(crew=self, embedder_config=self.embedder)
|
||||
self._short_term_memory = ShortTermMemory(
|
||||
crew=self, embedder_config=self.embedder
|
||||
)
|
||||
self._entity_memory = EntityMemory(crew=self, embedder_config=self.embedder)
|
||||
return self
|
||||
|
||||
@@ -276,10 +268,6 @@ class Crew(BaseModel):
|
||||
result, manager_metrics = self._run_hierarchical_process() # type: ignore # Unpacking a string is disallowed
|
||||
metrics.append(manager_metrics) # type: ignore # Cannot determine type of "manager_metrics"
|
||||
|
||||
result, manager_metrics = self._run_hierarchical_process()
|
||||
metrics.append(manager_metrics)
|
||||
elif self.process == Process.autonomous:
|
||||
result = self._run_autonomous_process()
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"The process '{self.process}' is not implemented yet."
|
||||
@@ -294,6 +282,10 @@ class Crew(BaseModel):
|
||||
|
||||
return result
|
||||
|
||||
def train(self, n_iterations: int) -> None:
|
||||
# TODO: Implement training
|
||||
pass
|
||||
|
||||
def _run_sequential_process(self) -> str:
|
||||
"""Executes tasks sequentially and returns the final output."""
|
||||
task_output = ""
|
||||
@@ -329,61 +321,6 @@ class Crew(BaseModel):
|
||||
self._finish_execution(task_output)
|
||||
return self._format_output(task_output)
|
||||
|
||||
def _run_autonomous_process(self) -> str:
|
||||
"""Executes high level tasks by automatically creating agents and tasks for achieving an initial task"""
|
||||
from crewai.internal.crew.planning_crew.crew import PlanningCrewCrew
|
||||
#task_output = ""
|
||||
# Need to decide how to break the initial task into smaller tasks
|
||||
# Need to decide on what agents to create to fullfill the tasks
|
||||
# Need to decide what process to use, whether sequential or hierarchical
|
||||
# Need to decide on what tools to use for the agents
|
||||
|
||||
import pkgutil
|
||||
import inspect
|
||||
import crewai_tools
|
||||
|
||||
def list_crewai_tools():
|
||||
tool_list = []
|
||||
for importer, modname, ispkg in pkgutil.iter_modules(crewai_tools.__path__):
|
||||
module = importer.find_module(modname).load_module(modname)
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
if obj.__module__ == module.__name__:
|
||||
tool_list.append(name)
|
||||
return tool_list
|
||||
|
||||
# Get the list of tools
|
||||
tools = list_crewai_tools()
|
||||
|
||||
descriptions = []
|
||||
for tool in tools:
|
||||
args = {
|
||||
k: {k2: v2 for k2, v2 in v.items() if k2 in ["description", "type"]}
|
||||
for k, v in tool.args.items()
|
||||
}
|
||||
descriptions.append(
|
||||
"\n".join(
|
||||
[
|
||||
f"Tool Name: {tool.name.lower()}",
|
||||
f"Tool Description: {tool.description}",
|
||||
f"Tool Arguments: {args}",
|
||||
]
|
||||
)
|
||||
)
|
||||
descriptions = "\n--\n".join(descriptions)
|
||||
print(descriptions)
|
||||
|
||||
crew = PlanningCrewCrew().crew()
|
||||
|
||||
for task in self.tasks:
|
||||
crew.kickoff({
|
||||
"task": task.description,
|
||||
"goal": task.expected_output,
|
||||
"tools_list": descriptions
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
def _run_hierarchical_process(self) -> str:
|
||||
"""Creates and assigns a manager agent to make sure the crew completes the tasks."""
|
||||
|
||||
@@ -433,7 +370,6 @@ class Crew(BaseModel):
|
||||
for task in self.tasks:
|
||||
if not task.callback:
|
||||
task.callback = self.task_callback
|
||||
task.callback = self.task_callback
|
||||
|
||||
def _interpolate_inputs(self, inputs: Dict[str, Any]) -> None:
|
||||
"""Interpolates the inputs in the tasks and agents."""
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
from crewai.agent import Agent
|
||||
from crewai.tools.agent_tools import AgentTools
|
||||
from crewai.utilities import I18N
|
||||
|
||||
class HierarchicalManagerAgent:
|
||||
def __init__(self, llm, agents, verbose):
|
||||
i18n = I18N()
|
||||
self.agent = Agent(
|
||||
role=i18n.retrieve("hierarchical_manager_agent", "role"),
|
||||
goal=i18n.retrieve("hierarchical_manager_agent", "goal"),
|
||||
backstory=i18n.retrieve("hierarchical_manager_agent", "backstory"),
|
||||
tools=AgentTools(agents=agents).tools(),
|
||||
llm=llm,
|
||||
verbose=verbose,
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
from crewai.agent import Agent
|
||||
from crewai.utilities import i18n
|
||||
|
||||
class PlanningManagerAgent:
|
||||
def __init__(self, llm, verbose):
|
||||
self.agent = Agent(
|
||||
role=i18n.retrieve("planning_manager_agent", "role"),
|
||||
goal=i18n.retrieve("planning_manager_agent", "goal"),
|
||||
backstory=i18n.retrieve("planning_manager_agent", "backstory"),
|
||||
verbose=verbose,
|
||||
llm=llm,
|
||||
)
|
||||
@@ -1,21 +0,0 @@
|
||||
project_manager:
|
||||
role: >
|
||||
High-Level Task Decomposer
|
||||
goal: >
|
||||
Efficiently break down high-level tasks into actionable subtasks.
|
||||
backstory: >
|
||||
As a visionary leader and strategist, you excel at dissecting complex
|
||||
projects into manageable parts. Your expertise lies in identifying key
|
||||
components and stages of a project, ensuring that each piece is addressed
|
||||
with precision and aligned with overarching objectives.
|
||||
|
||||
resource_manager:
|
||||
role: >
|
||||
Resource Allocation Specialist
|
||||
goal: >
|
||||
Assign the right resources (agents and tools) to the generated subtasks.
|
||||
backstory: >
|
||||
With a strategic mind and a masterful grasp of logistics, you specialize
|
||||
in allocating the best-suited agents and tools for specific tasks. Your
|
||||
ability to match project needs with available resources ensures optimal
|
||||
efficiency and effectiveness in project execution.
|
||||
@@ -1,36 +0,0 @@
|
||||
task_decomposition:
|
||||
description: >
|
||||
Analyze the high-level task, "{task}", provided and break it down into
|
||||
distinct, manageable subtasks that are specific and actionable.
|
||||
Consider all aspects of the task and ensure that each subtask is
|
||||
aligned with the final goal. Additionally, outline the roles, goals,
|
||||
and backstories for the agents you plan to recruit for performing
|
||||
each subtask. Make sure to clearly define their responsibilities in
|
||||
the context of this project.
|
||||
expected_output: >
|
||||
A detailed list of subtasks, each with defined objectives and
|
||||
scopes, ensuring they collectively address all components of the
|
||||
original task, "{task}". Each subtask should be assigned to a
|
||||
specific agent you will recruit, complete with their role, goal, and
|
||||
backstory. The main goal is to ensure systematic progression and
|
||||
effective task management.
|
||||
|
||||
resource_allocation:
|
||||
description: >
|
||||
Evaluate the list of subtasks generated from the high-level task
|
||||
"{task}" and determine the most appropriate agents and tools needed
|
||||
for each. Assign resources based on the complexity, required skills,
|
||||
and the tools needed to effectively complete each subtask. This
|
||||
evaluation should consider the unique requirements of each subtask
|
||||
and align resources to optimize task completion. Available tools to
|
||||
choose from include:
|
||||
{tools_list}.
|
||||
expected_output: >
|
||||
An allocation plan that lists each subtask along with the assigned
|
||||
agent and the tools they will use, explaining the rationale for each
|
||||
resource assignment to ensure transparency and optimal task
|
||||
alignment. The expected outcome is to have a fully resourced plan
|
||||
that facilitates efficient task execution and achievement of the
|
||||
main goal: "{goal}".
|
||||
Ensure each tool selected is from the provided list of available tools:
|
||||
{tools_list}.
|
||||
@@ -1,51 +0,0 @@
|
||||
from .... import Agent, Crew, Process, Task
|
||||
from ....project import CrewBase, agent, crew, task
|
||||
|
||||
from .crew_config import CrewConfig
|
||||
|
||||
@CrewBase
|
||||
class PlanningCrewCrew():
|
||||
"""PlanningCrew crew"""
|
||||
agents_config = '../internal/crew/planning_crew/config/agents.yaml'
|
||||
tasks_config = '../internal/crew/planning_crew/config/tasks.yaml'
|
||||
|
||||
@agent
|
||||
def project_manager(self) -> Agent:
|
||||
return Agent(
|
||||
config=self.agents_config['project_manager'],
|
||||
allow_delegation=False,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
@agent
|
||||
def resource_manager(self) -> Agent:
|
||||
return Agent(
|
||||
config=self.agents_config['resource_manager'],
|
||||
allow_delegation=False,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
@task
|
||||
def task_decomposition(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config['task_decomposition'],
|
||||
agent=self.project_manager(),
|
||||
output_pydantic=CrewConfig
|
||||
)
|
||||
|
||||
@task
|
||||
def resource_allocation(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config['resource_allocation'],
|
||||
agent=self.resource_manager(),
|
||||
output_pydantic=CrewConfig
|
||||
)
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
"""Creates the PlanningCrew crew"""
|
||||
return Crew(
|
||||
agents=[self.project_manager(), self.resource_manager()],
|
||||
tasks=[self.task_decomposition(), self.resource_allocation()],
|
||||
process=Process.sequential
|
||||
)
|
||||
@@ -1,17 +0,0 @@
|
||||
from typing import List
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AgentConfig(BaseModel):
|
||||
role: str = Field(..., description="The role of the agent")
|
||||
goal: str = Field(..., description="The goal of the agent")
|
||||
backstory: str = Field(..., description="The backstory of the agent")
|
||||
tools: List[str] = Field(..., description="The tools used by the agent")
|
||||
|
||||
class TaskConfig(BaseModel):
|
||||
description: str = Field(..., description="The description of the task")
|
||||
expected_output: str = Field(..., description="The expected output of the task")
|
||||
agent: AgentConfig = Field(..., description="The agent responsible for the task")
|
||||
|
||||
class CrewConfig(BaseModel):
|
||||
tasks: List[TaskConfig] = Field(..., description="The tasks to be performed by the crew")
|
||||
@@ -12,7 +12,10 @@ class EntityMemory(Memory):
|
||||
|
||||
def __init__(self, crew=None, embedder_config=None):
|
||||
storage = RAGStorage(
|
||||
type="entities", allow_reset=False, embedder_config=embedder_config, crew=crew
|
||||
type="entities",
|
||||
allow_reset=False,
|
||||
embedder_config=embedder_config,
|
||||
crew=crew,
|
||||
)
|
||||
super().__init__(storage)
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ class ShortTermMemory(Memory):
|
||||
"""
|
||||
|
||||
def __init__(self, crew=None, embedder_config=None):
|
||||
storage = RAGStorage(type="short_term", embedder_config=embedder_config, crew=crew)
|
||||
storage = RAGStorage(
|
||||
type="short_term", embedder_config=embedder_config, crew=crew
|
||||
)
|
||||
super().__init__(storage)
|
||||
|
||||
def save(self, item: ShortTermMemoryItem) -> None: # type: ignore # BUG?: Signature of "save" incompatible with supertype "Memory"
|
||||
|
||||
@@ -6,7 +6,6 @@ class Process(str, Enum):
|
||||
Class representing the different processes that can be used to tackle tasks
|
||||
"""
|
||||
|
||||
autonomous = "autonomous"
|
||||
sequential = "sequential"
|
||||
hierarchical = "hierarchical"
|
||||
# TODO: consensual = 'consensual'
|
||||
@@ -1,21 +1,18 @@
|
||||
import inspect
|
||||
import yaml
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def CrewBase(cls):
|
||||
class WrappedClass(cls):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
is_crew_class: bool = True
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
is_crew_class: bool = True # type: ignore
|
||||
|
||||
base_directory = None
|
||||
for frame_info in inspect.stack():
|
||||
|
||||
+1
-1
@@ -305,7 +305,7 @@ class Task(BaseModel):
|
||||
if directory and not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
with open(self.output_file, "w", encoding='utf-8') as file: # type: ignore # Argument 1 to "open" has incompatible type "str | None"; expected "int | str | bytes | PathLike[str] | PathLike[bytes]"
|
||||
with open(self.output_file, "w", encoding="utf-8") as file: # type: ignore # Argument 1 to "open" has incompatible type "str | None"; expected "int | str | bytes | PathLike[str] | PathLike[bytes]"
|
||||
file.write(result)
|
||||
return None
|
||||
|
||||
|
||||
@@ -33,20 +33,26 @@ class AgentTools(BaseModel):
|
||||
]
|
||||
return tools
|
||||
|
||||
def delegate_work(self, task: str, context: str, coworker: Union[str, None] = None, **kwargs):
|
||||
def delegate_work(
|
||||
self, task: str, context: str, coworker: Union[str, None] = None, **kwargs
|
||||
):
|
||||
"""Useful to delegate a specific task to a co-worker passing all necessary context and names."""
|
||||
coworker = coworker or kwargs.get("co_worker") or kwargs.get("co-worker")
|
||||
is_list = coworker.startswith("[") and coworker.endswith("]")
|
||||
if is_list:
|
||||
coworker = coworker[1:-1].split(",")[0]
|
||||
if coworker is not None:
|
||||
is_list = coworker.startswith("[") and coworker.endswith("]")
|
||||
if is_list:
|
||||
coworker = coworker[1:-1].split(",")[0]
|
||||
return self._execute(coworker, task, context)
|
||||
|
||||
def ask_question(self, question: str, context: str, coworker: Union[str, None] = None, **kwargs):
|
||||
def ask_question(
|
||||
self, question: str, context: str, coworker: Union[str, None] = None, **kwargs
|
||||
):
|
||||
"""Useful to ask a question, opinion or take from a co-worker passing all necessary context and names."""
|
||||
coworker = coworker or kwargs.get("co_worker") or kwargs.get("co-worker")
|
||||
is_list = coworker.startswith("[") and coworker.endswith("]")
|
||||
if is_list:
|
||||
coworker = coworker[1:-1].split(",")[0]
|
||||
if coworker is not None:
|
||||
is_list = coworker.startswith("[") and coworker.endswith("]")
|
||||
if is_list:
|
||||
coworker = coworker[1:-1].split(",")[0]
|
||||
return self._execute(coworker, question, context)
|
||||
|
||||
def _execute(self, agent, task, context):
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
"goal": "Manage the team to complete the task in the best way possible.",
|
||||
"backstory": "You are a seasoned manager with a knack for getting the best out of your team.\nYou are also known for your ability to delegate work to the right people, and to ask the right questions to get the best out of your team.\nEven though you don't perform tasks by yourself, you have a lot of experience in the field, which allows you to properly evaluate the work of your team members."
|
||||
},
|
||||
"planning_manager_agent": {
|
||||
"role": "Crew Creator",
|
||||
"goal": "Do the best most granular break down of a final goal into tasks and agents to complete them.",
|
||||
"backstory": "You are a very seasoned manager very capable of breaking goals into smaller tasks and recruiting the right people to complete them.\n You are also great at set expectation for the team you recruit and provide them with the best tools for their work."
|
||||
},
|
||||
"slices": {
|
||||
"observation": "\nObservation",
|
||||
"task": "\nCurrent Task: {input}\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from crewai.cli.cli import train, version
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner():
|
||||
return CliRunner()
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.cli.train_crew")
|
||||
def test_train_default_iterations(train_crew, runner):
|
||||
result = runner.invoke(train)
|
||||
|
||||
train_crew.assert_called_once_with(5)
|
||||
assert result.exit_code == 0
|
||||
assert "Training the crew for 5 iterations" in result.output
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.cli.train_crew")
|
||||
def test_train_custom_iterations(train_crew, runner):
|
||||
result = runner.invoke(train, ["--n_iterations", "10"])
|
||||
|
||||
train_crew.assert_called_once_with(10)
|
||||
assert result.exit_code == 0
|
||||
assert "Training the crew for 10 iterations" in result.output
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.cli.train_crew")
|
||||
def test_train_invalid_string_iterations(train_crew, runner):
|
||||
result = runner.invoke(train, ["--n_iterations", "invalid"])
|
||||
|
||||
train_crew.assert_not_called()
|
||||
assert result.exit_code == 2
|
||||
assert (
|
||||
"Usage: train [OPTIONS]\nTry 'train --help' for help.\n\nError: Invalid value for '-n' / '--n_iterations': 'invalid' is not a valid integer.\n"
|
||||
in result.output
|
||||
)
|
||||
|
||||
|
||||
def test_version_command(runner):
|
||||
result = runner.invoke(version)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "crewai version:" in result.output
|
||||
|
||||
|
||||
def test_version_command_with_tools(runner):
|
||||
result = runner.invoke(version, ["--tools"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "crewai version:" in result.output
|
||||
assert (
|
||||
"crewai tools version:" in result.output
|
||||
or "crewai tools not installed" in result.output
|
||||
)
|
||||
@@ -0,0 +1,87 @@
|
||||
import subprocess
|
||||
from unittest import mock
|
||||
|
||||
from crewai.cli.train_crew import train_crew
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.train_crew.subprocess.run")
|
||||
def test_train_crew_positive_iterations(mock_subprocess_run):
|
||||
# Arrange
|
||||
n_iterations = 5
|
||||
mock_subprocess_run.return_value = subprocess.CompletedProcess(
|
||||
args=["poetry", "run", "train", str(n_iterations)],
|
||||
returncode=0,
|
||||
stdout="Success",
|
||||
stderr="",
|
||||
)
|
||||
|
||||
# Act
|
||||
train_crew(n_iterations)
|
||||
|
||||
# Assert
|
||||
mock_subprocess_run.assert_called_once_with(
|
||||
["poetry", "run", "train", str(n_iterations)],
|
||||
capture_output=False,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.train_crew.click")
|
||||
def test_train_crew_zero_iterations(click):
|
||||
train_crew(0)
|
||||
click.echo.assert_called_once_with(
|
||||
"An unexpected error occurred: The number of iterations must be a positive integer.",
|
||||
err=True,
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.train_crew.click")
|
||||
def test_train_crew_negative_iterations(click):
|
||||
train_crew(-2)
|
||||
click.echo.assert_called_once_with(
|
||||
"An unexpected error occurred: The number of iterations must be a positive integer.",
|
||||
err=True,
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.train_crew.click")
|
||||
@mock.patch("crewai.cli.train_crew.subprocess.run")
|
||||
def test_train_crew_called_process_error(mock_subprocess_run, click):
|
||||
n_iterations = 5
|
||||
mock_subprocess_run.side_effect = subprocess.CalledProcessError(
|
||||
returncode=1,
|
||||
cmd=["poetry", "run", "train", str(n_iterations)],
|
||||
output="Error",
|
||||
stderr="Some error occurred",
|
||||
)
|
||||
train_crew(n_iterations)
|
||||
|
||||
mock_subprocess_run.assert_called_once_with(
|
||||
["poetry", "run", "train", "5"], capture_output=False, text=True, check=True
|
||||
)
|
||||
click.echo.assert_has_calls(
|
||||
[
|
||||
mock.call.echo(
|
||||
"An error occurred while training the crew: Command '['poetry', 'run', 'train', '5']' returned non-zero exit status 1.",
|
||||
err=True,
|
||||
),
|
||||
mock.call.echo("Error", err=True),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.train_crew.click")
|
||||
@mock.patch("crewai.cli.train_crew.subprocess.run")
|
||||
def test_train_crew_unexpected_exception(mock_subprocess_run, click):
|
||||
# Arrange
|
||||
n_iterations = 5
|
||||
mock_subprocess_run.side_effect = Exception("Unexpected error")
|
||||
train_crew(n_iterations)
|
||||
|
||||
mock_subprocess_run.assert_called_once_with(
|
||||
["poetry", "run", "train", "5"], capture_output=False, text=True, check=True
|
||||
)
|
||||
click.echo.assert_called_once_with(
|
||||
"An unexpected error occurred: Unexpected error", err=True
|
||||
)
|
||||
+70
-7
@@ -437,6 +437,7 @@ def test_async_task_execution():
|
||||
process=Process.sequential,
|
||||
tasks=[list_ideas, list_important_history, write_article],
|
||||
)
|
||||
output = TaskOutput(description="A 4 paragraph article about AI.", raw_output="ok")
|
||||
|
||||
with patch.object(Agent, "execute_task") as execute:
|
||||
execute.return_value = "ok"
|
||||
@@ -444,13 +445,11 @@ def test_async_task_execution():
|
||||
thread = threading.Thread(target=lambda: None, args=()).start()
|
||||
start.return_value = thread
|
||||
with patch.object(threading.Thread, "join", wraps=thread.join()) as join:
|
||||
list_ideas.output = TaskOutput(
|
||||
description="A 4 paragraph article about AI.", raw_output="ok"
|
||||
)
|
||||
list_important_history.output = TaskOutput(
|
||||
description="A 4 paragraph article about AI.", raw_output="ok"
|
||||
)
|
||||
list_ideas.output = output
|
||||
list_important_history.output = output
|
||||
|
||||
crew.kickoff()
|
||||
|
||||
start.assert_called()
|
||||
join.assert_called()
|
||||
|
||||
@@ -960,4 +959,68 @@ def test_manager_agent_in_agents_raises_exception():
|
||||
process=Process.hierarchical,
|
||||
manager_agent=manager,
|
||||
tasks=[task],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_manager_agent_with_tools_raises_exception():
|
||||
from crewai_tools import tool
|
||||
|
||||
@tool
|
||||
def testing_tool(first_number: int, second_number: int) -> int:
|
||||
"""Useful for when you need to multiply two numbers together."""
|
||||
return first_number * second_number
|
||||
|
||||
task = Task(
|
||||
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
|
||||
expected_output="5 bullet points with a paragraph for each idea.",
|
||||
)
|
||||
|
||||
manager = Agent(
|
||||
role="Manager",
|
||||
goal="Manage the crew and ensure the tasks are completed efficiently.",
|
||||
backstory="You're an experienced manager, skilled in overseeing complex projects and guiding teams to success. Your role is to coordinate the efforts of the crew members, ensuring that each task is completed on time and to the highest standard.",
|
||||
allow_delegation=False,
|
||||
tools=[testing_tool],
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.hierarchical,
|
||||
manager_agent=manager,
|
||||
tasks=[task],
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
crew.kickoff()
|
||||
|
||||
|
||||
def test_crew_train_success():
|
||||
task = Task(
|
||||
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
|
||||
expected_output="5 bullet points with a paragraph for each idea.",
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task],
|
||||
)
|
||||
|
||||
crew.train(n_iterations=2)
|
||||
|
||||
|
||||
def test_crew_train_error():
|
||||
task = Task(
|
||||
description="Come up with a list of 5 interesting ideas to explore for an article",
|
||||
expected_output="5 bullet points with a paragraph for each idea.",
|
||||
)
|
||||
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task],
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError) as e:
|
||||
crew.train()
|
||||
assert "train() missing 1 required positional argument: 'n_iterations'" in str(
|
||||
e
|
||||
)
|
||||
Reference in new issue
Block a user