fix: preserve crew name in events, fallback to class name

This commit is contained in:
Renato Nitta
2026-04-20 18:39:46 -03:00
parent d6d04717c2
commit 3546a48922
7 changed files with 87 additions and 15 deletions

View File

@@ -213,7 +213,7 @@ class Crew(FlowTrackable, BaseModel):
)
_kickoff_event_id: str | None = PrivateAttr(default=None)
name: str | None = Field(default="crew")
name: str | None = Field(default=None)
cache: bool = Field(default=True)
tasks: list[Task] = Field(default_factory=list)
agents: Annotated[
@@ -853,7 +853,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewTrainStartedEvent(
crew_name=self.name,
crew_name=self.name or self.__class__.__name__,
n_iterations=n_iterations,
filename=filename,
inputs=inputs,
@@ -881,7 +881,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewTrainCompletedEvent(
crew_name=self.name,
crew_name=self.name or self.__class__.__name__,
n_iterations=n_iterations,
filename=filename,
),
@@ -889,7 +889,10 @@ class Crew(FlowTrackable, BaseModel):
except Exception as e:
crewai_event_bus.emit(
self,
CrewTrainFailedEvent(error=str(e), crew_name=self.name),
CrewTrainFailedEvent(
error=str(e),
crew_name=self.name or self.__class__.__name__,
),
)
self._logger.log("error", f"Training failed: {e}", color="red")
CrewTrainingHandler(TRAINING_DATA_FILE).clear()
@@ -974,7 +977,7 @@ class Crew(FlowTrackable, BaseModel):
self,
CrewKickoffFailedEvent(
error=str(e),
crew_name=self.name,
crew_name=self.name or self.__class__.__name__,
started_event_id=self._kickoff_event_id,
),
)
@@ -1185,7 +1188,7 @@ class Crew(FlowTrackable, BaseModel):
self,
CrewKickoffFailedEvent(
error=str(e),
crew_name=self.name,
crew_name=self.name or self.__class__.__name__,
started_event_id=self._kickoff_event_id,
),
)
@@ -1791,7 +1794,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewKickoffCompletedEvent(
crew_name=self.name,
crew_name=self.name or self.__class__.__name__,
output=final_task_output,
total_tokens=self.token_usage.total_tokens,
started_event_id=self._kickoff_event_id,
@@ -2053,7 +2056,7 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewTestStartedEvent(
crew_name=self.name,
crew_name=self.name or self.__class__.__name__,
n_iterations=n_iterations,
eval_llm=llm_instance,
inputs=inputs,
@@ -2072,13 +2075,16 @@ class Crew(FlowTrackable, BaseModel):
crewai_event_bus.emit(
self,
CrewTestCompletedEvent(
crew_name=self.name,
crew_name=self.name or self.__class__.__name__,
),
)
except Exception as e:
crewai_event_bus.emit(
self,
CrewTestFailedEvent(error=str(e), crew_name=self.name),
CrewTestFailedEvent(
error=str(e),
crew_name=self.name or self.__class__.__name__,
),
)
raise

View File

@@ -311,7 +311,7 @@ def prepare_kickoff(
fmt = ConsoleFormatter(verbose=True)
content = fmt.create_status_content(
"Resuming from Checkpoint",
crew.name or "Crew",
crew.name or crew.__class__.__name__,
"bright_magenta",
ID=str(crew.id),
)
@@ -319,7 +319,10 @@ def prepare_kickoff(
content, "\U0001f504 Resuming from Checkpoint", "bright_magenta"
)
else:
started_event = CrewKickoffStartedEvent(crew_name=crew.name, inputs=normalized)
started_event = CrewKickoffStartedEvent(
crew_name=crew.name or crew.__class__.__name__,
inputs=normalized,
)
crew._kickoff_event_id = started_event.event_id
future = crewai_event_bus.emit(crew, started_event)
if future is not None:

View File

@@ -238,6 +238,9 @@ def crew(
crew_instance: Crew = _call_method(meth, self, *args, **kwargs)
if crew_instance.name is None:
crew_instance.name = getattr(self, "_crew_name", None)
def callback_wrapper(
hook: Callable[Concatenate[CrewInstance, P2], R2], instance: CrewInstance
) -> Callable[P2, R2]:

View File

@@ -213,7 +213,7 @@ class CrewEvaluator:
quality=quality_score,
execution_duration=current_task.execution_duration,
model=getattr(self.llm, "model", str(self.llm)),
crew_name=self.crew.name,
crew_name=self.crew.name or self.crew.__class__.__name__,
crew=self.crew,
),
)

View File

@@ -17,6 +17,7 @@ from crewai.crew import Crew
from crewai.crews.crew_output import CrewOutput
from crewai.events.event_bus import crewai_event_bus
from crewai.events.types.crew_events import (
CrewKickoffStartedEvent,
CrewTestCompletedEvent,
CrewTestStartedEvent,
CrewTrainCompletedEvent,
@@ -4738,7 +4739,60 @@ def test_default_crew_name(researcher, writer):
Task(description="Task 2", expected_output="output", agent=writer),
],
)
assert crew.name == "crew"
assert crew.name is None
def test_crew_kickoff_started_uses_class_name_fallback(researcher, writer):
"""Unnamed Crew subclasses should emit their class name in CrewKickoffStartedEvent."""
from crewai.crews.utils import prepare_kickoff
class ResearchAutomation(Crew):
pass
crew = ResearchAutomation(
agents=[researcher, writer],
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
],
)
captured: list[str | None] = []
with crewai_event_bus.scoped_handlers():
@crewai_event_bus.on(CrewKickoffStartedEvent)
def _capture(_source: Any, event: CrewKickoffStartedEvent) -> None:
captured.append(event.crew_name)
prepare_kickoff(crew, inputs=None)
assert captured == ["ResearchAutomation"]
def test_crew_kickoff_started_respects_explicit_name(researcher, writer):
"""Explicitly-named crews should emit the provided name, not the class name."""
from crewai.crews.utils import prepare_kickoff
class ResearchAutomation(Crew):
pass
crew = ResearchAutomation(
name="My Research Automation",
agents=[researcher, writer],
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
],
)
captured: list[str | None] = []
with crewai_event_bus.scoped_handlers():
@crewai_event_bus.on(CrewKickoffStartedEvent)
def _capture(_source: Any, event: CrewKickoffStartedEvent) -> None:
captured.append(event.crew_name)
prepare_kickoff(crew, inputs=None)
assert captured == ["My Research Automation"]
@pytest.mark.vcr()

View File

@@ -261,6 +261,12 @@ def test_crew_name():
assert crew._crew_name == "InternalCrew"
def test_crew_decorator_propagates_class_name_to_instance():
"""@crew-decorated factory method should set Crew.name to the decorated class name."""
crew_instance = InternalCrew().crew()
assert crew_instance.name == "InternalCrew"
@tool
def simple_tool():
"""Return 'Hi!'"""

View File

@@ -304,7 +304,7 @@ class TestTraceListenerSetup:
# Verify the first completion event has proper structure
completion_event = completion_events[0]
assert "crew_name" in completion_event.event_data
assert completion_event.event_data["crew_name"] == "crew"
assert completion_event.event_data["crew_name"] == "Crew"
# Verify all events have proper structure
for call in add_event_mock.call_args_list: