Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions lib/crewai/src/crewai/crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,42 @@ def create_crew_knowledge(self) -> Crew:
)
return self

@model_validator(mode="after")
def sync_agents_with_tasks(self) -> Self:
"""Ensure ``agents`` includes every agent assigned to a task.

When a ``Task`` has an agent assigned (``task.agent=agent``) but that
agent is not listed in ``Crew.agents``, downstream setup such as
``agent.crew = crew`` is skipped, which causes features that depend
on the crew reference (e.g. ``input_files`` injection, delegation
tools, crew memory lookups) to silently no-op.

This validator auto-populates ``self.agents`` with any task agent
that isn't already present so the crew behaves the same whether or
not ``agents=[...]`` was passed explicitly.
"""
if not self.tasks:
return self

existing: list[BaseAgent] = list(self.agents)

def _contains(agent: BaseAgent) -> bool:
return any(existing_agent is agent for existing_agent in existing)

for task in self.tasks:
task_agent = task.agent
if task_agent is None:
continue
if task_agent is self.manager_agent:
continue
if not _contains(task_agent):
existing.append(task_agent)

if len(existing) != len(self.agents):
self.agents = existing

return self

@model_validator(mode="after")
def check_manager_llm(self) -> Self:
"""Validates that the language model is set when using hierarchical process."""
Expand Down
110 changes: 109 additions & 1 deletion lib/crewai/tests/test_crew_multimodal.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,4 +457,112 @@ def test_pdf_upload_anthropic(self, pdf_file: PDFFile) -> None:
result = crew.kickoff(input_files={"document": pdf_file})

assert result.raw
assert len(result.raw) > 0
assert len(result.raw) > 0


class TestCrewMultimodalWithoutExplicitAgents:
"""Regression tests for issue #5534.

``Task.input_files`` must be propagated even when ``Crew`` is constructed
with only ``tasks=[...]`` and no explicit ``agents=[...]``. Prior to the
fix, ``crew.agents`` was empty in that case, ``setup_agents`` never wired
``task.agent.crew = crew``, and file injection silently no-op'd.
"""

@staticmethod
def _build_task_and_crew(
image_file: ImageFile,
) -> tuple[Agent, Task, Crew]:
llm = LLM(model="openai/gpt-4o-mini")
agent = Agent(
role="File Analyst",
goal="Analyze files",
backstory="Expert analyst.",
llm=llm,
multimodal=True,
verbose=False,
)
task = Task(
description="Describe the image.",
expected_output="A brief description.",
agent=agent,
input_files={"chart": image_file},
)
crew = Crew(tasks=[task], verbose=False)
return agent, task, crew

def test_crew_agents_auto_populated_from_tasks(
self, image_file: ImageFile
) -> None:
"""Crew.agents should include task.agent even if not passed explicitly."""
agent, _task, crew = self._build_task_and_crew(image_file)

assert agent in crew.agents
assert len(crew.agents) == 1

def test_crew_agents_not_duplicated_when_provided_explicitly(
self, image_file: ImageFile
) -> None:
"""When agents=[agent] is passed explicitly, no duplicates are added."""
llm = LLM(model="openai/gpt-4o-mini")
agent = Agent(
role="File Analyst",
goal="Analyze files",
backstory="Expert analyst.",
llm=llm,
multimodal=True,
verbose=False,
)
task = Task(
description="Describe the image.",
expected_output="A brief description.",
agent=agent,
input_files={"chart": image_file},
)
crew = Crew(agents=[agent], tasks=[task], verbose=False)

assert crew.agents.count(agent) == 1

def test_prepare_kickoff_wires_task_agent_to_crew(
self, image_file: ImageFile
) -> None:
"""Task agents not in ``agents=[...]`` should still get ``agent.crew``
set so downstream file/delegation code can find the crew."""
from crewai.crews.utils import prepare_kickoff
from crewai.utilities.file_store import clear_files, get_all_files

agent, task, crew = self._build_task_and_crew(image_file)

try:
prepare_kickoff(crew, inputs=None, input_files=None)

assert agent.crew is crew

# After prepare_kickoff + task._store_input_files, files stored
# at the task level must be retrievable via the crew id.
task._store_input_files()
files = get_all_files(crew.id, task.id)
assert files is not None
assert "chart" in files
finally:
clear_files(crew.id)

def test_task_prompt_includes_input_files_without_explicit_agents(
self, image_file: ImageFile
) -> None:
"""``task.prompt()`` must reference the input file even when the
crew is built without ``agents=[...]``."""
from crewai.crews.utils import prepare_kickoff
from crewai.utilities.file_store import clear_files

_agent, task, crew = self._build_task_and_crew(image_file)

try:
prepare_kickoff(crew, inputs=None, input_files=None)
task._store_input_files()

rendered = task.prompt()

assert "chart" in rendered
finally:
clear_files(crew.id)
Loading