diff --git a/docs/concepts/cli.mdx b/docs/concepts/cli.mdx index 198cfcd8b..e8f3d3088 100644 --- a/docs/concepts/cli.mdx +++ b/docs/concepts/cli.mdx @@ -110,6 +110,8 @@ crewai reset-memories [OPTIONS] - `-s, --short`: Reset SHORT TERM memory - `-e, --entities`: Reset ENTITIES memory - `-k, --kickoff-outputs`: Reset LATEST KICKOFF TASK OUTPUTS +- `-kn, --knowledge`: Reset KNOWLEDGE storage +- `-akn, --agent-knowledge`: Reset AGENT KNOWLEDGE storage - `-a, --all`: Reset ALL memories Example: diff --git a/docs/concepts/knowledge.mdx b/docs/concepts/knowledge.mdx index b4c7627d3..a5ecd2542 100644 --- a/docs/concepts/knowledge.mdx +++ b/docs/concepts/knowledge.mdx @@ -497,6 +497,13 @@ crew = Crew( result = crew.kickoff( inputs={"question": "What is the storage capacity of the XPS 13?"} ) + +# Resetting the agent specific knowledge via crew object +crew.reset_memories(command_type = 'agent_knowledge') + +# Resetting the agent specific knowledge via CLI +crewai reset-memories --agent-knowledge +crewai reset-memories -akn ``` diff --git a/docs/concepts/memory.mdx b/docs/concepts/memory.mdx index c375d4898..43f5918e4 100644 --- a/docs/concepts/memory.mdx +++ b/docs/concepts/memory.mdx @@ -679,6 +679,7 @@ crewai reset-memories [OPTIONS] | `-e`, `--entities` | Reset ENTITIES memory. | Flag (boolean) | False | | `-k`, `--kickoff-outputs` | Reset LATEST KICKOFF TASK OUTPUTS. | Flag (boolean) | False | | `-kn`, `--knowledge` | Reset KNOWLEDEGE storage | Flag (boolean) | False | +| `-akn`, `--agent-knowledge` | Reset AGENT KNOWLEDGE storage | Flag (boolean) | False | | `-a`, `--all` | Reset ALL memories. | Flag (boolean) | False | Note: To use the cli command you need to have your crew in a file called crew.py in the same directory. @@ -716,9 +717,11 @@ my_crew.reset_memories(command_type = 'all') # Resets all the memory | `entities` | Reset ENTITIES memory. | | `kickoff_outputs` | Reset LATEST KICKOFF TASK OUTPUTS. | | `knowledge` | Reset KNOWLEDGE memory. | +| `agent_knowledge` | Reset AGENT KNOWLEDGE memory. | | `all` | Reset ALL memories. | + ## Benefits of Using CrewAI's Memory System - 🦾 **Adaptive Learning:** Crews become more efficient over time, adapting to new information and refining their approach to tasks. diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index b2d59adbe..c0eff594c 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -1,6 +1,5 @@ -import os from importlib.metadata import version as get_version -from typing import Optional, Tuple +from typing import Optional import click @@ -138,12 +137,8 @@ def log_tasks_outputs() -> None: @click.option("-s", "--short", is_flag=True, help="Reset SHORT TERM memory") @click.option("-e", "--entities", is_flag=True, help="Reset ENTITIES memory") @click.option("-kn", "--knowledge", is_flag=True, help="Reset KNOWLEDGE storage") -@click.option( - "-k", - "--kickoff-outputs", - is_flag=True, - help="Reset LATEST KICKOFF TASK OUTPUTS", -) +@click.option("-akn", "--agent-knowledge", is_flag=True, help="Reset AGENT KNOWLEDGE storage") +@click.option("-k","--kickoff-outputs",is_flag=True,help="Reset LATEST KICKOFF TASK OUTPUTS") @click.option("-a", "--all", is_flag=True, help="Reset ALL memories") def reset_memories( long: bool, @@ -151,18 +146,20 @@ def reset_memories( entities: bool, knowledge: bool, kickoff_outputs: bool, + agent_knowledge: bool, all: bool, ) -> None: """ - Reset the crew memories (long, short, entity, latest_crew_kickoff_ouputs). This will delete all the data saved. + Reset the crew memories (long, short, entity, latest_crew_kickoff_ouputs, knowledge, agent_knowledge). This will delete all the data saved. """ try: - if not all and not (long or short or entities or knowledge or kickoff_outputs): + memory_types = [long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all] + if not any(memory_types): click.echo( "Please specify at least one memory type to reset using the appropriate flags." ) return - reset_memories_command(long, short, entities, knowledge, kickoff_outputs, all) + reset_memories_command(long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all) except Exception as e: click.echo(f"An error occurred while resetting memories: {e}", err=True) diff --git a/src/crewai/cli/reset_memories_command.py b/src/crewai/cli/reset_memories_command.py index eaf54ffb7..d8910f735 100644 --- a/src/crewai/cli/reset_memories_command.py +++ b/src/crewai/cli/reset_memories_command.py @@ -10,6 +10,7 @@ def reset_memories_command( short, entity, knowledge, + agent_knowledge, kickoff_outputs, all, ) -> None: @@ -23,10 +24,11 @@ def reset_memories_command( kickoff_outputs (bool): Whether to reset the latest kickoff task outputs. all (bool): Whether to reset all memories. knowledge (bool): Whether to reset the knowledge. + agent_knowledge (bool): Whether to reset the agents knowledge. """ try: - if not any([long, short, entity, kickoff_outputs, knowledge, all]): + if not any([long, short, entity, kickoff_outputs, knowledge, agent_knowledge, all]): click.echo( "No memory type specified. Please specify at least one type to reset." ) @@ -67,6 +69,11 @@ def reset_memories_command( click.echo( f"[Crew ({crew.name if crew.name else crew.id})] Knowledge has been reset." ) + if agent_knowledge: + crew.reset_memories(command_type="agent_knowledge") + click.echo( + f"[Crew ({crew.name if crew.name else crew.id})] Agents knowledge has been reset." + ) except subprocess.CalledProcessError as e: click.echo(f"An error occurred while resetting the memories: {e}", err=True) diff --git a/src/crewai/crew.py b/src/crewai/crew.py index a0158f646..0164552c5 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -1356,7 +1356,7 @@ class Crew(FlowTrackable, BaseModel): Args: command_type: Type of memory to reset. - Valid options: 'long', 'short', 'entity', 'knowledge', + Valid options: 'long', 'short', 'entity', 'knowledge', 'agent_knowledge' 'kickoff_outputs', or 'all' Raises: @@ -1369,6 +1369,7 @@ class Crew(FlowTrackable, BaseModel): "short", "entity", "knowledge", + "agent_knowledge", "kickoff_outputs", "all", "external", @@ -1393,19 +1394,14 @@ class Crew(FlowTrackable, BaseModel): def _reset_all_memories(self) -> None: """Reset all available memory systems.""" - memory_systems = [ - ("short term", getattr(self, "_short_term_memory", None)), - ("entity", getattr(self, "_entity_memory", None)), - ("external", getattr(self, "_external_memory", None)), - ("long term", getattr(self, "_long_term_memory", None)), - ("task output", getattr(self, "_task_output_handler", None)), - ("knowledge", getattr(self, "knowledge", None)), - ] + memory_systems = self._get_memory_systems() - for name, system in memory_systems: - if system is not None: + for memory_type, config in memory_systems.items(): + if (system := config.get('system')) is not None: + name = config.get('name') try: - system.reset() + reset_fn: Callable = cast(Callable, config.get('reset')) + reset_fn(system) self._logger.log( "info", f"[Crew ({self.name if self.name else self.id})] {name} memory has been reset", @@ -1424,24 +1420,17 @@ class Crew(FlowTrackable, BaseModel): Raises: RuntimeError: If the specified memory system fails to reset """ - reset_functions = { - "long": (getattr(self, "_long_term_memory", None), "long term"), - "short": (getattr(self, "_short_term_memory", None), "short term"), - "entity": (getattr(self, "_entity_memory", None), "entity"), - "knowledge": (getattr(self, "knowledge", None), "knowledge"), - "kickoff_outputs": ( - getattr(self, "_task_output_handler", None), - "task output", - ), - "external": (getattr(self, "_external_memory", None), "external"), - } + memory_systems = self._get_memory_systems() + config = memory_systems[memory_type] + system = config.get('system') + name = config.get('name') - memory_system, name = reset_functions[memory_type] - if memory_system is None: + if system is None: raise RuntimeError(f"{name} memory system is not initialized") - + try: - memory_system.reset() + reset_fn: Callable = cast(Callable, config.get('reset')) + reset_fn(system) self._logger.log( "info", f"[Crew ({self.name if self.name else self.id})] {name} memory has been reset", @@ -1450,3 +1439,64 @@ class Crew(FlowTrackable, BaseModel): raise RuntimeError( f"[Crew ({self.name if self.name else self.id})] Failed to reset {name} memory: {str(e)}" ) from e + + def _get_memory_systems(self): + """Get all available memory systems with their configuration. + + Returns: + Dict containing all memory systems with their reset functions and display names. + """ + def default_reset(memory): + return memory.reset() + def knowledge_reset(memory): + return self.reset_knowledge(memory) + + # Get knowledge for agents + agent_knowledges = [getattr(agent, "knowledge", None) for agent in self.agents + if getattr(agent, "knowledge", None) is not None] + # Get knowledge for crew and agents + crew_knowledge = getattr(self, "knowledge", None) + crew_and_agent_knowledges = ([crew_knowledge] if crew_knowledge is not None else []) + agent_knowledges + + return { + 'short': { + 'system': getattr(self, "_short_term_memory", None), + 'reset': default_reset, + 'name': 'Short Term' + }, + 'entity': { + 'system': getattr(self, "_entity_memory", None), + 'reset': default_reset, + 'name': 'Entity' + }, + 'external': { + 'system': getattr(self, "_external_memory", None), + 'reset': default_reset, + 'name': 'External' + }, + 'long': { + 'system': getattr(self, "_long_term_memory", None), + 'reset': default_reset, + 'name': 'Long Term' + }, + 'kickoff_outputs': { + 'system': getattr(self, "_task_output_handler", None), + 'reset': default_reset, + 'name': 'Task Output' + }, + 'knowledge': { + 'system': crew_and_agent_knowledges if crew_and_agent_knowledges else None, + 'reset': knowledge_reset, + 'name': 'Crew Knowledge and Agent Knowledge' + }, + 'agent_knowledge': { + 'system': agent_knowledges if agent_knowledges else None, + 'reset': knowledge_reset, + 'name': 'Agent Knowledge' + } + } + + def reset_knowledge(self, knowledges: List[Knowledge]) -> None: + """Reset crew and agent knowledge storage.""" + for ks in knowledges: + ks.reset() diff --git a/tests/cli/cli_test.py b/tests/cli/cli_test.py index 19439cb82..0ce747637 100644 --- a/tests/cli/cli_test.py +++ b/tests/cli/cli_test.py @@ -162,8 +162,18 @@ def test_reset_knowledge(mock_get_crews, runner): assert call_count == 1, "reset_memories should have been called once" -def test_reset_memory_from_many_crews(mock_get_crews, runner): +def test_reset_agent_knowledge(mock_get_crews, runner): + result = runner.invoke(reset_memories, ["--agent-knowledge"]) + call_count = 0 + for crew in mock_get_crews.return_value: + crew.reset_memories.assert_called_once_with(command_type="agent_knowledge") + assert f"[Crew ({crew.name})] Agents knowledge has been reset." in result.output + call_count += 1 + assert call_count == 1, "reset_memories should have been called once" + + +def test_reset_memory_from_many_crews(mock_get_crews, runner): crews = [] for crew_id in ["id-1234", "id-5678"]: mock_crew = mock.Mock(spec=Crew) diff --git a/tests/crew_test.py b/tests/crew_test.py index 7c242c825..62b934883 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -14,6 +14,7 @@ from crewai.agents import CacheHandler from crewai.crew import Crew from crewai.crews.crew_output import CrewOutput from crewai.flow import Flow, start +from crewai.knowledge.knowledge import Knowledge from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource from crewai.llm import LLM from crewai.memory.contextual.contextual_memory import ContextualMemory @@ -4403,3 +4404,165 @@ def test_sets_parent_flow_when_inside_flow(researcher, writer): flow = MyFlow() result = flow.kickoff() assert result.parent_flow is flow + + +def test_reset_knowledge_with_no_crew_knowledge(researcher,writer): + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ] + ) + + with pytest.raises(RuntimeError) as excinfo: + crew.reset_memories(command_type='knowledge') + + # Optionally, you can also check the error message + assert "Crew Knowledge and Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message + + +def test_reset_knowledge_with_only_crew_knowledge(researcher,writer): + mock_ks = MagicMock(spec=Knowledge) + + with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge: + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ], + knowledge=mock_ks + ) + + crew.reset_memories(command_type='knowledge') + mock_reset_agent_knowledge.assert_called_once_with([mock_ks]) + + +def test_reset_knowledge_with_crew_and_agent_knowledge(researcher,writer): + mock_ks_crew = MagicMock(spec=Knowledge) + mock_ks_research = MagicMock(spec=Knowledge) + mock_ks_writer = MagicMock(spec=Knowledge) + + researcher.knowledge = mock_ks_research + writer.knowledge = mock_ks_writer + + with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge: + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ], + knowledge=mock_ks_crew + ) + + crew.reset_memories(command_type='knowledge') + mock_reset_agent_knowledge.assert_called_once_with([mock_ks_crew,mock_ks_research,mock_ks_writer]) + + +def test_reset_knowledge_with_only_agent_knowledge(researcher,writer): + mock_ks_research = MagicMock(spec=Knowledge) + mock_ks_writer = MagicMock(spec=Knowledge) + + researcher.knowledge = mock_ks_research + writer.knowledge = mock_ks_writer + + with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge: + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ], + ) + + crew.reset_memories(command_type='knowledge') + mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer]) + + +def test_reset_agent_knowledge_with_no_agent_knowledge(researcher,writer): + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ], + ) + + with pytest.raises(RuntimeError) as excinfo: + crew.reset_memories(command_type='agent_knowledge') + + # Optionally, you can also check the error message + assert "Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message + + +def test_reset_agent_knowledge_with_only_crew_knowledge(researcher,writer): + mock_ks = MagicMock(spec=Knowledge) + + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ], + knowledge=mock_ks + ) + + with pytest.raises(RuntimeError) as excinfo: + crew.reset_memories(command_type='agent_knowledge') + + # Optionally, you can also check the error message + assert "Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message + + +def test_reset_agent_knowledge_with_crew_and_agent_knowledge(researcher,writer): + mock_ks_crew = MagicMock(spec=Knowledge) + mock_ks_research = MagicMock(spec=Knowledge) + mock_ks_writer = MagicMock(spec=Knowledge) + + researcher.knowledge = mock_ks_research + writer.knowledge = mock_ks_writer + + with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge: + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ], + knowledge=mock_ks_crew + ) + + crew.reset_memories(command_type='agent_knowledge') + mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer]) + + +def test_reset_agent_knowledge_with_only_agent_knowledge(researcher,writer): + mock_ks_research = MagicMock(spec=Knowledge) + mock_ks_writer = MagicMock(spec=Knowledge) + + researcher.knowledge = mock_ks_research + writer.knowledge = mock_ks_writer + + with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge: + crew = Crew( + agents=[researcher, writer], + process=Process.sequential, + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ], + ) + + crew.reset_memories(command_type='agent_knowledge') + mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer]) + +