fix: Correctly copy memory objects during crew training (fixes #2593) (#2594)

* fix: Correctly copy memory objects during crew training (#2593)

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: Fix import order in tests/crew_test.py

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Rely on validator for memory copy, update test assertions

Removes manual deep copy of memory objects in Crew.copy().
The Pydantic model_validator 'create_crew_memory' handles the
initialization of new memory instances for the copied crew.

Updates test_crew_copy_with_memory assertions to verify that
the private memory attributes (_short_term_memory, etc.) are
correctly initialized as new instances in the copied crew.

Co-Authored-By: Joe Moura <joao@crewai.com>

* Revert "fix: Rely on validator for memory copy, update test assertions"

This reverts commit 8702bf1e34.

* fix: Re-add manual deep copy for all memory types in Crew.copy

Addresses feedback on PR #2594 to ensure all memory objects
(short_term, long_term, entity, external, user) are correctly
deep copied using model_copy(deep=True).

Also simplifies the test case to directly verify the copy behavior
instead of relying on the train method.

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
This commit is contained in:
devin-ai-integration[bot]
2025-04-14 14:59:12 -04:00
committed by GitHub
parent 6a1eb10830
commit 88455cd52c
2 changed files with 64 additions and 3 deletions

View File

@@ -1214,6 +1214,17 @@ class Crew(BaseModel):
copied_data = self.model_dump(exclude=exclude) copied_data = self.model_dump(exclude=exclude)
copied_data = {k: v for k, v in copied_data.items() if v is not None} copied_data = {k: v for k, v in copied_data.items() if v is not None}
if self.short_term_memory:
copied_data["short_term_memory"] = self.short_term_memory.model_copy(deep=True)
if self.long_term_memory:
copied_data["long_term_memory"] = self.long_term_memory.model_copy(deep=True)
if self.entity_memory:
copied_data["entity_memory"] = self.entity_memory.model_copy(deep=True)
if self.external_memory:
copied_data["external_memory"] = self.external_memory.model_copy(deep=True)
if self.user_memory:
copied_data["user_memory"] = self.user_memory.model_copy(deep=True)
copied_data.pop("agents", None) copied_data.pop("agents", None)
copied_data.pop("tasks", None) copied_data.pop("tasks", None)

View File

@@ -3,6 +3,7 @@
import hashlib import hashlib
import json import json
import os import os
import tempfile
from concurrent.futures import Future from concurrent.futures import Future
from unittest import mock from unittest import mock
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@@ -19,6 +20,7 @@ from crewai.crews.crew_output import CrewOutput
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
from crewai.llm import LLM from crewai.llm import LLM
from crewai.memory.contextual.contextual_memory import ContextualMemory from crewai.memory.contextual.contextual_memory import ContextualMemory
from crewai.memory.short_term.short_term_memory import ShortTermMemory
from crewai.process import Process from crewai.process import Process
from crewai.task import Task from crewai.task import Task
from crewai.tasks.conditional_task import ConditionalTask from crewai.tasks.conditional_task import ConditionalTask
@@ -4116,6 +4118,54 @@ def test_crew_kickoff_for_each_works_with_manager_agent_copy():
assert crew_copy.manager_agent.id != crew.manager_agent.id assert crew_copy.manager_agent.id != crew.manager_agent.id
assert crew_copy.manager_agent.role == crew.manager_agent.role assert crew_copy.manager_agent.role == crew.manager_agent.role
assert crew_copy.manager_agent.goal == crew.manager_agent.goal assert crew_copy.manager_agent.goal == crew.manager_agent.goal
assert crew_copy.manager_agent.backstory == crew.manager_agent.backstory
assert isinstance(crew_copy.manager_agent.agent_executor, CrewAgentExecutor) def test_crew_copy_with_memory():
assert isinstance(crew_copy.manager_agent.cache_handler, CacheHandler) """Test that copying a crew with memory enabled does not raise validation errors and copies memory correctly."""
agent = Agent(role="Test Agent", goal="Test Goal", backstory="Test Backstory")
task = Task(description="Test Task", expected_output="Test Output", agent=agent)
crew = Crew(agents=[agent], tasks=[task], memory=True)
original_short_term_id = id(crew._short_term_memory) if crew._short_term_memory else None
original_long_term_id = id(crew._long_term_memory) if crew._long_term_memory else None
original_entity_id = id(crew._entity_memory) if crew._entity_memory else None
original_external_id = id(crew._external_memory) if crew._external_memory else None
original_user_id = id(crew._user_memory) if crew._user_memory else None
try:
crew_copy = crew.copy()
assert hasattr(crew_copy, "_short_term_memory"), "Copied crew should have _short_term_memory"
assert crew_copy._short_term_memory is not None, "Copied _short_term_memory should not be None"
assert id(crew_copy._short_term_memory) != original_short_term_id, "Copied _short_term_memory should be a new object"
assert hasattr(crew_copy, "_long_term_memory"), "Copied crew should have _long_term_memory"
assert crew_copy._long_term_memory is not None, "Copied _long_term_memory should not be None"
assert id(crew_copy._long_term_memory) != original_long_term_id, "Copied _long_term_memory should be a new object"
assert hasattr(crew_copy, "_entity_memory"), "Copied crew should have _entity_memory"
assert crew_copy._entity_memory is not None, "Copied _entity_memory should not be None"
assert id(crew_copy._entity_memory) != original_entity_id, "Copied _entity_memory should be a new object"
if original_external_id:
assert hasattr(crew_copy, "_external_memory"), "Copied crew should have _external_memory"
assert crew_copy._external_memory is not None, "Copied _external_memory should not be None"
assert id(crew_copy._external_memory) != original_external_id, "Copied _external_memory should be a new object"
else:
assert not hasattr(crew_copy, "_external_memory") or crew_copy._external_memory is None, "Copied _external_memory should be None if not originally present"
if original_user_id:
assert hasattr(crew_copy, "_user_memory"), "Copied crew should have _user_memory"
assert crew_copy._user_memory is not None, "Copied _user_memory should not be None"
assert id(crew_copy._user_memory) != original_user_id, "Copied _user_memory should be a new object"
else:
assert not hasattr(crew_copy, "_user_memory") or crew_copy._user_memory is None, "Copied _user_memory should be None if not originally present"
except pydantic_core.ValidationError as e:
if "Input should be an instance of" in str(e) and ("Memory" in str(e)):
pytest.fail(f"Copying with memory raised Pydantic ValidationError, likely due to incorrect memory copy: {e}")
else:
raise e # Re-raise other validation errors
except Exception as e:
pytest.fail(f"Copying crew raised an unexpected exception: {e}")