From 63914f38cb0810f7798fc5e952c96bf230d1b206 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 09:52:49 +0000 Subject: [PATCH] fix: cache agent knowledge to avoid reloading on every kickoff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add caching mechanism in Agent.set_knowledge to track loaded state - Skip knowledge reloading when sources and embedder haven't changed - Add reset_knowledge_cache method for explicit cache clearing - Add comprehensive tests for caching behavior and edge cases - Fixes issue #3076 performance overhead on repeated kickoffs Co-Authored-By: João --- src/crewai/agent.py | 19 ++++++++ tests/agent_test.py | 116 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index c8e34b2e6..cfa01a558 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -194,6 +194,14 @@ class Agent(BaseAgent): def set_knowledge(self, crew_embedder: Optional[Dict[str, Any]] = None): try: + current_embedder = crew_embedder or self.embedder + if (hasattr(self, '_knowledge_loaded') and + self._knowledge_loaded and + self.knowledge is not None and + getattr(self, '_last_embedder', None) == current_embedder and + getattr(self, '_last_knowledge_sources', None) == self.knowledge_sources): + return + if self.embedder is None and crew_embedder: self.embedder = crew_embedder @@ -208,6 +216,10 @@ class Agent(BaseAgent): storage=self.knowledge_storage or None, ) self.knowledge.add_sources() + + self._knowledge_loaded = True + self._last_embedder = current_embedder + self._last_knowledge_sources = self.knowledge_sources.copy() if self.knowledge_sources else None except (TypeError, ValueError) as e: raise ValueError(f"Invalid Knowledge Configuration: {str(e)}") @@ -228,6 +240,13 @@ class Agent(BaseAgent): return any(getattr(self.crew, attr) for attr in memory_attributes) + def reset_knowledge_cache(self): + """Reset the knowledge cache to force reloading on next set_knowledge call.""" + self._knowledge_loaded = False + self._last_embedder = None + self._last_knowledge_sources = None + self.knowledge = None + def execute_task( self, task: Task, diff --git a/tests/agent_test.py b/tests/agent_test.py index 5711bef18..5c1c2279f 100644 --- a/tests/agent_test.py +++ b/tests/agent_test.py @@ -2276,3 +2276,119 @@ def test_agent_from_repository_without_org_set( "No organization currently set. We recommend setting one before using: `crewai org switch ` command.", style="yellow", ) + + +def test_agent_knowledge_caching_on_multiple_kickoffs(): + """Test that agent knowledge is only loaded once when kickoff is called multiple times.""" + content = "Brandon's favorite color is blue and he likes Mexican food." + string_source = StringKnowledgeSource(content=content) + + agent = Agent( + role="Researcher", + goal="Research about Brandon", + backstory="You are a researcher.", + knowledge_sources=[string_source] + ) + + task = Task( + description="What is Brandon's favorite color?", + expected_output="Brandon's favorite color", + agent=agent + ) + + crew = Crew(agents=[agent], tasks=[task]) + + with patch.object(Knowledge, 'add_sources') as mock_add_sources: + crew.kickoff() + assert mock_add_sources.call_count == 1 + + crew.kickoff() + assert mock_add_sources.call_count == 1 + + crew.kickoff() + assert mock_add_sources.call_count == 1 + + +def test_agent_knowledge_reloads_when_sources_change(): + """Test that agent knowledge is reloaded when knowledge sources change.""" + content1 = "Brandon's favorite color is blue." + content2 = "Brandon's favorite food is tacos." + string_source1 = StringKnowledgeSource(content=content1) + string_source2 = StringKnowledgeSource(content=content2) + + agent = Agent( + role="Researcher", + goal="Research about Brandon", + backstory="You are a researcher.", + knowledge_sources=[string_source1] + ) + + task = Task( + description="What do you know about Brandon?", + expected_output="Information about Brandon", + agent=agent + ) + + crew = Crew(agents=[agent], tasks=[task]) + + with patch.object(Knowledge, 'add_sources') as mock_add_sources: + crew.kickoff() + assert mock_add_sources.call_count == 1 + + agent.knowledge_sources = [string_source2] + + crew.kickoff() + assert mock_add_sources.call_count == 2 + + +def test_agent_knowledge_reloads_when_embedder_changes(): + """Test that agent knowledge is reloaded when embedder changes.""" + content = "Brandon's favorite color is blue." + string_source = StringKnowledgeSource(content=content) + + agent = Agent( + role="Researcher", + goal="Research about Brandon", + backstory="You are a researcher.", + knowledge_sources=[string_source] + ) + + task = Task( + description="What is Brandon's favorite color?", + expected_output="Brandon's favorite color", + agent=agent + ) + + crew1 = Crew(agents=[agent], tasks=[task], embedder={"model": "text-embedding-ada-002"}) + crew2 = Crew(agents=[agent], tasks=[task], embedder={"model": "text-embedding-3-small"}) + + with patch.object(Knowledge, 'add_sources') as mock_add_sources: + crew1.kickoff() + assert mock_add_sources.call_count == 1 + + crew2.kickoff() + assert mock_add_sources.call_count == 2 + + +def test_agent_reset_knowledge_cache(): + """Test that reset_knowledge_cache forces knowledge reloading.""" + content = "Brandon's favorite color is blue." + string_source = StringKnowledgeSource(content=content) + + agent = Agent( + role="Researcher", + goal="Research about Brandon", + backstory="You are a researcher.", + knowledge_sources=[string_source] + ) + + agent._knowledge_loaded = True + agent._last_embedder = {"model": "test"} + agent._last_knowledge_sources = [string_source] + + agent.reset_knowledge_cache() + + assert not getattr(agent, '_knowledge_loaded', True) + assert agent._last_embedder is None + assert agent._last_knowledge_sources is None + assert agent.knowledge is None