mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-22 10:56:50 +00:00
fix(agents): carry from_cache on the native tool path's ToolUsageFinishedEvent (#7501)
`_execute_single_native_tool_call` reads the tools handler's cache and skips the tool body on a hit, but emitted its ToolUsageFinishedEvent without `from_cache`, so the bus — and every trace built from it — saw a replayed native function call as a live one. The text-protocol path (ToolUsage.on_tool_use_finished) already carried the flag. One kwarg. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
This commit is contained in:
@@ -1056,6 +1056,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
|
||||
agent_key=agent_key,
|
||||
started_at=started_at,
|
||||
finished_at=datetime.now(),
|
||||
from_cache=from_cache,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
100
lib/crewai/tests/agents/test_native_tool_from_cache.py
Normal file
100
lib/crewai/tests/agents/test_native_tool_from_cache.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""A native function-calling tool call served from the cache says so on its event.
|
||||
|
||||
`_execute_single_native_tool_call` reads the tools handler's cache and skips
|
||||
the tool body on a hit, but its `ToolUsageFinishedEvent` used to be emitted
|
||||
without `from_cache`, so every consumer of the bus (tracing included) saw a
|
||||
replayed call as a live one. The text-protocol path (`ToolUsage`) already
|
||||
carried the flag.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from crewai.agents.cache.cache_handler import CacheHandler
|
||||
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||
from crewai.agents.tools_handler import ToolsHandler
|
||||
from crewai.events import crewai_event_bus
|
||||
from crewai.events.types.tool_usage_events import ToolUsageFinishedEvent
|
||||
from crewai.tools.base_tool import BaseTool, to_langchain
|
||||
from crewai.utilities.agent_utils import convert_tools_to_openai_schema
|
||||
|
||||
CALLS: list[str] = []
|
||||
|
||||
|
||||
class LookupTool(BaseTool):
|
||||
name: str = "lookup"
|
||||
description: str = "Look a term up."
|
||||
|
||||
def _run(self, term: str) -> str:
|
||||
CALLS.append(term)
|
||||
return f"value:{term}"
|
||||
|
||||
|
||||
def _executor(tool: BaseTool, cache: CacheHandler) -> CrewAgentExecutor:
|
||||
executor = CrewAgentExecutor(tools=to_langchain([tool]), original_tools=[tool])
|
||||
agent = Mock()
|
||||
agent.key = "test_agent"
|
||||
agent.role = "tester"
|
||||
agent.id = "agent-id"
|
||||
agent.verbose = False
|
||||
agent.fingerprint = None
|
||||
agent.tools_results = []
|
||||
task = Mock()
|
||||
task.name = "test"
|
||||
task.description = "test"
|
||||
task.id = "task-id"
|
||||
executor.agent = agent
|
||||
executor.task = task
|
||||
executor.tools_handler = ToolsHandler(cache=cache)
|
||||
return executor
|
||||
|
||||
|
||||
def _call(executor: CrewAgentExecutor, tool: BaseTool, args: dict) -> tuple[dict, list]:
|
||||
_, available_functions, _ = convert_tools_to_openai_schema([tool])
|
||||
events: list[ToolUsageFinishedEvent] = []
|
||||
with crewai_event_bus.scoped_handlers():
|
||||
|
||||
@crewai_event_bus.on(ToolUsageFinishedEvent)
|
||||
def _capture(_source: object, event: ToolUsageFinishedEvent) -> None:
|
||||
events.append(event)
|
||||
|
||||
result = executor._execute_single_native_tool_call(
|
||||
call_id="call_1",
|
||||
func_name="lookup",
|
||||
func_args=args,
|
||||
available_functions=available_functions,
|
||||
original_tool=tool,
|
||||
)
|
||||
crewai_event_bus.flush()
|
||||
return result, events
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
def test_a_cached_native_tool_call_reports_from_cache_on_the_finished_event() -> None:
|
||||
CALLS.clear()
|
||||
tool = LookupTool()
|
||||
cache = CacheHandler()
|
||||
cache.add(tool="lookup", input=json.dumps({"term": "x"}), output="cached:x")
|
||||
|
||||
result, events = _call(_executor(tool, cache), tool, {"term": "x"})
|
||||
|
||||
assert result["from_cache"] is True
|
||||
assert CALLS == []
|
||||
assert [event.from_cache for event in events] == [True]
|
||||
assert events[0].output == "cached:x"
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
def test_a_live_native_tool_call_reports_from_cache_false() -> None:
|
||||
CALLS.clear()
|
||||
tool = LookupTool()
|
||||
|
||||
result, events = _call(_executor(tool, CacheHandler()), tool, {"term": "y"})
|
||||
|
||||
assert result["from_cache"] is False
|
||||
assert CALLS == ["y"]
|
||||
assert [event.from_cache for event in events] == [False]
|
||||
Reference in New Issue
Block a user