Skip to content
Open
16 changes: 15 additions & 1 deletion lib/crewai/src/crewai/agents/crew_agent_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,13 @@ def _execute_single_native_tool_call(
ToolUsageFinishedEvent,
ToolUsageStartedEvent,
)
from crewai.tools.file_artifact import (
artifact_scope_id,
resolve_artifact_handles,
store_if_artifact,
)

scope_id = artifact_scope_id(self.crew, self.task, self.agent)

args_dict, parse_error = parse_tool_call_args(
func_args, func_name, call_id, original_tool
Expand Down Expand Up @@ -896,6 +903,7 @@ def _execute_single_native_tool_call(
tool=func_name, input=input_str
)
if cached_result is not None:
cached_result = store_if_artifact(cached_result, scope_id)
result = (
str(cached_result)
if not isinstance(cached_result, str)
Expand Down Expand Up @@ -960,7 +968,8 @@ def _execute_single_native_tool_call(
result = f"Tool '{func_name}' has reached its usage limit of {original_tool.max_usage_count} times and cannot be used anymore."
elif not from_cache and func_name in available_functions:
try:
raw_result = available_functions[func_name](**(args_dict or {}))
invoke_args = resolve_artifact_handles(args_dict) if args_dict else {}
raw_result = available_functions[func_name](**invoke_args)

if self.tools_handler and self.tools_handler.cache:
should_cache = True
Expand All @@ -977,6 +986,7 @@ def _execute_single_native_tool_call(
tool=func_name, input=input_str, output=raw_result
)

raw_result = store_if_artifact(raw_result, scope_id)
Comment thread
cursor[bot] marked this conversation as resolved.
result = (
str(raw_result) if not isinstance(raw_result, str) else raw_result
)
Expand Down Expand Up @@ -1020,6 +1030,10 @@ def _execute_single_native_tool_call(
color="red",
)

# An after_tool_call hook may have replaced the result with a
# FileArtifact; keep those bytes out of the message and events too.
result = store_if_artifact(result, scope_id)

if not error_event_emitted:
crewai_event_bus.emit(
self,
Expand Down
3 changes: 3 additions & 0 deletions lib/crewai/src/crewai/crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def get_supported_content_types(provider: str, api: str | None = None) -> list[s
from crewai.tools.agent_tools.agent_tools import AgentTools
from crewai.tools.agent_tools.read_file_tool import ReadFileTool
from crewai.tools.base_tool import BaseTool
from crewai.tools.file_artifact import clear_artifact_scope
from crewai.types.callback import SerializableCallable
from crewai.types.streaming import CrewStreamingOutput
from crewai.types.usage_metrics import UsageMetrics
Expand Down Expand Up @@ -1047,6 +1048,7 @@ def run_crew() -> None:
if self._memory is not None and hasattr(self._memory, "drain_writes"):
self._memory.drain_writes()
clear_files(self.id)
clear_artifact_scope(self.id)
Comment thread
cursor[bot] marked this conversation as resolved.
detach(token)

def _post_kickoff(self, result: CrewOutput) -> CrewOutput:
Expand Down Expand Up @@ -1255,6 +1257,7 @@ async def run_crew() -> None:
raise
finally:
clear_files(self.id)
clear_artifact_scope(self.id)
detach(token)

async def akickoff_for_each(
Expand Down
18 changes: 17 additions & 1 deletion lib/crewai/src/crewai/experimental/agent_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
BeforeLLMCallHookType,
)
from crewai.tools.base_tool import BaseTool
from crewai.tools.file_artifact import (
artifact_scope_id,
resolve_artifact_handles,
store_if_artifact,
)
from crewai.tools.structured_tool import CrewStructuredTool
from crewai.utilities.agent_utils import (
_llm_stop_words_applied,
Expand Down Expand Up @@ -1762,6 +1767,8 @@ def _execute_single_native_tool_call(self, tool_call: Any) -> dict[str, Any]:
return parse_error
args_dict: dict[str, Any] = parsed_args or {}

scope_id = artifact_scope_id(self.crew, self.task, self.agent)

# Get agent_key for event tracking
agent_key = getattr(self.agent, "key", "unknown") if self.agent else "unknown"

Expand Down Expand Up @@ -1794,6 +1801,7 @@ def _execute_single_native_tool_call(self, tool_call: Any) -> dict[str, Any]:
tool=func_name, input=input_str
)
if cached_result is not None:
cached_result = store_if_artifact(cached_result, scope_id)
result = (
str(cached_result)
if not isinstance(cached_result, str)
Expand Down Expand Up @@ -1859,7 +1867,10 @@ def _execute_single_native_tool_call(self, tool_call: Any) -> dict[str, Any]:
if func_name in self._available_functions:
try:
tool_func = self._available_functions[func_name]
raw_result = tool_func(**args_dict)
invoke_args = (
resolve_artifact_handles(args_dict) if args_dict else {}
)
raw_result = tool_func(**invoke_args)

# Add to cache after successful execution (before string conversion)
if self.tools_handler and self.tools_handler.cache:
Expand All @@ -1874,6 +1885,7 @@ def _execute_single_native_tool_call(self, tool_call: Any) -> dict[str, Any]:
)

# Convert to string for message
raw_result = store_if_artifact(raw_result, scope_id)
result = (
str(raw_result)
if not isinstance(raw_result, str)
Expand Down Expand Up @@ -1927,6 +1939,10 @@ def _execute_single_native_tool_call(self, tool_call: Any) -> dict[str, Any]:
color="red",
)

# An after_tool_call hook may have replaced the result with a
# FileArtifact; keep those bytes out of the message and events too.
result = store_if_artifact(result, scope_id)

if not error_event_emitted:
crewai_event_bus.emit(
self,
Expand Down
2 changes: 2 additions & 0 deletions lib/crewai/src/crewai/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from crewai.tools.base_tool import BaseTool, EnvVar, tool
from crewai.tools.file_artifact import FileArtifact


__all__ = [
"BaseTool",
"EnvVar",
"FileArtifact",
"tool",
]
Loading
Loading