diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index 5c9333557..bdb409b3a 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -1,8 +1,7 @@ import ast -import datetime import json import time -from datetime import UTC +from datetime import datetime from difflib import SequenceMatcher from json import JSONDecodeError from textwrap import dedent @@ -18,6 +17,7 @@ from crewai.tools import BaseTool from crewai.tools.structured_tool import CrewStructuredTool from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling from crewai.utilities import I18N, Converter, ConverterError, Printer +from crewai.utilities.datetime_compat import UTC from crewai.utilities.events.crewai_event_bus import crewai_event_bus from crewai.utilities.events.tool_usage_events import ( ToolSelectionErrorEvent, @@ -158,7 +158,7 @@ class ToolUsage: self.task.increment_tools_errors() started_at = time.time() - started_at_trace = datetime.datetime.now(UTC) + started_at_trace = datetime.now(UTC) from_cache = False result = None # type: ignore # Incompatible types in assignment (expression has type "None", variable has type "str") @@ -506,8 +506,8 @@ class ToolUsage: event_data = self._prepare_event_data(tool, tool_calling) event_data.update( { - "started_at": datetime.datetime.fromtimestamp(started_at), - "finished_at": datetime.datetime.fromtimestamp(finished_at), + "started_at": datetime.fromtimestamp(started_at), + "finished_at": datetime.fromtimestamp(finished_at), "from_cache": from_cache, } ) diff --git a/src/crewai/traces/unified_trace_controller.py b/src/crewai/traces/unified_trace_controller.py index 986a0a174..ae51b1455 100644 --- a/src/crewai/traces/unified_trace_controller.py +++ b/src/crewai/traces/unified_trace_controller.py @@ -1,6 +1,6 @@ import inspect import os -from datetime import UTC, datetime +from datetime import datetime from functools import wraps from typing import Any, Awaitable, Callable, Dict, List, Optional from uuid import uuid4 @@ -14,6 +14,7 @@ from crewai.traces.models import ( LLMResponse, ToolCall, ) +from crewai.utilities.datetime_compat import UTC class UnifiedTraceController: diff --git a/src/crewai/utilities/__init__.py b/src/crewai/utilities/__init__.py index dd6d9fa44..bd015db91 100644 --- a/src/crewai/utilities/__init__.py +++ b/src/crewai/utilities/__init__.py @@ -1,4 +1,5 @@ from .converter import Converter, ConverterError +from .datetime_compat import UTC from .file_handler import FileHandler from .i18n import I18N from .internal_instructor import InternalInstructor @@ -22,6 +23,7 @@ __all__ = [ "Printer", "Prompts", "RPMController", + "UTC", "YamlParser", "LLMContextLengthExceededException", "EmbeddingConfigurator", diff --git a/src/crewai/utilities/datetime_compat.py b/src/crewai/utilities/datetime_compat.py new file mode 100644 index 000000000..a394e80a0 --- /dev/null +++ b/src/crewai/utilities/datetime_compat.py @@ -0,0 +1,6 @@ +"""Compatibility module for datetime functionality across Python versions.""" +from datetime import timezone + +# Provide UTC timezone constant that works in Python 3.10+ +# This is equivalent to datetime.UTC in Python 3.11+ +UTC = timezone.utc diff --git a/tests/agent_test.py b/tests/agent_test.py index 3547398e5..37a20ea4c 100644 --- a/tests/agent_test.py +++ b/tests/agent_test.py @@ -1,7 +1,7 @@ """Test Agent creation and execution basic functionality.""" import os -from datetime import UTC, datetime, timezone +from datetime import datetime, timezone from unittest import mock from unittest.mock import patch @@ -18,6 +18,7 @@ from crewai.tools import tool from crewai.tools.tool_calling import InstructorToolCalling from crewai.tools.tool_usage import ToolUsage from crewai.utilities import RPMController +from crewai.utilities.datetime_compat import UTC from crewai.utilities.events import crewai_event_bus from crewai.utilities.events.tool_usage_events import ToolUsageFinishedEvent @@ -916,9 +917,10 @@ def test_tool_result_as_answer_is_the_final_answer_for_the_agent(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_tool_usage_information_is_appended_to_agent(): - from datetime import UTC, datetime + from datetime import datetime from crewai.tools import BaseTool + from crewai.utilities.datetime_compat import UTC class MyCustomTool(BaseTool): name: str = "Decide Greetings" @@ -928,8 +930,9 @@ def test_tool_usage_information_is_appended_to_agent(): return "Howdy!" fixed_datetime = datetime(2025, 2, 10, 12, 0, 0, tzinfo=UTC) - with patch("datetime.datetime") as mock_datetime: + with patch("crewai.tools.tool_usage.datetime") as mock_datetime: mock_datetime.now.return_value = fixed_datetime + mock_datetime.fromtimestamp = datetime.fromtimestamp mock_datetime.side_effect = lambda *args, **kw: datetime(*args, **kw) agent1 = Agent( diff --git a/tests/traces/test_unified_trace_controller.py b/tests/traces/test_unified_trace_controller.py index b14fb5d2d..e6b1ad6e7 100644 --- a/tests/traces/test_unified_trace_controller.py +++ b/tests/traces/test_unified_trace_controller.py @@ -1,5 +1,5 @@ import os -from datetime import UTC, datetime +from datetime import datetime from unittest.mock import MagicMock, patch from uuid import UUID @@ -21,6 +21,7 @@ from crewai.traces.unified_trace_controller import ( trace_flow_step, trace_llm_call, ) +from crewai.utilities.datetime_compat import UTC class TestUnifiedTraceController: diff --git a/tests/utilities/test_datetime_compat.py b/tests/utilities/test_datetime_compat.py new file mode 100644 index 000000000..3edfc67c4 --- /dev/null +++ b/tests/utilities/test_datetime_compat.py @@ -0,0 +1,14 @@ +"""Test datetime compatibility module.""" +from datetime import timezone + +from crewai.utilities.datetime_compat import UTC + + +def test_utc_timezone_compatibility(): + """Test that UTC timezone is compatible with both Python 3.10 and 3.11+""" + assert UTC == timezone.utc + assert UTC.tzname(None) == "UTC" + # Verify it works with datetime.now() + from datetime import datetime + dt = datetime.now(UTC) + assert dt.tzinfo == timezone.utc