Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/crewai/src/crewai/tools/base_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ class _ArgsSchemaPlaceholder(PydanticBaseModel):
default=0,
description="Current number of times this tool has been used.",
)
allow_repeated_usage: bool = Field(
default=False,
description="If True, allows the tool to be called consecutively with the same arguments. "
"Useful for stateful tools like browser automation where repeated calls with the same "
"arguments may produce different results due to external state changes.",
)

@field_validator("args_schema", mode="before")
@classmethod
Expand Down Expand Up @@ -227,6 +233,7 @@ def to_structured_tool(self) -> CrewStructuredTool:
result_as_answer=self.result_as_answer,
max_usage_count=self.max_usage_count,
current_usage_count=self.current_usage_count,
allow_repeated_usage=self.allow_repeated_usage,
)
structured_tool._original_tool = self
return structured_tool
Expand Down
3 changes: 3 additions & 0 deletions lib/crewai/src/crewai/tools/structured_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(
result_as_answer: bool = False,
max_usage_count: int | None = None,
current_usage_count: int = 0,
allow_repeated_usage: bool = False,
) -> None:
"""Initialize the structured tool.

Expand All @@ -47,6 +48,7 @@ def __init__(
result_as_answer: Whether to return the output directly
max_usage_count: Maximum number of times this tool can be used. None means unlimited usage.
current_usage_count: Current number of times this tool has been used.
allow_repeated_usage: If True, allows the tool to be called consecutively with the same arguments.
"""
self.name = name
self.description = description
Expand All @@ -56,6 +58,7 @@ def __init__(
self.result_as_answer = result_as_answer
self.max_usage_count = max_usage_count
self.current_usage_count = current_usage_count
self.allow_repeated_usage = allow_repeated_usage
self._original_tool: BaseTool | None = None

# Validate the function signature matches the schema
Expand Down
9 changes: 6 additions & 3 deletions lib/crewai/src/crewai/tools/tool_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ async def _ause(
Returns:
The result of the tool execution as a string.
"""
if self._check_tool_repeated_usage(calling=calling):
if self._check_tool_repeated_usage(calling=calling, tool=tool):
try:
result = self._i18n.errors("task_repeated_usage").format(
tool_names=self.tools_names
Expand Down Expand Up @@ -412,7 +412,7 @@ def _use(
tool: CrewStructuredTool,
calling: ToolCalling | InstructorToolCalling,
) -> str:
if self._check_tool_repeated_usage(calling=calling):
if self._check_tool_repeated_usage(calling=calling, tool=tool):
try:
result = self._i18n.errors("task_repeated_usage").format(
tool_names=self.tools_names
Expand Down Expand Up @@ -618,10 +618,13 @@ def _remember_format(self, result: str) -> str:
return result

def _check_tool_repeated_usage(
self, calling: ToolCalling | InstructorToolCalling
self, calling: ToolCalling | InstructorToolCalling, tool: Any = None
) -> bool:
if not self.tools_handler:
return False
# Check if the tool allows repeated usage (e.g., stateful tools like browser automation)
if tool is not None and getattr(tool, "allow_repeated_usage", False):
return False
if last_tool_usage := self.tools_handler.last_used_tool:
return (calling.tool_name == last_tool_usage.tool_name) and (
calling.arguments == last_tool_usage.arguments
Expand Down