Lorenze/max retry defaults tools (#3362)

* feat: enhance BaseTool and CrewStructuredTool with usage tracking

This commit introduces a mechanism to track the usage count of tools within the CrewAI framework. The `BaseTool` class now includes a `_increment_usage_count` method that updates the current usage count, which is also reflected in the associated `CrewStructuredTool`. Additionally, a new test has been added to ensure that the maximum usage count is respected when invoking tools, enhancing the overall reliability and functionality of the tool system.

* feat: add max usage count feature to tools documentation

This commit introduces a new section in the tools overview documentation that explains the maximum usage count feature for tools within the CrewAI framework. Users can now set a limit on how many times a tool can be used, enhancing control over tool usage. An example of implementing the `FileReadTool` with a maximum usage count is also provided, improving the clarity and usability of the documentation.

* undo field string
This commit is contained in:
Lorenze Jay
2025-08-19 10:44:55 -07:00
committed by GitHub
parent 95e3d6db7a
commit d6254918fd
5 changed files with 935 additions and 12 deletions

View File

@@ -117,4 +117,19 @@ agent = Agent(
)
```
## **Max Usage Count**
You can set a maximum usage count for a tool to prevent it from being used more than a certain number of times.
By default, the max usage count is unlimited.
```python
from crewai_tools import FileReadTool
tool = FileReadTool(max_usage_count=5, ...)
```
Ready to explore? Pick a category above to discover tools that fit your use case!

View File

@@ -14,12 +14,14 @@ from pydantic import BaseModel as PydanticBaseModel
from crewai.tools.structured_tool import CrewStructuredTool
class EnvVar(BaseModel):
name: str
description: str
required: bool = True
default: Optional[str] = None
class BaseTool(BaseModel, ABC):
class _ArgsSchemaPlaceholder(PydanticBaseModel):
pass
@@ -108,7 +110,7 @@ class BaseTool(BaseModel, ABC):
def to_structured_tool(self) -> CrewStructuredTool:
"""Convert this tool to a CrewStructuredTool instance."""
self._set_args_schema()
return CrewStructuredTool(
structured_tool = CrewStructuredTool(
name=self.name,
description=self.description,
args_schema=self.args_schema,
@@ -117,6 +119,8 @@ class BaseTool(BaseModel, ABC):
max_usage_count=self.max_usage_count,
current_usage_count=self.current_usage_count,
)
structured_tool._original_tool = self
return structured_tool
@classmethod
def from_langchain(cls, tool: Any) -> "BaseTool":
@@ -276,7 +280,9 @@ def to_langchain(
return [t.to_structured_tool() if isinstance(t, BaseTool) else t for t in tools]
def tool(*args, result_as_answer: bool = False, max_usage_count: int | None = None) -> Callable:
def tool(
*args, result_as_answer: bool = False, max_usage_count: int | None = None
) -> Callable:
"""
Decorator to create a tool from a function.

View File

@@ -10,6 +10,11 @@ from pydantic import BaseModel, Field, create_model
from crewai.utilities.logger import Logger
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from crewai.tools.base_tool import BaseTool
class CrewStructuredTool:
"""A structured tool that can operate on any number of inputs.
@@ -18,6 +23,8 @@ class CrewStructuredTool:
that integrates better with CrewAI's ecosystem.
"""
_original_tool: BaseTool | None = None
def __init__(
self,
name: str,
@@ -47,6 +54,7 @@ class CrewStructuredTool:
self.result_as_answer = result_as_answer
self.max_usage_count = max_usage_count
self.current_usage_count = current_usage_count
self._original_tool = None
# Validate the function signature matches the schema
self._validate_function_signature()
@@ -219,6 +227,8 @@ class CrewStructuredTool:
"""
parsed_args = self._parse_args(input)
self._increment_usage_count()
if inspect.iscoroutinefunction(self.func):
return await self.func(**parsed_args, **kwargs)
else:
@@ -242,6 +252,8 @@ class CrewStructuredTool:
"""Main method for tool execution."""
parsed_args = self._parse_args(input)
self._increment_usage_count()
if inspect.iscoroutinefunction(self.func):
result = asyncio.run(self.func(**parsed_args, **kwargs))
return result
@@ -253,6 +265,12 @@ class CrewStructuredTool:
return result
def _increment_usage_count(self) -> None:
"""Increment the usage count."""
self.current_usage_count += 1
if self._original_tool is not None:
self._original_tool.current_usage_count = self.current_usage_count
@property
def args(self) -> dict:
"""Get the tool's input arguments schema."""

View File

@@ -0,0 +1,845 @@
interactions:
- request:
body: '{"messages": [{"role": "system", "content": "You are Iterating Agent. You
are an agent that iterates a given number of times\nYour personal goal is: Call
the iterating tool 5 times\nYou ONLY have access to the following tools, and
should NEVER make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool
Arguments: {''input_text'': {''description'': None, ''type'': ''str''}}\nTool
Description: A tool that iterates a given number of times\n\nIMPORTANT: Use
the following format in your response:\n\n```\nThought: you should always think
about what to do\nAction: the action to take, only one name of [iterating_tool],
just the name, exactly as it''s written.\nAction Input: the input to the action,
just a simple JSON object, enclosed in curly braces, using \" to wrap keys and
values.\nObservation: the result of the action\n```\n\nOnce all necessary information
is gathered, return the following format:\n\n```\nThought: I now know the final
answer\nFinal Answer: the final answer to the original input question\n```"},
{"role": "user", "content": "\nCurrent Task: Call the iterating tool 5 times\n\nThis
is the expected criteria for your final answer: A list of the iterations\nyou
MUST return the actual complete content as the final answer, not a summary.\n\nBegin!
This is VERY important to you, use the tools available and give your best Final
Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '1452'
content-type:
- application/json
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.93.0
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.93.0
x-stainless-raw-response:
- 'true'
x-stainless-read-timeout:
- '600.0'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.9
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: !!binary |
H4sIAAAAAAAAAwAAAP//hFPRjtMwEHzPV6z83J5ySVvu8nYCIZ1ACKSCEOSUOs4mMefYlr2Boqr/
jpy0TQ7uxIsV7exMZmftQwTAZMUyYKLlJDqrlq837zbXn/flx6/fVPIp/fI+3ZbaxVv568ObR7YI
DFP+QEFn1pUwnVVI0ugRFg45YVC9frVeb5I0WacD0JkKVaA1lpYrs0ziZLWMb5bx5kRsjRToWQbf
IwCAw3AGi7rCPcsgXpwrHXrPG2TZpQmAOaNChXHvpSeuiS0mUBhNqAfXu90u19vW9E1LGdyDRqyA
DAiuFFCLIAkdJ6kbIGPGUi2dJyDZ4VWu70SYNpv6itB3rsO9tj1lcMiZDF8F4Z5ylkHO3g4qJ5rR
OTvOLTqse89DQrpXagZwrQ0NjCGchxNyvMShTGOdKf1fVFZLLX1bOOTe6DC6J2PZgB4jgIch9v5J
ksw601kqyDzi8Lvk9nbUY9OiJzRdn0AyxNWsnq4Wz+gVFRKXys8WxwQXLVYTddoy7ytpZkA0m/pf
N89pj5OPG/qv/AQIgZawKqzDSoqnE09tDsM7eKntkvJgmHl0P6XAgiS6sIkKa96r8Yoy/9sTdkUt
dYPOOjne09oWq01Z1zHG4oZFx+gPAAAA//8DAHvlcCKwAwAA
headers:
CF-RAY:
- 971b3f72effa6897-SJC
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 19 Aug 2025 17:07:35 GMT
Server:
- cloudflare
Set-Cookie:
- __cf_bm=6OZC5kPO9TEmVSACP2sSOZK9ZEZ5I4T_VUlfmzsoY9Y-1755623255-1.0.1.1-ek3SaNBOhXmCg7K3J7LIsE0aCrnK5YfSumHDT6nc8Df1Zh3bzMLHLDqTUwtqiG8SwxiIFXeGP4.Vt2sx9b3FCkxoyrqNpgrBL5DAffAGHm8;
path=/; expires=Tue, 19-Aug-25 17:37:35 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=R_H7SrOF3QFEWePZfvxzuyKWZAt5ulsNbP28.6DC9wM-1755623255760-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
access-control-expose-headers:
- X-Request-ID
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
openai-organization:
- crewai-iuxna1
openai-processing-ms:
- '2564'
openai-project:
- proj_xitITlrFeen7zjNSzML82h9x
openai-version:
- '2020-10-01'
x-envoy-upstream-service-time:
- '2751'
x-ratelimit-limit-project-tokens:
- '30000000'
x-ratelimit-limit-requests:
- '10000'
x-ratelimit-limit-tokens:
- '30000000'
x-ratelimit-remaining-project-tokens:
- '29999674'
x-ratelimit-remaining-requests:
- '9999'
x-ratelimit-remaining-tokens:
- '29999674'
x-ratelimit-reset-project-tokens:
- 0s
x-ratelimit-reset-requests:
- 6ms
x-ratelimit-reset-tokens:
- 0s
x-request-id:
- req_d654df1116aa42ca8ee7d10b4b424303
status:
code: 200
message: OK
- request:
body: '{"messages": [{"role": "system", "content": "You are Iterating Agent. You
are an agent that iterates a given number of times\nYour personal goal is: Call
the iterating tool 5 times\nYou ONLY have access to the following tools, and
should NEVER make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool
Arguments: {''input_text'': {''description'': None, ''type'': ''str''}}\nTool
Description: A tool that iterates a given number of times\n\nIMPORTANT: Use
the following format in your response:\n\n```\nThought: you should always think
about what to do\nAction: the action to take, only one name of [iterating_tool],
just the name, exactly as it''s written.\nAction Input: the input to the action,
just a simple JSON object, enclosed in curly braces, using \" to wrap keys and
values.\nObservation: the result of the action\n```\n\nOnce all necessary information
is gathered, return the following format:\n\n```\nThought: I now know the final
answer\nFinal Answer: the final answer to the original input question\n```"},
{"role": "user", "content": "\nCurrent Task: Call the iterating tool 5 times\n\nThis
is the expected criteria for your final answer: A list of the iterations\nyou
MUST return the actual complete content as the final answer, not a summary.\n\nBegin!
This is VERY important to you, use the tools available and give your best Final
Answer, your job depends on it!\n\nThought:"}, {"role": "assistant", "content":
"```\nThought: I need to call the iterating tool the first time.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"First iteration\"}\nObservation: Iteration 0: First
iteration"}], "model": "gpt-4o", "stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '1673'
content-type:
- application/json
cookie:
- __cf_bm=6OZC5kPO9TEmVSACP2sSOZK9ZEZ5I4T_VUlfmzsoY9Y-1755623255-1.0.1.1-ek3SaNBOhXmCg7K3J7LIsE0aCrnK5YfSumHDT6nc8Df1Zh3bzMLHLDqTUwtqiG8SwxiIFXeGP4.Vt2sx9b3FCkxoyrqNpgrBL5DAffAGHm8;
_cfuvid=R_H7SrOF3QFEWePZfvxzuyKWZAt5ulsNbP28.6DC9wM-1755623255760-0.0.1.1-604800000
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.93.0
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.93.0
x-stainless-raw-response:
- 'true'
x-stainless-read-timeout:
- '600.0'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.9
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: !!binary |
H4sIAAAAAAAAA4RTTW/bMAy9+1cQOidFlg8v8G1LB6zosbvNhaNItK3NFjWJ7loE+e+D7CR2uw67
CAIf3xP5SB0TAGG0yECoWrJqXTPfpffp2tWqu93u7m+Xu+3Dl6/m96+X+pB+VmIWGXT4gYovrBtF
rWuQDdkBVh4lY1T98HGzSZer5SbtgZY0NpFWOZ6vab5cLNfzxXa+SM/EmozCIDL4ngAAHPszlmg1
PosMFrNLpMUQZIUiuyYBCE9NjAgZggksLYvZCCqyjLaver/f5/ZbTV1VcwZ3UMsnhHMXqIFrhNL4
wGAYvYyNgbQaLEaQwHlS52tMDajIaiCLN7n9pGJ6dmHaqmCi5hKHO+s6zuCYCxNvBeMz5yKDXDwM
KtcXc3GaVu+x7IKM5tmuaSaAtJa4Z/S+PZ6R09Wphirn6RDeUEVprAl14VEGstGVwOREj54SgMd+
It0rk4Xz1DoumH5i/9xqvRn0xLgDE3R7BplYNtP4avaOXqGRpWnCZKZCSVWjHqnjAshOG5oAyaTr
v6t5T3vofBjRf+VHQCl0jLpwHrVRrzse0zzGL/KvtKvLfcEioH8yCgs26OMkNJaya4btFeElMLZF
aWyF3nkzrHDpinV6KMsFLtRWJKfkDwAAAP//AwAMYddzywMAAA==
headers:
CF-RAY:
- 971b3f84cf146897-SJC
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 19 Aug 2025 17:07:38 GMT
Server:
- cloudflare
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
access-control-expose-headers:
- X-Request-ID
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
openai-organization:
- crewai-iuxna1
openai-processing-ms:
- '1900'
openai-project:
- proj_xitITlrFeen7zjNSzML82h9x
openai-version:
- '2020-10-01'
x-envoy-upstream-service-time:
- '2551'
x-ratelimit-limit-requests:
- '10000'
x-ratelimit-limit-tokens:
- '30000000'
x-ratelimit-remaining-requests:
- '9999'
x-ratelimit-remaining-tokens:
- '29999629'
x-ratelimit-reset-requests:
- 6ms
x-ratelimit-reset-tokens:
- 0s
x-request-id:
- req_f4181fe581264993ac5c6deba4f1c287
status:
code: 200
message: OK
- request:
body: '{"messages": [{"role": "system", "content": "You are Iterating Agent. You
are an agent that iterates a given number of times\nYour personal goal is: Call
the iterating tool 5 times\nYou ONLY have access to the following tools, and
should NEVER make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool
Arguments: {''input_text'': {''description'': None, ''type'': ''str''}}\nTool
Description: A tool that iterates a given number of times\n\nIMPORTANT: Use
the following format in your response:\n\n```\nThought: you should always think
about what to do\nAction: the action to take, only one name of [iterating_tool],
just the name, exactly as it''s written.\nAction Input: the input to the action,
just a simple JSON object, enclosed in curly braces, using \" to wrap keys and
values.\nObservation: the result of the action\n```\n\nOnce all necessary information
is gathered, return the following format:\n\n```\nThought: I now know the final
answer\nFinal Answer: the final answer to the original input question\n```"},
{"role": "user", "content": "\nCurrent Task: Call the iterating tool 5 times\n\nThis
is the expected criteria for your final answer: A list of the iterations\nyou
MUST return the actual complete content as the final answer, not a summary.\n\nBegin!
This is VERY important to you, use the tools available and give your best Final
Answer, your job depends on it!\n\nThought:"}, {"role": "assistant", "content":
"```\nThought: I need to call the iterating tool the first time.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"First iteration\"}\nObservation: Iteration 0: First
iteration"}, {"role": "assistant", "content": "```\nThought: I have completed
the first iteration and need to proceed to the second one.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"Second iteration\"}\nObservation: Iteration 0: Second
iteration"}], "model": "gpt-4o", "stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '1922'
content-type:
- application/json
cookie:
- __cf_bm=6OZC5kPO9TEmVSACP2sSOZK9ZEZ5I4T_VUlfmzsoY9Y-1755623255-1.0.1.1-ek3SaNBOhXmCg7K3J7LIsE0aCrnK5YfSumHDT6nc8Df1Zh3bzMLHLDqTUwtqiG8SwxiIFXeGP4.Vt2sx9b3FCkxoyrqNpgrBL5DAffAGHm8;
_cfuvid=R_H7SrOF3QFEWePZfvxzuyKWZAt5ulsNbP28.6DC9wM-1755623255760-0.0.1.1-604800000
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.93.0
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.93.0
x-stainless-raw-response:
- 'true'
x-stainless-read-timeout:
- '600.0'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.9
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: !!binary |
H4sIAAAAAAAAAwAAAP//hFNNb9swDL37VxA6J0XmfCz1bRswNBh2GXrYsBSOItG2ElsUJLrIUOS/
D7KT2N067CIIfHxP5CP1kgAIo0UGQlWSVePq6afVl9Xq9HAI3z6uj8eHz6os9OGrvt98T9sfYhIZ
tD+g4ivrTlHjamRDtoeVR8kYVd+9Xy5X6TxdrjugIY11pJWOpwuaprN0MZ2tp7PVhViRURhEBj8T
AICX7owlWo0nkcFsco00GIIsUWS3JADhqY4RIUMwgaVlMRlARZbRdlXvdrutfayoLSvOYAOVfEa4
dIEauEIIqMhqMIxexs5AWg0WI0rgPKnLNeZyZbwGsni3tR9UzM6uRFvmTFRf47CxruUMXrbCxFvO
eOKtyGArHjuR23tbcR4X77Fog4ze2bauR4C0lrhjdLY9XZDzzaiaSudpH/6gisJYE6rcowxkoymB
yYkOPScAT91A2lceC+epcZwzHbF7bn6/6PXEsAIjdH0BmVjWQ3wxTydv6OUaWZo6jEYqlFQV6oE6
zF+22tAISEZd/13NW9p95/2E/is/AEqhY9S586iNet3xkOYx/pB/pd1c7goWAf2zUZizQR8nobGQ
bd0vrwi/AmOTF8aW6J03/QYXLl+s9kUxw5lai+Sc/AYAAP//AwDpY7tZygMAAA==
headers:
CF-RAY:
- 971b3f958e746897-SJC
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 19 Aug 2025 17:07:39 GMT
Server:
- cloudflare
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
access-control-expose-headers:
- X-Request-ID
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
openai-organization:
- crewai-iuxna1
openai-processing-ms:
- '890'
openai-project:
- proj_xitITlrFeen7zjNSzML82h9x
openai-version:
- '2020-10-01'
x-envoy-upstream-service-time:
- '906'
x-ratelimit-limit-project-tokens:
- '30000000'
x-ratelimit-limit-requests:
- '10000'
x-ratelimit-limit-tokens:
- '30000000'
x-ratelimit-remaining-project-tokens:
- '29999577'
x-ratelimit-remaining-requests:
- '9999'
x-ratelimit-remaining-tokens:
- '29999577'
x-ratelimit-reset-project-tokens:
- 0s
x-ratelimit-reset-requests:
- 6ms
x-ratelimit-reset-tokens:
- 0s
x-request-id:
- req_b3632e88268747218e4cc4cc08d87bca
status:
code: 200
message: OK
- request:
body: '{"messages": [{"role": "system", "content": "You are Iterating Agent. You
are an agent that iterates a given number of times\nYour personal goal is: Call
the iterating tool 5 times\nYou ONLY have access to the following tools, and
should NEVER make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool
Arguments: {''input_text'': {''description'': None, ''type'': ''str''}}\nTool
Description: A tool that iterates a given number of times\n\nIMPORTANT: Use
the following format in your response:\n\n```\nThought: you should always think
about what to do\nAction: the action to take, only one name of [iterating_tool],
just the name, exactly as it''s written.\nAction Input: the input to the action,
just a simple JSON object, enclosed in curly braces, using \" to wrap keys and
values.\nObservation: the result of the action\n```\n\nOnce all necessary information
is gathered, return the following format:\n\n```\nThought: I now know the final
answer\nFinal Answer: the final answer to the original input question\n```"},
{"role": "user", "content": "\nCurrent Task: Call the iterating tool 5 times\n\nThis
is the expected criteria for your final answer: A list of the iterations\nyou
MUST return the actual complete content as the final answer, not a summary.\n\nBegin!
This is VERY important to you, use the tools available and give your best Final
Answer, your job depends on it!\n\nThought:"}, {"role": "assistant", "content":
"```\nThought: I need to call the iterating tool the first time.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"First iteration\"}\nObservation: Iteration 0: First
iteration"}, {"role": "assistant", "content": "```\nThought: I have completed
the first iteration and need to proceed to the second one.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"Second iteration\"}\nObservation: Iteration 0: Second
iteration"}, {"role": "assistant", "content": "```\nThought: I have completed
the second iteration and need to proceed to the third one.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"Third iteration\"}\nObservation: Iteration 0: Third
iteration\n\n\nYou ONLY have access to the following tools, and should NEVER
make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool Arguments:
{''input_text'': {''description'': None, ''type'': ''str''}}\nTool Description:
A tool that iterates a given number of times\n\nIMPORTANT: Use the following
format in your response:\n\n```\nThought: you should always think about what
to do\nAction: the action to take, only one name of [iterating_tool], just the
name, exactly as it''s written.\nAction Input: the input to the action, just
a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation:
the result of the action\n```\n\nOnce all necessary information is gathered,
return the following format:\n\n```\nThought: I now know the final answer\nFinal
Answer: the final answer to the original input question\n```"}], "model": "gpt-4o",
"stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '3018'
content-type:
- application/json
cookie:
- __cf_bm=6OZC5kPO9TEmVSACP2sSOZK9ZEZ5I4T_VUlfmzsoY9Y-1755623255-1.0.1.1-ek3SaNBOhXmCg7K3J7LIsE0aCrnK5YfSumHDT6nc8Df1Zh3bzMLHLDqTUwtqiG8SwxiIFXeGP4.Vt2sx9b3FCkxoyrqNpgrBL5DAffAGHm8;
_cfuvid=R_H7SrOF3QFEWePZfvxzuyKWZAt5ulsNbP28.6DC9wM-1755623255760-0.0.1.1-604800000
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.93.0
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.93.0
x-stainless-raw-response:
- 'true'
x-stainless-read-timeout:
- '600.0'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.9
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: !!binary |
H4sIAAAAAAAAAwAAAP//hFPBbtswDL37Kwidk8JzErfxbdgwtNh1GwrMhaPItK3MFgWJLloE+fdB
dhK7W4ddBIGP74l8pI4RgNClyECoRrLqbLv8lH5Nb8k9Hu7T5PP37f361R1Wj1us6IeVYhEYtD+g
4gvrRlFnW2RNZoSVQ8kYVD/cbjZpsko22wHoqMQ20GrLyzUtkzhZL+O7ZZyeiQ1phV5k8DMCADgO
ZyjRlPgiMogXl0iH3ssaRXZNAhCO2hAR0nvtWRoWiwlUZBjNUPVut8vNt4b6uuEMHqCRzwjnLrAE
bhC40a4EzehkaAykKcFgAAmsI3W+htSKescNkMGb3HxUIT27ME1dMFF7icODsT1ncMyFDreC8YVz
kUEuvowq1xdzcZpX77DqvQzmmb5tZ4A0hnhgDL49nZHT1amWauto7/+gikob7ZvCofRkgiueyYoB
PUUAT8NE+jcmC+uos1ww/cLhuTRJRz0x7cCEru7OIBPLdsZK14t39IoSWerWz2YqlFQNlhN1WgDZ
l5pmQDTr+u9q3tMeOx9H9F/5CVAKLWNZWIelVm87ntIchi/yr7Sry0PBwqN71goL1ujCJEqsZN+O
2yv8q2fsikqbGp11elzhyhbrdF9VMcbqTkSn6DcAAAD//wMACjMtRssDAAA=
headers:
CF-RAY:
- 971b3f9bbef86897-SJC
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 19 Aug 2025 17:07:40 GMT
Server:
- cloudflare
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
access-control-expose-headers:
- X-Request-ID
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
openai-organization:
- crewai-iuxna1
openai-processing-ms:
- '1182'
openai-project:
- proj_xitITlrFeen7zjNSzML82h9x
openai-version:
- '2020-10-01'
x-envoy-upstream-service-time:
- '1208'
x-ratelimit-limit-project-tokens:
- '30000000'
x-ratelimit-limit-requests:
- '10000'
x-ratelimit-limit-tokens:
- '30000000'
x-ratelimit-remaining-project-tokens:
- '29999320'
x-ratelimit-remaining-requests:
- '9999'
x-ratelimit-remaining-tokens:
- '29999320'
x-ratelimit-reset-project-tokens:
- 1ms
x-ratelimit-reset-requests:
- 6ms
x-ratelimit-reset-tokens:
- 1ms
x-request-id:
- req_7fc641fabc634f29ae085ef176071402
status:
code: 200
message: OK
- request:
body: '{"messages": [{"role": "system", "content": "You are Iterating Agent. You
are an agent that iterates a given number of times\nYour personal goal is: Call
the iterating tool 5 times\nYou ONLY have access to the following tools, and
should NEVER make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool
Arguments: {''input_text'': {''description'': None, ''type'': ''str''}}\nTool
Description: A tool that iterates a given number of times\n\nIMPORTANT: Use
the following format in your response:\n\n```\nThought: you should always think
about what to do\nAction: the action to take, only one name of [iterating_tool],
just the name, exactly as it''s written.\nAction Input: the input to the action,
just a simple JSON object, enclosed in curly braces, using \" to wrap keys and
values.\nObservation: the result of the action\n```\n\nOnce all necessary information
is gathered, return the following format:\n\n```\nThought: I now know the final
answer\nFinal Answer: the final answer to the original input question\n```"},
{"role": "user", "content": "\nCurrent Task: Call the iterating tool 5 times\n\nThis
is the expected criteria for your final answer: A list of the iterations\nyou
MUST return the actual complete content as the final answer, not a summary.\n\nBegin!
This is VERY important to you, use the tools available and give your best Final
Answer, your job depends on it!\n\nThought:"}, {"role": "assistant", "content":
"```\nThought: I need to call the iterating tool the first time.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"First iteration\"}\nObservation: Iteration 0: First
iteration"}, {"role": "assistant", "content": "```\nThought: I have completed
the first iteration and need to proceed to the second one.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"Second iteration\"}\nObservation: Iteration 0: Second
iteration"}, {"role": "assistant", "content": "```\nThought: I have completed
the second iteration and need to proceed to the third one.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"Third iteration\"}\nObservation: Iteration 0: Third
iteration\n\n\nYou ONLY have access to the following tools, and should NEVER
make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool Arguments:
{''input_text'': {''description'': None, ''type'': ''str''}}\nTool Description:
A tool that iterates a given number of times\n\nIMPORTANT: Use the following
format in your response:\n\n```\nThought: you should always think about what
to do\nAction: the action to take, only one name of [iterating_tool], just the
name, exactly as it''s written.\nAction Input: the input to the action, just
a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation:
the result of the action\n```\n\nOnce all necessary information is gathered,
return the following format:\n\n```\nThought: I now know the final answer\nFinal
Answer: the final answer to the original input question\n```"}, {"role": "assistant",
"content": "```\nThought: I have completed the third iteration and need to proceed
to the fourth one.\nAction: iterating_tool\nAction Input: {\"input_text\": \"Fourth
iteration\"}\nObservation: Iteration 0: Fourth iteration"}], "model": "gpt-4o",
"stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '3267'
content-type:
- application/json
cookie:
- __cf_bm=6OZC5kPO9TEmVSACP2sSOZK9ZEZ5I4T_VUlfmzsoY9Y-1755623255-1.0.1.1-ek3SaNBOhXmCg7K3J7LIsE0aCrnK5YfSumHDT6nc8Df1Zh3bzMLHLDqTUwtqiG8SwxiIFXeGP4.Vt2sx9b3FCkxoyrqNpgrBL5DAffAGHm8;
_cfuvid=R_H7SrOF3QFEWePZfvxzuyKWZAt5ulsNbP28.6DC9wM-1755623255760-0.0.1.1-604800000
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.93.0
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.93.0
x-stainless-raw-response:
- 'true'
x-stainless-read-timeout:
- '600.0'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.9
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: !!binary |
H4sIAAAAAAAAA4RTTW/bMAy9+1cQOieFmyZO5ts2bECxDwztgB3mwlFk2tZmi4JEFx2C/PdBchKn
W4ddBIGP74l8pPYJgNCVyEGoVrLqbTd/m33INvJ+0NnnT/ff3tTZR72483eSv7xLScwCg3Y/UPGJ
daWotx2yJjPCyqFkDKrX69UqW9wssjQCPVXYBVpjeb6k+SJdLOfpZp5mR2JLWqEXOXxPAAD28Qwl
mgqfRA5RJkZ69F42KPJzEoBw1IWIkN5rz9KwmE2gIsNoYtXb7bYwX1sampZzuIVWPiIcu8AKuEWo
aXDcgmZ0MnQG0lRgMKAE1pE6XmOurrkFMnhVmNcqZOcnomlKJupOcbg1duAc9oXQ4VYyPnEhcijE
+yhyfq8Qh8viHdaDl8E7M3TdBSCNIY6MaNvDETmcjeqosY52/g+qqLXRvi0dSk8mmOKZrIjoIQF4
iAMZnnksrKPecsn0E+Nz2Xo16olpBSb05tURZGLZTfH19XL2gl5ZIUvd+YuRCiVVi9VEneYvh0rT
BZBcdP13NS9pj52PE/qv/AQohZaxKq3DSqvnHU9pDsMP+Vfa2eVYsPDoHrXCkjW6MIkKazl04/IK
/8sz9mWtTYPOOj1ucG3LZbar6xRTtRHJIfkNAAD//wMANy12ZMoDAAA=
headers:
CF-RAY:
- 971b3fa3ea2f6897-SJC
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 19 Aug 2025 17:07:41 GMT
Server:
- cloudflare
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
access-control-expose-headers:
- X-Request-ID
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
openai-organization:
- crewai-iuxna1
openai-processing-ms:
- '780'
openai-project:
- proj_xitITlrFeen7zjNSzML82h9x
openai-version:
- '2020-10-01'
x-envoy-upstream-service-time:
- '878'
x-ratelimit-limit-project-tokens:
- '30000000'
x-ratelimit-limit-requests:
- '10000'
x-ratelimit-limit-tokens:
- '30000000'
x-ratelimit-remaining-project-tokens:
- '29999268'
x-ratelimit-remaining-requests:
- '9999'
x-ratelimit-remaining-tokens:
- '29999268'
x-ratelimit-reset-project-tokens:
- 1ms
x-ratelimit-reset-requests:
- 6ms
x-ratelimit-reset-tokens:
- 1ms
x-request-id:
- req_603a6c645bac468888838d21c64db11f
status:
code: 200
message: OK
- request:
body: '{"messages": [{"role": "system", "content": "You are Iterating Agent. You
are an agent that iterates a given number of times\nYour personal goal is: Call
the iterating tool 5 times\nYou ONLY have access to the following tools, and
should NEVER make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool
Arguments: {''input_text'': {''description'': None, ''type'': ''str''}}\nTool
Description: A tool that iterates a given number of times\n\nIMPORTANT: Use
the following format in your response:\n\n```\nThought: you should always think
about what to do\nAction: the action to take, only one name of [iterating_tool],
just the name, exactly as it''s written.\nAction Input: the input to the action,
just a simple JSON object, enclosed in curly braces, using \" to wrap keys and
values.\nObservation: the result of the action\n```\n\nOnce all necessary information
is gathered, return the following format:\n\n```\nThought: I now know the final
answer\nFinal Answer: the final answer to the original input question\n```"},
{"role": "user", "content": "\nCurrent Task: Call the iterating tool 5 times\n\nThis
is the expected criteria for your final answer: A list of the iterations\nyou
MUST return the actual complete content as the final answer, not a summary.\n\nBegin!
This is VERY important to you, use the tools available and give your best Final
Answer, your job depends on it!\n\nThought:"}, {"role": "assistant", "content":
"```\nThought: I need to call the iterating tool the first time.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"First iteration\"}\nObservation: Iteration 0: First
iteration"}, {"role": "assistant", "content": "```\nThought: I have completed
the first iteration and need to proceed to the second one.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"Second iteration\"}\nObservation: Iteration 0: Second
iteration"}, {"role": "assistant", "content": "```\nThought: I have completed
the second iteration and need to proceed to the third one.\nAction: iterating_tool\nAction
Input: {\"input_text\": \"Third iteration\"}\nObservation: Iteration 0: Third
iteration\n\n\nYou ONLY have access to the following tools, and should NEVER
make up tools that are not listed here:\n\nTool Name: iterating_tool\nTool Arguments:
{''input_text'': {''description'': None, ''type'': ''str''}}\nTool Description:
A tool that iterates a given number of times\n\nIMPORTANT: Use the following
format in your response:\n\n```\nThought: you should always think about what
to do\nAction: the action to take, only one name of [iterating_tool], just the
name, exactly as it''s written.\nAction Input: the input to the action, just
a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation:
the result of the action\n```\n\nOnce all necessary information is gathered,
return the following format:\n\n```\nThought: I now know the final answer\nFinal
Answer: the final answer to the original input question\n```"}, {"role": "assistant",
"content": "```\nThought: I have completed the third iteration and need to proceed
to the fourth one.\nAction: iterating_tool\nAction Input: {\"input_text\": \"Fourth
iteration\"}\nObservation: Iteration 0: Fourth iteration"}, {"role": "assistant",
"content": "```\nThought: I have completed the fourth iteration and need to
proceed to the fifth one.\nAction: iterating_tool\nAction Input: {\"input_text\":
\"Fifth iteration\"}\nObservation: Iteration 0: Fifth iteration"}], "model":
"gpt-4o", "stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '3514'
content-type:
- application/json
cookie:
- __cf_bm=6OZC5kPO9TEmVSACP2sSOZK9ZEZ5I4T_VUlfmzsoY9Y-1755623255-1.0.1.1-ek3SaNBOhXmCg7K3J7LIsE0aCrnK5YfSumHDT6nc8Df1Zh3bzMLHLDqTUwtqiG8SwxiIFXeGP4.Vt2sx9b3FCkxoyrqNpgrBL5DAffAGHm8;
_cfuvid=R_H7SrOF3QFEWePZfvxzuyKWZAt5ulsNbP28.6DC9wM-1755623255760-0.0.1.1-604800000
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.93.0
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.93.0
x-stainless-raw-response:
- 'true'
x-stainless-read-timeout:
- '600.0'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.9
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: !!binary |
H4sIAAAAAAAAA4xTy26jMBTd8xVXXpOK0IRE7KqRInVazaZZTROBY1/AU2NT26QzqvLvI0MSSNuJ
ZsPC58E99/EeABDBSQqEVdSxupGTb8lDcne7pt8Xb/r14XX5tJiufiT1z8f944smoVfo3S9k7qS6
YbpuJDqhVQ8zg9Shd50u5vMkvo2TuANqzVF6Wdm4yUxP4iieTaLlJEqOwkoLhpak8BwAALx3X1+i
4vibpBCFp5caraUlkvRMAiBGS/9CqLXCOqocCQeQaeVQdVXneb5R60q3ZeVSuIeK7hGOKZADlRLm
IBwa6kPZm41aCUUl3Cn7hiaF5w25P6EQpbASxrpBsCEhfGA8IdOKX6WsK2GuM1a6Na66ThHFJWO7
UXmej/tgsGgt9WNQrZQjgCqlXZ/YT2B7RA7nnktdNkbv7AcpKYQStsoMUquV7691uiEdeggAtt1s
24txkcbounGZ0y/Y/W4Rz3s/MmzTgM6TI+i0o3KkWk7DL/wyjo4KaUfbQRhlFfJBOqwSbbnQIyAY
pf5czVfefXKhyv+xHwDGsHHIs8YgF+wy8UAz6I/tX7Rzl7uCiUWzFwwzJ9D4SXAsaCv7OyD2j3VY
Z4VQJZrGiP4YiiabJbuiiDBiSxIcgr8AAAD//wMAnPTcwxUEAAA=
headers:
CF-RAY:
- 971b3faa6ba46897-SJC
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 19 Aug 2025 17:07:43 GMT
Server:
- cloudflare
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
access-control-expose-headers:
- X-Request-ID
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
openai-organization:
- crewai-iuxna1
openai-processing-ms:
- '1392'
openai-project:
- proj_xitITlrFeen7zjNSzML82h9x
openai-version:
- '2020-10-01'
x-envoy-upstream-service-time:
- '1841'
x-ratelimit-limit-requests:
- '10000'
x-ratelimit-limit-tokens:
- '30000000'
x-ratelimit-remaining-requests:
- '9999'
x-ratelimit-remaining-tokens:
- '29999217'
x-ratelimit-reset-requests:
- 6ms
x-ratelimit-reset-tokens:
- 1ms
x-request-id:
- req_19dc255cec9d4763be7d5f597c80e936
status:
code: 200
message: OK
version: 1

View File

@@ -1,9 +1,12 @@
import asyncio
import inspect
import unittest
from typing import Any, Callable, Dict, List
from typing import Callable
from unittest.mock import patch
import pytest
from crewai.agent import Agent
from crewai.crew import Crew
from crewai.task import Task
from crewai.tools import BaseTool, tool
@@ -130,6 +133,7 @@ def test_result_as_answer_in_tool_decorator():
class SyncTool(BaseTool):
"""Test implementation with a synchronous _run method"""
name: str = "sync_tool"
description: str = "A synchronous tool for testing"
@@ -140,6 +144,7 @@ class SyncTool(BaseTool):
class AsyncTool(BaseTool):
"""Test implementation with an asynchronous _run method"""
name: str = "async_tool"
description: str = "An asynchronous tool for testing"
@@ -174,7 +179,7 @@ def test_run_calls_asyncio_run_for_async_tools():
"""Test that asyncio.run is called when using async tools."""
async_tool = AsyncTool()
with patch('asyncio.run') as mock_run:
with patch("asyncio.run") as mock_run:
mock_run.return_value = "Processed test asynchronously"
async_result = async_tool.run(input_text="test")
@@ -186,9 +191,43 @@ def test_run_does_not_call_asyncio_run_for_sync_tools():
"""Test that asyncio.run is NOT called when using sync tools."""
sync_tool = SyncTool()
with patch('asyncio.run') as mock_run:
with patch("asyncio.run") as mock_run:
sync_result = sync_tool.run(input_text="test")
mock_run.assert_not_called()
assert sync_result == "Processed test synchronously"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_max_usage_count_is_respected():
class IteratingTool(BaseTool):
name: str = "iterating_tool"
description: str = "A tool that iterates a given number of times"
def _run(self, input_text: str):
return f"Iteration {input_text}"
tool = IteratingTool(max_usage_count=5)
agent = Agent(
role="Iterating Agent",
goal="Call the iterating tool 5 times",
backstory="You are an agent that iterates a given number of times",
tools=[tool],
)
task = Task(
description="Call the iterating tool 5 times",
expected_output="A list of the iterations",
agent=agent,
)
crew = Crew(
agents=[agent],
tasks=[task],
verbose=True,
)
crew.kickoff()
assert tool.max_usage_count == 5
assert tool.current_usage_count == 5