mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-02 05:38:12 +00:00
fix: store FileArtifact returned by after_tool_call hooks
store_if_artifact ran before the after_tool_call hooks, so a hook that replaced the result with a FileArtifact put raw bytes / a dataclass repr into the tool message and events. Re-run store_if_artifact on the final result after the hook loop in all three native tool paths (no-op for the normal string case).
This commit is contained in:
@@ -1030,6 +1030,10 @@ class CrewAgentExecutor(BaseAgentExecutor):
|
||||
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,
|
||||
|
||||
@@ -1939,6 +1939,10 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):
|
||||
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,
|
||||
|
||||
@@ -1541,6 +1541,10 @@ def execute_single_native_tool_call(
|
||||
except Exception: # noqa: S110
|
||||
pass
|
||||
|
||||
# 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(
|
||||
event_source,
|
||||
|
||||
@@ -314,6 +314,39 @@ class TestNativeExecutorWiring:
|
||||
assert base64.b64decode(captured["content"]) == payload
|
||||
|
||||
|
||||
class TestAfterHookArtifact:
|
||||
"""An after_tool_call hook that returns a FileArtifact must still be stored."""
|
||||
|
||||
def test_hook_returned_artifact_is_replaced_by_handle(self) -> None:
|
||||
from crewai.hooks.tool_hooks import (
|
||||
register_after_tool_call_hook,
|
||||
unregister_after_tool_call_hook,
|
||||
)
|
||||
from crewai.tools import BaseTool, FileArtifact
|
||||
|
||||
payload = bytes(range(256)) * 50
|
||||
|
||||
class Echo(BaseTool):
|
||||
name: str = "echo"
|
||||
description: str = "Echo"
|
||||
|
||||
def _run(self) -> str:
|
||||
return "plain text"
|
||||
|
||||
def hook(_context):
|
||||
return FileArtifact(data=payload, filename="hook.bin")
|
||||
|
||||
register_after_tool_call_hook(hook)
|
||||
try:
|
||||
run = _experimental_executor_runner([Echo()])
|
||||
result = run("echo", "{}")["result"]
|
||||
finally:
|
||||
unregister_after_tool_call_hook(hook)
|
||||
|
||||
assert base64.b64encode(payload).decode() not in result
|
||||
assert _HANDLE.search(result) is not None
|
||||
|
||||
|
||||
class TestTtlPrune:
|
||||
@staticmethod
|
||||
def _expire(handle: str) -> None:
|
||||
|
||||
Reference in New Issue
Block a user