Compare commits

..

9 Commits

Author SHA1 Message Date
Lucas Gomide
a2224bbe18 Merge branch 'main' into bugfix-python-3-10 2025-04-10 14:11:16 -03:00
Vini Brasil
37979a0ca1 Raise exception when flow fails (#2579) 2025-04-10 13:08:32 -04:00
Lorenze Jay
d96543d314 Merge branch 'main' into bugfix-python-3-10 2025-04-10 09:47:12 -07:00
Lucas Gomide
52e10d6c84 Merge branch 'main' into bugfix-python-3-10 2025-04-10 09:27:37 -03:00
Lorenze Jay
f18a112cd7 Merge branch 'main' into bugfix-python-3-10 2025-04-09 08:35:27 -07:00
Lucas Gomide
40dcdb43d6 Merge branch 'main' into bugfix-python-3-10 2025-04-09 11:58:16 -03:00
Lucas Gomide
1167fbdd8c chore: rename external_memory file test 2025-04-09 11:19:07 -03:00
Lucas Gomide
d200d00bb5 refactor: remove explicit Self import from typing
Python 3.10+ natively supports Self type annotation without explicit imports
2025-04-09 11:13:01 -03:00
Lucas Gomide
bf55dde358 ci(workflows): add Python version matrix (3.10-3.12) for tests 2025-04-09 11:13:01 -03:00
7 changed files with 31 additions and 104 deletions

View File

@@ -12,6 +12,9 @@ jobs:
tests:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -21,9 +24,8 @@ jobs:
with:
enable-cache: true
- name: Set up Python
run: uv python install 3.12.8
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install the project
run: uv sync --dev --all-extras

View File

@@ -1043,6 +1043,7 @@ class Flow(Generic[T], metaclass=FlowMeta):
import traceback
traceback.print_exc()
raise
def _log_flow_event(
self, message: str, color: str = "yellow", level: str = "info"

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Dict, Optional, Self
from typing import TYPE_CHECKING, Any, Dict, Optional
from crewai.memory.external.external_memory_item import ExternalMemoryItem
from crewai.memory.memory import Memory
@@ -52,7 +52,7 @@ class ExternalMemory(Memory):
def reset(self) -> None:
self.storage.reset()
def set_crew(self, crew: Any) -> Self:
def set_crew(self, crew: Any) -> "ExternalMemory":
super().set_crew(crew)
if not self.storage:

View File

@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional, Self
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
@@ -38,6 +38,6 @@ class Memory(BaseModel):
query=query, limit=limit, score_threshold=score_threshold
)
def set_crew(self, crew: Any) -> Self:
def set_crew(self, crew: Any) -> "Memory":
self.crew = crew
return self

View File

@@ -141,8 +141,7 @@ class ToolUsage:
self._printer.print(content=f"\n\n{error}\n", color="red")
return error
result = self._use(tool_string=tool_string, tool=tool, calling=calling)
return f"{result}"
return f"{self._use(tool_string=tool_string, tool=tool, calling=calling)}"
def _use(
self,
@@ -150,9 +149,9 @@ class ToolUsage:
tool: CrewStructuredTool,
calling: Union[ToolCalling, InstructorToolCalling],
) -> str:
if self._check_tool_repeated_usage(calling=calling):
if self._check_tool_repeated_usage(calling=calling): # type: ignore # _check_tool_repeated_usage of "ToolUsage" does not return a value (it only ever returns None)
try:
repeated_result = self._i18n.errors("task_repeated_usage").format(
result = self._i18n.errors("task_repeated_usage").format(
tool_names=self.tools_names
)
self._telemetry.tool_repeated_usage(
@@ -160,8 +159,8 @@ class ToolUsage:
tool_name=tool.name,
attempts=self._run_attempts,
)
repeated_result = self._format_result(result=repeated_result)
return repeated_result
result = self._format_result(result=result) # type: ignore # "_format_result" of "ToolUsage" does not return a value (it only ever returns None)
return result # type: ignore # Fix the return type of this function
except Exception:
if self.task:
@@ -169,15 +168,13 @@ class ToolUsage:
started_at = time.time()
from_cache = False
result: Optional[str] = None
result = None # type: ignore
if self.tools_handler and self.tools_handler.cache:
cache_result = self.tools_handler.cache.read(
result = self.tools_handler.cache.read(
tool=calling.tool_name, input=calling.arguments
)
if cache_result is not None:
result = cache_result
from_cache = True
) # type: ignore
from_cache = result is not None
available_tool = next(
(
@@ -239,32 +236,31 @@ class ToolUsage:
self._printer.print(
content=f"\n\n{error_message}\n", color="red"
)
return error
return error # type: ignore # No return value expected
if self.task:
self.task.increment_tools_errors()
return self.use(calling=calling, tool_string=tool_string)
return self.use(calling=calling, tool_string=tool_string) # type: ignore # No return value expected
if self.tools_handler:
should_cache = True
if available_tool is not None and (
if (
hasattr(available_tool, "cache_function")
and available_tool.cache_function
and available_tool.cache_function # type: ignore # Item "None" of "Any | None" has no attribute "cache_function"
):
should_cache = available_tool.cache_function(
should_cache = available_tool.cache_function( # type: ignore # Item "None" of "Any | None" has no attribute "cache_function"
calling.arguments, result
)
output_result = "" if result is None else result
self.tools_handler.on_tool_use(
calling=calling, output=output_result, should_cache=should_cache
calling=calling, output=result, should_cache=should_cache
)
self._telemetry.tool_usage(
llm=self.function_calling_llm,
tool_name=tool.name,
attempts=self._run_attempts,
)
result = self._format_result(result=result)
result = self._format_result(result=result) # type: ignore # "_format_result" of "ToolUsage" does not return a value (it only ever returns None)
data = {
"result": result,
"tool_name": tool.name,
@@ -279,13 +275,12 @@ class ToolUsage:
result=result,
)
if available_tool is not None and (
if (
hasattr(available_tool, "result_as_answer")
and available_tool.result_as_answer
and available_tool.result_as_answer # type: ignore # Item "None" of "Any | None" has no attribute "cache_function"
):
result_as_answer = available_tool.result_as_answer
if isinstance(result_as_answer, bool):
data["result_as_answer"] = result_as_answer
result_as_answer = available_tool.result_as_answer # type: ignore # Item "None" of "Any | None" has no attribute "result_as_answer"
data["result_as_answer"] = result_as_answer # type: ignore
if self.agent and hasattr(self.agent, "tools_results"):
self.agent.tools_results.append(data)
@@ -453,7 +448,7 @@ class ToolUsage:
self.task.increment_tools_errors()
if self.agent and self.agent.verbose:
self._printer.print(content=f"\n\n{e}\n", color="red")
return ToolUsageErrorException(
return ToolUsageErrorException( # type: ignore # Incompatible return value type (got "ToolUsageErrorException", expected "ToolCalling | InstructorToolCalling")
f"{self._i18n.errors('tool_usage_error').format(error=e)}\nMoving on then. {self._i18n.slice('format').format(tool_names=self.tools_names)}"
)
return self._tool_calling(tool_string)

View File

@@ -1,71 +0,0 @@
from typing import Any, Dict, List, Optional, Union
from unittest.mock import MagicMock, patch
import pytest
from crewai import Agent, Crew, Task
from crewai.agents.tools_handler import ToolsHandler
from crewai.tasks.task_output import TaskOutput
from crewai.tools import BaseTool
from crewai.tools.structured_tool import CrewStructuredTool
from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling
from crewai.tools.tool_usage import ToolUsage
class TestCodeInterpreterTool(BaseTool):
name: str = "Test Code Interpreter"
description: str = "A test tool that simulates code execution."
result_as_answer: bool = False
execution_called: bool = False
def _run(self, code: str = "", libraries_used: List[str] = []) -> str:
self.execution_called = True
return f"Code executed: {code}"
def test_direct_tool_execution():
"""Test that the tool can be executed directly."""
test_tool = TestCodeInterpreterTool()
result = test_tool.run("print('Hello World')")
assert "Code executed" in result
assert test_tool.execution_called
def test_tool_usage_return_types():
"""Test that the ToolUsage methods return the correct types."""
agent_mock = MagicMock()
task_mock = MagicMock()
task_mock.used_tools = 0
tools_handler_mock = MagicMock()
tool_usage = ToolUsage(
tools_handler=tools_handler_mock,
tools=[],
task=task_mock,
function_calling_llm=None,
agent=agent_mock,
action=None
)
i18n_mock = MagicMock()
i18n_mock.slice.return_value.format.return_value = "Tool description"
tool_usage._i18n = i18n_mock
result = "test result"
formatted_result = tool_usage._format_result(result)
assert formatted_result is not None, "_format_result should return the result"
assert isinstance(formatted_result, str), "_format_result should return a string"
result = tool_usage._should_remember_format()
assert isinstance(result, bool), "_should_remember_format should return a boolean"
result = "test result"
remembered_result = tool_usage._remember_format(result)
assert remembered_result is not None, "_remember_format should return the result"
assert isinstance(remembered_result, str), "_remember_format should return a string"
tool_calling_mock = MagicMock()
result = tool_usage._check_tool_repeated_usage(tool_calling_mock)
assert isinstance(result, bool), "_check_tool_repeated_usage should return a boolean"