-
Notifications
You must be signed in to change notification settings - Fork 5k
Description
Description
Bug Report Description:
There is a bug in the invoke() method of CrewStructuredTool related to the handling of synchronous and asynchronous functions.
-
If self.func is a coroutine function, the code uses asyncio.run() to execute it. However, if invoke() is called from within an already running event loop (e.g., in an async context or from another async function), asyncio.run() will raise a "RuntimeError: asyncio.run() cannot be called from a running event loop".
-
The code redundantly calls self.func(**parsed_args, **kwargs) twice for synchronous functions: once inside the try block and again immediately after. This results in the function being executed twice, which is likely unintended and can cause side effects or performance issues.
-
The code checks if the result is a coroutine and, if so, calls asyncio.run(result). This can again cause the same RuntimeError as above if already in an event loop, and also suggests that the function may return a coroutine even if not declared as async, which is confusing and inconsistent.
Steps to Reproduce
- Call invoke() on a CrewStructuredTool instance with an async function as self.func from within an async context.
- Observe the RuntimeError due to nested event loops.
- Call invoke() with a sync function and observe that the function is executed twice.
Expected behavior
- The function should only be called once.
- Async functions should be awaited properly without using asyncio.run() if already in an event loop.
- The code should distinguish clearly between sync and async functions and handle them appropriately.
Screenshots/Code snippets

Operating System
macOS Sonoma
Python Version
3.12
crewAI Version
lasted
crewAI Tools Version
lasted
Virtual Environment
Venv
Evidence
def invoke(
self, input: Union[str, dict], config: Optional[dict] = None, **kwargs: Any
) -> Any:
"""Main method for tool execution."""
parsed_args = self._parse_args(input)
if self.has_reached_max_usage_count():
raise ToolUsageLimitExceeded(
f"Tool '{self.name}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {self.name} tool again."
)
self._increment_usage_count()
if inspect.iscoroutinefunction(self.func):
result = asyncio.run(self.func(**parsed_args, **kwargs))
return result
try:
result = self.func(**parsed_args, **kwargs)
except Exception:
raise
result = self.func(**parsed_args, **kwargs)
if asyncio.iscoroutine(result):
return asyncio.run(result)
return result
Possible Solution
def invoke(self, input: Union[str, dict], config: Optional[dict] = None, **kwargs: Any) -> Any:
parsed_args = self._parse_args(input)
if self.has_reached_max_usage_count():
raise ToolUsageLimitExceeded(...)
self._increment_usage_count()
# 修复后:只调用一次,且保留协程/协程结果处理
if inspect.iscoroutinefunction(self.func):
result = asyncio.run(self.func(**parsed_args, **kwargs))
else:
result = self.func(**parsed_args, **kwargs)
if asyncio.iscoroutine(result):
return asyncio.run(result)
return result
Additional context
no