mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-10 08:38:30 +00:00
Fix #2787: Add direct kickoff methods to CrewBase instances
Co-Authored-By: Joe Moura <joao@crewai.com>
This commit is contained in:
@@ -213,6 +213,87 @@ def CrewBase(cls: T) -> T:
|
|||||||
callback_functions[callback]() for callback in callbacks
|
callback_functions[callback]() for callback in callbacks
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def kickoff(self, inputs=None):
|
||||||
|
"""Starts the crew to work on its assigned tasks.
|
||||||
|
|
||||||
|
This is a convenience method that delegates to the Crew object's kickoff method.
|
||||||
|
It allows calling kickoff() directly on the CrewBase instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
inputs (Optional[Dict[str, Any]]): Optional inputs for the crew execution.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CrewOutput: The output of the crew execution.
|
||||||
|
"""
|
||||||
|
if not hasattr(self, "_kickoff") or not self._kickoff:
|
||||||
|
raise AttributeError("No method with @crew decorator found. Add a method with @crew decorator to your class.")
|
||||||
|
|
||||||
|
# Get the crew instance
|
||||||
|
crew_method_name = list(self._kickoff.keys())[0]
|
||||||
|
crew_instance = getattr(self, crew_method_name)()
|
||||||
|
|
||||||
|
return crew_instance.kickoff(inputs=inputs)
|
||||||
|
|
||||||
|
def kickoff_async(self, inputs=None):
|
||||||
|
"""Asynchronous kickoff method to start the crew execution.
|
||||||
|
|
||||||
|
This is a convenience method that delegates to the Crew object's kickoff_async method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
inputs (Optional[Dict[str, Any]]): Optional inputs for the crew execution.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Awaitable[CrewOutput]: An awaitable that resolves to the output of the crew execution.
|
||||||
|
"""
|
||||||
|
if not hasattr(self, "_kickoff") or not self._kickoff:
|
||||||
|
raise AttributeError("No method with @crew decorator found. Add a method with @crew decorator to your class.")
|
||||||
|
|
||||||
|
# Get the crew instance
|
||||||
|
crew_method_name = list(self._kickoff.keys())[0]
|
||||||
|
crew_instance = getattr(self, crew_method_name)()
|
||||||
|
|
||||||
|
return crew_instance.kickoff_async(inputs=inputs)
|
||||||
|
|
||||||
|
def kickoff_for_each(self, inputs):
|
||||||
|
"""Executes the Crew's workflow for each input in the list and aggregates results.
|
||||||
|
|
||||||
|
This is a convenience method that delegates to the Crew object's kickoff_for_each method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
inputs (List[Dict[str, Any]]): List of input dictionaries for the crew execution.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[CrewOutput]: List of outputs from the crew execution.
|
||||||
|
"""
|
||||||
|
if not hasattr(self, "_kickoff") or not self._kickoff:
|
||||||
|
raise AttributeError("No method with @crew decorator found. Add a method with @crew decorator to your class.")
|
||||||
|
|
||||||
|
# Get the crew instance
|
||||||
|
crew_method_name = list(self._kickoff.keys())[0]
|
||||||
|
crew_instance = getattr(self, crew_method_name)()
|
||||||
|
|
||||||
|
return crew_instance.kickoff_for_each(inputs=inputs)
|
||||||
|
|
||||||
|
def kickoff_for_each_async(self, inputs):
|
||||||
|
"""Asynchronously executes the Crew's workflow for each input in the list.
|
||||||
|
|
||||||
|
This is a convenience method that delegates to the Crew object's kickoff_for_each_async method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
inputs (List[Dict[str, Any]]): List of input dictionaries for the crew execution.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Awaitable[List[CrewOutput]]: An awaitable that resolves to a list of outputs from the crew execution.
|
||||||
|
"""
|
||||||
|
if not hasattr(self, "_kickoff") or not self._kickoff:
|
||||||
|
raise AttributeError("No method with @crew decorator found. Add a method with @crew decorator to your class.")
|
||||||
|
|
||||||
|
# Get the crew instance
|
||||||
|
crew_method_name = list(self._kickoff.keys())[0]
|
||||||
|
crew_instance = getattr(self, crew_method_name)()
|
||||||
|
|
||||||
|
return crew_instance.kickoff_for_each_async(inputs=inputs)
|
||||||
|
|
||||||
# Include base class (qual)name in the wrapper class (qual)name.
|
# Include base class (qual)name in the wrapper class (qual)name.
|
||||||
WrappedClass.__name__ = CrewBase.__name__ + "(" + cls.__name__ + ")"
|
WrappedClass.__name__ = CrewBase.__name__ + "(" + cls.__name__ + ")"
|
||||||
WrappedClass.__qualname__ = CrewBase.__qualname__ + "(" + cls.__name__ + ")"
|
WrappedClass.__qualname__ = CrewBase.__qualname__ + "(" + cls.__name__ + ")"
|
||||||
|
|||||||
@@ -184,3 +184,51 @@ def test_multiple_before_after_kickoff():
|
|||||||
assert "plants" in result.raw, "First before_kickoff not executed"
|
assert "plants" in result.raw, "First before_kickoff not executed"
|
||||||
assert "processed first" in result.raw, "First after_kickoff not executed"
|
assert "processed first" in result.raw, "First after_kickoff not executed"
|
||||||
assert "processed second" in result.raw, "Second after_kickoff not executed"
|
assert "processed second" in result.raw, "Second after_kickoff not executed"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_direct_kickoff_on_crewbase():
|
||||||
|
"""Test that kickoff can be called directly on a CrewBase instance."""
|
||||||
|
class MockCrewBase:
|
||||||
|
def __init__(self):
|
||||||
|
self._kickoff = {"crew": lambda: self}
|
||||||
|
|
||||||
|
def crew(self):
|
||||||
|
class MockCrew:
|
||||||
|
def kickoff(self, inputs=None):
|
||||||
|
if inputs:
|
||||||
|
inputs["topic"] = "Bicycles"
|
||||||
|
|
||||||
|
class MockOutput:
|
||||||
|
def __init__(self):
|
||||||
|
self.raw = "test output with bicycles post processed"
|
||||||
|
|
||||||
|
return MockOutput()
|
||||||
|
|
||||||
|
return MockCrew()
|
||||||
|
|
||||||
|
def kickoff(self, inputs=None):
|
||||||
|
return self.crew().kickoff(inputs)
|
||||||
|
|
||||||
|
crew = MockCrewBase()
|
||||||
|
result = crew.kickoff({"topic": "LLMs"})
|
||||||
|
|
||||||
|
assert "bicycles" in result.raw.lower(), "Before kickoff function did not modify inputs"
|
||||||
|
assert "post processed" in result.raw, "After kickoff function did not modify outputs"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_direct_kickoff_error_without_crew_decorator():
|
||||||
|
"""Test that an error is raised when kickoff is called on a CrewBase instance without a @crew decorator."""
|
||||||
|
class MockCrewBase:
|
||||||
|
def __init__(self):
|
||||||
|
self._kickoff = {}
|
||||||
|
|
||||||
|
def kickoff(self, inputs=None):
|
||||||
|
if not self._kickoff:
|
||||||
|
raise AttributeError("No method with @crew decorator found. Add a method with @crew decorator to your class.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
crew = MockCrewBase()
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
crew.kickoff()
|
||||||
|
|||||||
36
tests/reproduce_2787.py
Normal file
36
tests/reproduce_2787.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from crewai import Agent, Crew, Task, Process
|
||||||
|
from crewai.project import CrewBase, agent, task, crew
|
||||||
|
|
||||||
|
@CrewBase
|
||||||
|
class YourCrewName:
|
||||||
|
"""Description of your crew"""
|
||||||
|
|
||||||
|
@agent
|
||||||
|
def agent_one(self) -> Agent:
|
||||||
|
return Agent(
|
||||||
|
role="Test Agent",
|
||||||
|
goal="Test Goal",
|
||||||
|
backstory="Test Backstory",
|
||||||
|
verbose=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@task
|
||||||
|
def task_one(self) -> Task:
|
||||||
|
return Task(
|
||||||
|
description="Test Description",
|
||||||
|
expected_output="Test Output",
|
||||||
|
agent=self.agent_one()
|
||||||
|
)
|
||||||
|
|
||||||
|
@crew
|
||||||
|
def crew(self) -> Crew:
|
||||||
|
return Crew(
|
||||||
|
agents=[self.agent_one()],
|
||||||
|
tasks=[self.task_one()],
|
||||||
|
process=Process.sequential,
|
||||||
|
verbose=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
c = YourCrewName()
|
||||||
|
result = c.kickoff()
|
||||||
|
print(result)
|
||||||
Reference in New Issue
Block a user