From 0a0a46f9729a8704d2ec5ab37e4dd84e2b3d3e66 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 04:17:40 +0000 Subject: [PATCH] Fix test implementation to improve reliability and prevent timeouts Co-Authored-By: Joe Moura --- src/crewai/agent.py | 27 +++++-- src/crewai/knowledge/__init__.py | 4 ++ src/crewai/task.py | 18 +++++ tests/agent_test.py | 117 +++++++++++++++++++++---------- 4 files changed, 123 insertions(+), 43 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index cc65edba9..db3066967 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -1,3 +1,4 @@ +import logging import re import shutil import subprocess @@ -5,6 +6,8 @@ from typing import Any, Dict, List, Literal, Optional, Sequence, Union from pydantic import Field, InstanceOf, PrivateAttr, model_validator +logger = logging.getLogger(__name__) + from crewai.agents import CacheHandler from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.crew_agent_executor import CrewAgentExecutor @@ -209,13 +212,23 @@ class Agent(BaseAgent): # Check if the task has knowledge first if hasattr(task, 'knowledge') and task.knowledge: - task_knowledge_snippets = task.knowledge.query([task.prompt()]) - if task_knowledge_snippets: - task_knowledge_context = extract_knowledge_context( - task_knowledge_snippets - ) - if task_knowledge_context: - task_prompt += task_knowledge_context + """ + Knowledge is queried in the following priority order: + 1. Task-specific knowledge + 2. Agent's knowledge + 3. Crew's knowledge + This ensures the most specific context is considered first. + """ + try: + task_knowledge_snippets = task.knowledge.query([task.prompt()]) + if task_knowledge_snippets: + task_knowledge_context = extract_knowledge_context( + task_knowledge_snippets + ) + if task_knowledge_context: + task_prompt += task_knowledge_context + except Exception as e: + logger.warning(f"Error querying task knowledge: {str(e)}") # Then check agent's knowledge if self.knowledge: diff --git a/src/crewai/knowledge/__init__.py b/src/crewai/knowledge/__init__.py index 6cf6898fa..d293ca2a3 100644 --- a/src/crewai/knowledge/__init__.py +++ b/src/crewai/knowledge/__init__.py @@ -1,3 +1,7 @@ +""" +Knowledge management module for CrewAI. +Provides functionality for managing and querying knowledge sources. +""" from crewai.knowledge.knowledge import Knowledge __all__ = ["Knowledge"] diff --git a/src/crewai/task.py b/src/crewai/task.py index 828464978..576eff7e9 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -220,6 +220,24 @@ class Task(BaseModel): "may_not_set_field", "This field is not to be set by the user.", {} ) + @field_validator("knowledge") + @classmethod + def validate_knowledge(cls, knowledge): + """Validate that the knowledge field is an instance of Knowledge class. + + Args: + knowledge: The knowledge to validate. Can be None or an instance of Knowledge. + + Returns: + The validated knowledge object, or None if no knowledge was provided. + + Raises: + ValueError: If the knowledge is not an instance of Knowledge class. + """ + if knowledge is not None and not isinstance(knowledge, Knowledge): + raise ValueError("Knowledge must be an instance of Knowledge class") + return knowledge + @field_validator("output_file") @classmethod def output_file_validation(cls, value: Optional[str]) -> Optional[str]: diff --git a/tests/agent_test.py b/tests/agent_test.py index 525aa7197..4aacac17f 100644 --- a/tests/agent_test.py +++ b/tests/agent_test.py @@ -1670,44 +1670,89 @@ def test_agent_uses_task_knowledge(): # Create a mock Knowledge object with patch("crewai.knowledge.Knowledge", autospec=True) as MockKnowledge: - # Configure the mock - mock_knowledge = MockKnowledge.return_value - mock_knowledge.query.return_value = [{"content": content}] - - # Create an agent without knowledge sources - agent = Agent( - role="Geography Teacher", - goal="Provide accurate geographic information", - backstory="You are a geography expert who teaches students about world capitals.", - llm=LLM(model="gpt-4o-mini"), - ) - - # Create a task with knowledge - task = Task( - description="What is the capital of France?", - expected_output="The capital of France.", - agent=agent, - knowledge=mock_knowledge, - ) - - # Mock the agent's execute_task method to avoid actual LLM calls - with patch.object(agent.llm, "call") as mock_llm_call: - mock_llm_call.return_value = "The capital of France is Paris, where the Eiffel Tower is located." + try: + # Configure the mock + mock_knowledge = MockKnowledge.return_value + mock_knowledge.query.return_value = [{"content": content}] - # Execute the task - result = agent.execute_task(task) + # Create an agent with a simple mocked LLM + with patch("crewai.llm.LLM", autospec=True) as MockLLM: + mock_llm = MockLLM.return_value + mock_llm.call.return_value = "The capital of France is Paris, where the Eiffel Tower is located." + + agent = Agent( + role="Geography Teacher", + goal="Provide accurate geographic information", + backstory="You are a geography expert who teaches students about world capitals.", + llm=mock_llm, + ) + + # Create a task with knowledge + task = Task( + description="What is the capital of France?", + expected_output="The capital of France.", + agent=agent, + knowledge=mock_knowledge, + ) + + # Execute the task + result = agent.execute_task(task) + + # Assert that the agent provides the correct information + assert "paris" in result.lower() + assert "eiffel tower" in result.lower() + + # Verify that the task's knowledge was queried + mock_knowledge.query.assert_called_once() + + # The query should include the task prompt + query_arg = mock_knowledge.query.call_args[0][0] + assert isinstance(query_arg, list) + assert "capital of france" in query_arg[0].lower() + finally: + MockKnowledge.reset_mock() + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_agent_with_empty_task_knowledge(): + """Test that an agent handles empty task knowledge gracefully.""" + # Create a mock Knowledge object + with patch("crewai.knowledge.Knowledge", autospec=True) as MockKnowledge: + try: + # Configure the mock to return empty results + mock_knowledge = MockKnowledge.return_value + mock_knowledge.query.return_value = [] - # Assert that the agent provides the correct information - assert "paris" in result.lower() - assert "eiffel tower" in result.lower() - - # Verify that the task's knowledge was queried - mock_knowledge.query.assert_called_once() - - # The query should include the task prompt - query_arg = mock_knowledge.query.call_args[0][0] - assert isinstance(query_arg, list) - assert "capital of france" in query_arg[0].lower() + # Create an agent with a simple mocked LLM + with patch("crewai.llm.LLM", autospec=True) as MockLLM: + mock_llm = MockLLM.return_value + mock_llm.call.return_value = "The capital of France is Paris." + + agent = Agent( + role="Geography Teacher", + goal="Provide accurate geographic information", + backstory="You are a geography expert who teaches students about world capitals.", + llm=mock_llm, + ) + + # Create a task with empty knowledge + task = Task( + description="What is the capital of France?", + expected_output="The capital of France.", + agent=agent, + knowledge=mock_knowledge, + ) + + # Execute the task + result = agent.execute_task(task) + + # Assert that the agent still provides a response + assert "paris" in result.lower() + + # Verify that the task's knowledge was queried + mock_knowledge.query.assert_called_once() + finally: + MockKnowledge.reset_mock() @pytest.mark.vcr(filter_headers=["authorization"])