fix: propagate implicit @CrewBase names to crew events (#5574)

* fix: propagate implicit @CrewBase names to crew events

* test: appease static analysis for @CrewBase kickoff test

---------

Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com>
This commit is contained in:
Renato Nitta
2026-04-21 15:57:19 -03:00
committed by GitHub
parent d4f9f875f7
commit 42d6c03ebc
3 changed files with 109 additions and 1 deletions

View File

@@ -237,6 +237,8 @@ def crew(
self.tasks = instantiated_tasks
crew_instance: Crew = _call_method(meth, self, *args, **kwargs)
if "name" not in crew_instance.model_fields_set:
crew_instance.name = getattr(self, "_crew_name", None) or crew_instance.name
def callback_wrapper(
hook: Callable[Concatenate[CrewInstance, P2], R2], instance: CrewInstance

View File

@@ -8,6 +8,7 @@ from concurrent.futures import Future
from hashlib import md5
import re
import sys
from typing import Any, cast
from unittest.mock import ANY, MagicMock, call, patch
from crewai.agent import Agent
@@ -17,6 +18,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,
@@ -4741,6 +4743,61 @@ def test_default_crew_name(researcher, writer):
assert crew.name == "crew"
@pytest.mark.parametrize(
"explicit_name,expected",
[
(None, "ResearchAutomation"),
("My Research Automation", "My Research Automation"),
],
ids=["class_name_from_decorator", "explicit_name_preserved"],
)
def test_crew_kickoff_started_emits_display_name(
researcher, writer, explicit_name, expected
):
"""Kickoff events should use the decorator-provided display name when implicit."""
from crewai.crews.utils import prepare_kickoff
from crewai.project import CrewBase, agent, crew, task
@CrewBase
class ResearchAutomation:
agents_config = None
tasks_config = None
@agent
def researcher(self):
return researcher
@task
def first_task(self):
return Task(
description="Task 1",
expected_output="output",
agent=self.researcher(),
)
@crew
def crew(self):
crew_kwargs: dict[str, Any] = {
"agents": self.agents,
"tasks": self.tasks,
}
if explicit_name is not None:
crew_kwargs["name"] = explicit_name
return Crew(**crew_kwargs)
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)
automation_cls = cast(type[Any], ResearchAutomation)
prepare_kickoff(cast(Any, automation_cls()).crew(), inputs=None)
assert captured == [expected]
@pytest.mark.vcr()
def test_memory_remember_receives_task_content():
"""With memory=True, extract_memories receives raw content with task, agent, expected output, and result."""

View File

@@ -1,4 +1,4 @@
from typing import Any, ClassVar
from typing import Any, ClassVar, cast
from unittest.mock import Mock, create_autospec, patch
import pytest
@@ -261,6 +261,55 @@ 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."""
sample_agent = Agent(role="r", goal="g", backstory="b")
sample_task = Task(description="d", expected_output="o", agent=sample_agent)
@CrewBase
class ImplicitNameCrewFactory:
agents_config = None
tasks_config = None
agents: list[BaseAgent] = [sample_agent]
tasks: list[Task] = [sample_task]
@crew
def crew(self):
return Crew(
agents=[sample_agent],
tasks=[sample_task],
)
factory_cls = cast(type[Any], ImplicitNameCrewFactory)
crew_instance: Crew = cast(Any, factory_cls()).crew()
assert crew_instance.name == "ImplicitNameCrewFactory"
def test_crew_decorator_preserves_explicit_name():
"""Explicit Crew(name=...) inside @crew should win over the @CrewBase class name."""
sample_agent = Agent(role="r", goal="g", backstory="b")
sample_task = Task(description="d", expected_output="o", agent=sample_agent)
@CrewBase
class NamedCrewFactory:
agents_config = None
tasks_config = None
agents: list[BaseAgent] = [sample_agent]
tasks: list[Task] = [sample_task]
@crew
def crew(self):
return Crew(
name="My Explicit Name",
agents=[sample_agent],
tasks=[sample_task],
)
factory_cls = cast(type[Any], NamedCrewFactory)
crew_instance: Crew = cast(Any, factory_cls()).crew()
assert crew_instance.name == "My Explicit Name"
@tool
def simple_tool():
"""Return 'Hi!'"""