Skip to content

[BUG] There is a bug in the invoke() method of CrewStructuredTool related to the handling of synchronous and asynchronous functions. #3447

@hiyoung123

Description

@hiyoung123

Description

Bug Report Description:

There is a bug in the invoke() method of CrewStructuredTool related to the handling of synchronous and asynchronous functions.

  1. 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".

  2. 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.

  3. 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

Image

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions