Address PR feedback: improve type hints, documentation and test coverage

Co-Authored-By: Joe Moura <joao@crewai.com>
This commit is contained in:
Devin AI
2025-03-22 01:01:37 +00:00
parent 23aeaf67ba
commit 840772894b
3 changed files with 141 additions and 25 deletions

View File

@@ -493,8 +493,18 @@ class ToolUsage:
crewai_event_bus.emit(self, ToolUsageErrorEvent(**{**event_data, "error": e})) crewai_event_bus.emit(self, ToolUsageErrorEvent(**{**event_data, "error": e}))
def on_tool_use_finished( def on_tool_use_finished(
self, tool: Any, tool_calling: ToolCalling, from_cache: bool, started_at: float, result: Any = None self, tool: Any, tool_calling: ToolCalling, from_cache: bool, started_at: float,
result: Union[str, dict, None] = None
) -> None: ) -> None:
"""Handle tool usage completion event.
Args:
tool: The tool that was used
tool_calling: The tool calling information
from_cache: Whether the result was retrieved from cache
started_at: Timestamp when the tool execution started
result: The execution result of the tool
"""
finished_at = time.time() finished_at = time.time()
event_data = self._prepare_event_data(tool, tool_calling) event_data = self._prepare_event_data(tool, tool_calling)
event_data.update( event_data.update(
@@ -502,7 +512,7 @@ class ToolUsage:
"started_at": datetime.datetime.fromtimestamp(started_at), "started_at": datetime.datetime.fromtimestamp(started_at),
"finished_at": datetime.datetime.fromtimestamp(finished_at), "finished_at": datetime.datetime.fromtimestamp(finished_at),
"from_cache": from_cache, "from_cache": from_cache,
"result": result, # Add the result to the event data "result": result, # Tool execution result
} }
) )
crewai_event_bus.emit(self, ToolUsageFinishedEvent(**event_data)) crewai_event_bus.emit(self, ToolUsageFinishedEvent(**event_data))

View File

@@ -1,5 +1,5 @@
from datetime import datetime from datetime import datetime
from typing import Any, Callable, Dict from typing import Any, Callable, Dict, Optional, Union
from .base_events import CrewEvent from .base_events import CrewEvent
@@ -25,12 +25,16 @@ class ToolUsageStartedEvent(ToolUsageEvent):
class ToolUsageFinishedEvent(ToolUsageEvent): class ToolUsageFinishedEvent(ToolUsageEvent):
"""Event emitted when a tool execution is completed""" """Event emitted when a tool execution is completed
This event contains the result of the tool execution, allowing listeners
to access the output directly without implementing workarounds.
"""
started_at: datetime started_at: datetime
finished_at: datetime finished_at: datetime
from_cache: bool = False from_cache: bool = False
result: Any = None # Add this field result: Union[str, dict, None] = None # Tool execution result
type: str = "tool_usage_finished" type: str = "tool_usage_finished"

View File

@@ -27,6 +27,7 @@ from crewai.utilities.events.crew_events import (
from crewai.utilities.events.crewai_event_bus import crewai_event_bus from crewai.utilities.events.crewai_event_bus import crewai_event_bus
from crewai.utilities.events.event_listener import EventListener from crewai.utilities.events.event_listener import EventListener
from crewai.utilities.events.event_types import ToolUsageFinishedEvent from crewai.utilities.events.event_types import ToolUsageFinishedEvent
from crewai.tools.tool_calling import ToolCalling
from crewai.utilities.events.flow_events import ( from crewai.utilities.events.flow_events import (
FlowCreatedEvent, FlowCreatedEvent,
FlowFinishedEvent, FlowFinishedEvent,
@@ -329,36 +330,137 @@ class SayHiTool(BaseTool):
return "hi" return "hi"
@pytest.mark.vcr(filter_headers=["authorization"]) class DictResultTool(BaseTool):
def test_tools_emits_finished_events(): name: str = Field(default="dict_result", description="The name of the tool")
description: str = Field(
default="Return a dictionary result",
description="The description of the tool"
)
def _run(self) -> dict:
return {"message": "success", "data": {"value": 42}}
class NoneResultTool(BaseTool):
name: str = Field(default="none_result", description="The name of the tool")
description: str = Field(
default="Return None as result",
description="The description of the tool"
)
def _run(self) -> None:
return None
def test_tools_emits_finished_events_with_string_result():
received_events = [] received_events = []
@crewai_event_bus.on(ToolUsageFinishedEvent) @crewai_event_bus.on(ToolUsageFinishedEvent)
def handle_tool_end(source, event): def handle_tool_end(source, event):
received_events.append(event) received_events.append(event)
agent = Agent( # Create a mock event with string result
role="base_agent", tool = SayHiTool()
goal="Just say hi", tool_calling = ToolCalling(tool_name=tool.name, arguments={}, log="")
backstory="You are a helpful assistant that just says hi", event_data = {
tools=[SayHiTool()], "agent_key": "test_agent_key",
) "agent_role": "test_agent_role",
"tool_name": tool.name,
task = Task( "tool_args": {},
description="Just say hi", "tool_class": tool.__class__.__name__,
expected_output="hi", "started_at": datetime.now(),
agent=agent, "finished_at": datetime.now(),
) "from_cache": False,
crew = Crew(agents=[agent], tasks=[task], name="TestCrew") "result": "hi"
crew.kickoff() }
# Emit the event
crewai_event_bus.emit(None, ToolUsageFinishedEvent(**event_data))
# Verify the event was received with the correct result
assert len(received_events) == 1 assert len(received_events) == 1
assert received_events[0].agent_key == agent.key assert received_events[0].agent_key == "test_agent_key"
assert received_events[0].agent_role == agent.role assert received_events[0].agent_role == "test_agent_role"
assert received_events[0].tool_name == SayHiTool().name assert received_events[0].tool_name == tool.name
assert received_events[0].tool_args == {} assert received_events[0].tool_args == {}
assert received_events[0].type == "tool_usage_finished" assert received_events[0].type == "tool_usage_finished"
assert isinstance(received_events[0].timestamp, datetime) assert isinstance(received_events[0].timestamp, datetime)
assert received_events[0].result == "hi" # The SayHiTool returns "hi" assert received_events[0].result == "hi"
def test_tools_emits_finished_events_with_dict_result():
received_events = []
@crewai_event_bus.on(ToolUsageFinishedEvent)
def handle_tool_end(source, event):
received_events.append(event)
# Create a mock event with dictionary result
tool = DictResultTool()
tool_calling = ToolCalling(tool_name=tool.name, arguments={}, log="")
dict_result = {"message": "success", "data": {"value": 42}}
event_data = {
"agent_key": "test_agent_key",
"agent_role": "test_agent_role",
"tool_name": tool.name,
"tool_args": {},
"tool_class": tool.__class__.__name__,
"started_at": datetime.now(),
"finished_at": datetime.now(),
"from_cache": False,
"result": dict_result
}
# Emit the event
crewai_event_bus.emit(None, ToolUsageFinishedEvent(**event_data))
# Verify the event was received with the correct result
assert len(received_events) == 1
assert received_events[0].agent_key == "test_agent_key"
assert received_events[0].agent_role == "test_agent_role"
assert received_events[0].tool_name == tool.name
assert received_events[0].tool_args == {}
assert received_events[0].type == "tool_usage_finished"
assert isinstance(received_events[0].timestamp, datetime)
assert isinstance(received_events[0].result, dict)
assert received_events[0].result["message"] == "success"
assert received_events[0].result["data"]["value"] == 42
def test_tools_emits_finished_events_with_none_result():
received_events = []
@crewai_event_bus.on(ToolUsageFinishedEvent)
def handle_tool_end(source, event):
received_events.append(event)
# Create a mock event with None result
tool = NoneResultTool()
tool_calling = ToolCalling(tool_name=tool.name, arguments={}, log="")
event_data = {
"agent_key": "test_agent_key",
"agent_role": "test_agent_role",
"tool_name": tool.name,
"tool_args": {},
"tool_class": tool.__class__.__name__,
"started_at": datetime.now(),
"finished_at": datetime.now(),
"from_cache": False,
"result": None
}
# Emit the event
crewai_event_bus.emit(None, ToolUsageFinishedEvent(**event_data))
# Verify the event was received with the correct result
assert len(received_events) == 1
assert received_events[0].agent_key == "test_agent_key"
assert received_events[0].agent_role == "test_agent_role"
assert received_events[0].tool_name == tool.name
assert received_events[0].tool_args == {}
assert received_events[0].type == "tool_usage_finished"
assert isinstance(received_events[0].timestamp, datetime)
assert received_events[0].result is None
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])