Skip to content

fix(tools): await coroutines returned by sync wrappers in CrewStructuredTool.ainvoke - #7481

Open
Rainmemery wants to merge 1 commit into
crewAIInc:mainfrom
Rainmemery:fix/ainvoke-async-wrapper
Open

Rainmemery wants to merge 1 commit into
crewAIInc:mainfrom
Rainmemery:fix/ainvoke-async-wrapper

Conversation

@Rainmemery

Copy link
Copy Markdown

Fixes #7474

Summary

BaseTool.to_structured_tool() stores func=self._run (lib/crewai/src/crewai/tools/base_tool.py:413), and Tool._run is a sync method that returns self.func(...) as-is - for an @tool-decorated async function that is a bare coroutine. Inside CrewStructuredTool.ainvoke(), inspect.iscoroutinefunction(self.func) is therefore False, the call is dispatched through run_in_executor, and the coroutine object was handed straight back to the caller: the tool body never executed, the observation fed to the LLM was the literal <coroutine object fetch_data at 0x...> repr, and the coroutine was garbage-collected with RuntimeWarning: coroutine 'fetch_data' was never awaited.

Every sibling branch already handles this case - invoke() (lines 440-447) checks asyncio.iscoroutine(result), BaseTool.run and Tool.run re-run via asyncio.run, and Tool._arun awaits awaitables. Only the async branch of CrewStructuredTool missed the check.

Change

The executor branch of ainvoke() now awaits the result when it is awaitable before returning it, mirroring the sibling branches.

Test plan

pytest lib/crewai/tests/tools/test_structured_tool.py - 26 passed (24 existing + 2 new):

  • test_ainvoke_awaits_coroutine_from_sync_wrapper: a sync wrapper function returning a coroutine; previously ainvoke returned the coroutine object itself, now the tool body runs and its result is returned;
  • test_ainvoke_through_to_structured_tool_resolves_async_tool_result: the exact path from the issue (@tool async def -> to_structured_tool() -> ainvoke); previously the caller received <coroutine object ...>, now the awaited tool result.

Full tests/tools/ directory: 241 passed; the two test_tool_failure.py failures reproduce on pristine main in this environment (mock/transport environment issue, unrelated to this change).

…redTool.ainvoke (crewAIInc#7474)

BaseTool.to_structured_tool() stores func=self._run, and Tool._run is a sync
method that returns self.func(...) as-is - for an async tool that is a bare
coroutine. Inside CrewStructuredTool.ainvoke the sync branch dispatches through
run_in_executor, so inspect.iscoroutinefunction(self.func) is False and the
coroutine object was handed straight back to the caller: the tool body never
ran, the observation fed to the LLM was the literal '<coroutine object ...>'
repr, and the coroutine was GC'd with 'coroutine was never awaited'.

Every sibling branch already resolves this (invoke() checks
asyncio.iscoroutine, BaseTool.run and Tool.run re-run via asyncio.run,
Tool._arun awaits awaitables); only the async branch of CrewStructuredTool
missed the check. Await the result when it is awaitable before returning it.

Regression tests cover the direct sync-wrapper case and the
@tool(async_fn).to_structured_tool() path from the issue.
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 6dcc932d-48c2-4000-9a53-bc23fc0c100c

📥 Commits

Reviewing files that changed from the base of the PR and between c6ff786 and 6f6e853.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/tools/structured_tool.py
  • lib/crewai/tests/tools/test_structured_tool.py

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

CrewStructuredTool.ainvoke now awaits coroutine results returned by synchronous wrappers. Tests cover direct wrappers and async functions decorated with @tool after conversion to structured tools.

Changes

Structured tool async invocation

Layer / File(s) Summary
Await executor results and validate async tool paths
lib/crewai/src/crewai/tools/structured_tool.py, lib/crewai/tests/tools/test_structured_tool.py
ainvoke awaits executor results when they are awaitable. Tests verify resolved results and single execution for wrapped async functions and @tool-decorated functions.

Suggested reviewers: joaomdmoura

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 6f6e8

The async invocation change resolves coroutine results for the affected wrapper paths without an identified remaining merge risk.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: awaiting coroutines returned by synchronous wrappers in CrewStructuredTool.ainvoke.
Description check ✅ Passed The description links issue #7474, explains the cause and fix, and documents regression tests and test results. It uses a "Test plan" heading instead of the template's "Verification" heading and does …
Linked Issues check ✅ Passed Issue #7474 requires CrewStructuredTool.ainvoke() to resolve awaitable results returned by synchronous wrappers. In structured_tool.py, the executor result is stored in result, checked with `ins…
Out of Scope Changes check ✅ Passed The pull request changes only the affected CrewStructuredTool.ainvoke() branch and adds tests for the two issue-specific execution paths. The changes support Issue #7474 and show no unrelated implem…
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 2 files.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant