Skip to content

fix(experimental): use async LLM calls in AgentExecutor - #6684

Open
suqinghen wants to merge 7 commits into
crewAIInc:mainfrom
suqinghen:feature/async-agent-executor-llm
Open

fix(experimental): use async LLM calls in AgentExecutor#6684
suqinghen wants to merge 7 commits into
crewAIInc:mainfrom
suqinghen:feature/async-agent-executor-llm

Conversation

@suqinghen

@suqinghen suqinghen commented Jul 27, 2026

Copy link
Copy Markdown

Summary

  • make the experimental AgentExecutor ReAct and native-tool LLM routers async
  • route the main LLM request paths through aget_llm_response() and LLM.acall()
  • preserve sync-only custom LLM support through asyncio.to_thread(LLM.call)
  • offload synchronous request preparation, response validation, and RPM enforcement
  • update provider, tool, cache, and telemetry fixtures for async execution
  • avoid an event-loop deadlock in the synchronous CrewAI Files upload-cache bridge

Fixes #6683

Root cause

AgentExecutor.ainvoke() uses Flow.kickoff_async(), while both LLM router methods called the synchronous get_llm_response(). Provider requests therefore occupied default-executor workers for their full duration and bypassed provider-native async clients.

Moving the routers to LLM.acall() also exposed two existing compatibility assumptions:

  1. several tests and custom doubles only configured LLM.call() or synchronous provider clients
  2. the CrewAI Files synchronous upload-cache bridge waited on a coroutine scheduled onto the same running event loop, which could deadlock

Behavior

Providers with async support now run through LLM.acall(). Custom providers that raise NotImplementedError from acall() continue through a worker-thread LLM.call() fallback.

Request preparation and response finalization run in worker threads so synchronous hooks and validation do not block the event loop. ReAct parsing, native-tool response handling, structured responses, callbacks, and error routing retain their existing semantics.

The forced-final-answer path used after maximum iterations continues to use the existing synchronous LLM.call() behavior.

Scope

This change covers the main LLM routing paths in crewai.experimental.AgentExecutor, their provider test fixtures, and the CrewAI Files upload-cache bridge reached by async multimodal execution.

Async native tool execution remains covered by #6616 and #6622.

Test plan

  • focused async executor, provider, tool, telemetry, multimodal, and upload-cache suite: 728 passed, 13 skipped
  • CrewAI Files suite: 168 passed, 4 skipped
  • full workspace integration run on Python 3.13 after applying separately scoped current-main CLI and deterministic-fixture fixes: 5970 passed, 70 skipped
  • Ruff checks on changed production and test files
  • git diff --check

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

AgentExecutor now awaits asynchronous LLM responses for parsing and native-tool calls. Sync-only providers use LLM.call() in a worker thread. Cache execution and provider tests now support asynchronous control flow.

Changes

Async LLM execution

Layer / File(s) Summary
Async response compatibility
lib/crewai/src/crewai/utilities/agent_utils.py, lib/crewai/tests/agents/test_async_agent_executor.py
aget_llm_response offloads preparation and finalization, awaits llm.acall(), and uses asyncio.to_thread() for NotImplementedError fallback. Tests verify worker-thread execution and hook ordering.
AgentExecutor async paths
lib/crewai/src/crewai/experimental/agent_executor.py, lib/crewai/tests/agents/test_agent_executor.py
call_llm_and_parse and call_llm_native_tools await aget_llm_response and offload RPM limiting. Tests cover routing, tool fallback, parser errors, and context errors.
Synchronous cache execution in async contexts
lib/crewai-files/src/crewai_files/cache/upload_cache.py, lib/crewai-files/tests/test_upload_cache.py
UploadCache._run_sync uses a dedicated context-preserving worker thread when called inside an event loop. An async regression test covers synchronous cache access.
Async provider and execution test coverage
lib/crewai/tests/agents/test_agent.py, lib/crewai/tests/agents/test_native_tool_calling.py, lib/crewai/tests/llms/*, lib/crewai/tests/telemetry/*, lib/crewai/tests/test_crew_multimodal.py, lib/crewai/tests/test_tool_cache_default.py, lib/crewai/tests/tools/test_tool_failure.py
Provider, telemetry, multimodal, and tool tests now mock or implement acall() and verify awaited calls, arguments, streaming behavior, and async fixtures.

Sequence Diagram(s)

sequenceDiagram
  participant AgentExecutor
  participant aget_llm_response
  participant LLM
  participant asyncio_to_thread
  AgentExecutor->>aget_llm_response: await LLM request
  aget_llm_response->>LLM: await acall()
  alt Async call unavailable
    LLM-->>aget_llm_response: NotImplementedError
    aget_llm_response->>asyncio_to_thread: run call()
    asyncio_to_thread->>LLM: execute call()
  else Async call available
    LLM-->>aget_llm_response: response
  end
  aget_llm_response-->>AgentExecutor: finalized response
Loading

Suggested reviewers: greysonlalonde

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: enabling asynchronous LLM calls in the experimental AgentExecutor.
Description check ✅ Passed The description directly explains the asynchronous routing changes, compatibility fallback, related fixes, scope, and testing.
Linked Issues check ✅ Passed The changes satisfy issue #6683 by awaiting aget_llm_response, using LLM.acall(), and preserving sync-only fallback support.
Out of Scope Changes check ✅ Passed The provider, tool, telemetry, multimodal, and upload-cache changes support the async routing objectives and introduce no unrelated code changes.
✨ 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.

@suqinghen
suqinghen force-pushed the feature/async-agent-executor-llm branch from e61a1d4 to fcafc13 Compare July 27, 2026 09:49
@suqinghen
suqinghen marked this pull request as ready for review July 27, 2026 09:52
@suqinghen

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@lorenzejay lorenzejay self-assigned this Jul 27, 2026
@lorenzejay

Copy link
Copy Markdown
Collaborator

love this pr!!

@lorenzejay lorenzejay left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two async-path concerns worth addressing before merge:

Comment thread lib/crewai/src/crewai/experimental/agent_executor.py
Comment thread lib/crewai/src/crewai/experimental/agent_executor.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/crewai/src/crewai/utilities/agent_utils.py (1)

601-624: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Docstring doesn't document the new sync-fallback/threading behavior.

The function now silently retries via a worker-thread llm.call() when llm.acall() raises NotImplementedError, and offloads _prepare_llm_call/_validate_and_finalize_llm_response to threads. This non-obvious control flow isn't mentioned in the docstring (e.g., in a Note: section or the Raises: block).

📝 Suggested docstring addition
     Raises:
         Exception: If an error occurs.
         ValueError: If the response is None or empty.
+
+    Note:
+        If `llm.acall()` raises `NotImplementedError` (sync-only custom LLMs),
+        this function falls back to running `llm.call()` in a worker thread via
+        `asyncio.to_thread` so the caller's event loop is not blocked.
     """

As per coding guidelines, "Document public APIs and complex logic in Python code."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/utilities/agent_utils.py` around lines 601 - 624,
Update the docstring for the async LLM-call function surrounding the documented
arguments and return behavior to describe that NotImplementedError from
llm.acall() triggers a worker-thread fallback using llm.call(), and that
_prepare_llm_call and _validate_and_finalize_llm_response execute in worker
threads. Include this control flow in an appropriate Note or Raises section
without changing implementation behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/crewai/src/crewai/utilities/agent_utils.py`:
- Around line 601-624: Update the docstring for the async LLM-call function
surrounding the documented arguments and return behavior to describe that
NotImplementedError from llm.acall() triggers a worker-thread fallback using
llm.call(), and that _prepare_llm_call and _validate_and_finalize_llm_response
execute in worker threads. Include this control flow in an appropriate Note or
Raises section without changing implementation behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2403b5c9-de12-4514-a8b7-760cbaa9ba78

📥 Commits

Reviewing files that changed from the base of the PR and between fcafc13 and fab7367.

📒 Files selected for processing (4)
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/src/crewai/utilities/agent_utils.py
  • lib/crewai/tests/agents/test_agent_executor.py
  • lib/crewai/tests/agents/test_async_agent_executor.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/tests/agents/test_agent_executor.py

@lorenzejay
lorenzejay enabled auto-merge (squash) July 30, 2026 05:52
auto-merge was automatically disabled July 31, 2026 11:59

Head branch was pushed to by a user without write access

@suqinghen

Copy link
Copy Markdown
Author

Refactored associated unit tests to fix mismatches between test cases and the actual code logic.

@suqinghen
suqinghen force-pushed the feature/async-agent-executor-llm branch from bf60ccb to 985c8e1 Compare July 31, 2026 12:14
@suqinghen
suqinghen requested a review from lorenzejay August 3, 2026 04:04
@lorenzejay
lorenzejay enabled auto-merge (squash) August 4, 2026 16:18
auto-merge was automatically disabled August 7, 2026 08:43

Head branch was pushed to by a user without write access

@suqinghen
suqinghen force-pushed the feature/async-agent-executor-llm branch from 9ab9a52 to 64f2a11 Compare August 7, 2026 08:43
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/tests/llms/azure/test_azure.py`:
- Around line 364-366: Require the tools argument in both test assertions: in
lib/crewai/tests/llms/azure/test_azure.py lines 364-366 and
lib/crewai/tests/llms/google/test_google.py lines 344-346, replace the
conditional checks with direct assertions that await_args.kwargs["tools"] exists
and is nonempty.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ca84485-070d-46d7-b716-46c7e5ed59f9

📥 Commits

Reviewing files that changed from the base of the PR and between 18c52c4 and 64f2a11.

📒 Files selected for processing (20)
  • lib/crewai-files/src/crewai_files/cache/upload_cache.py
  • lib/crewai-files/tests/test_upload_cache.py
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/src/crewai/utilities/agent_utils.py
  • lib/crewai/tests/agents/test_agent.py
  • lib/crewai/tests/agents/test_agent_executor.py
  • lib/crewai/tests/agents/test_async_agent_executor.py
  • lib/crewai/tests/agents/test_native_tool_calling.py
  • lib/crewai/tests/llms/anthropic/test_anthropic.py
  • lib/crewai/tests/llms/azure/conftest.py
  • lib/crewai/tests/llms/azure/test_azure.py
  • lib/crewai/tests/llms/azure/test_azure_async.py
  • lib/crewai/tests/llms/bedrock/test_bedrock.py
  • lib/crewai/tests/llms/google/test_google.py
  • lib/crewai/tests/llms/openai/test_openai.py
  • lib/crewai/tests/llms/openai/test_tools_reasoning_effort_retry.py
  • lib/crewai/tests/telemetry/test_flow_crew_span_integration.py
  • lib/crewai/tests/test_crew_multimodal.py
  • lib/crewai/tests/test_tool_cache_default.py
  • lib/crewai/tests/tools/test_tool_failure.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • lib/crewai/src/crewai/utilities/agent_utils.py
  • lib/crewai/tests/agents/test_async_agent_executor.py
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/tests/agents/test_agent_executor.py

Comment thread lib/crewai/tests/llms/azure/test_azure.py Outdated
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
lib/crewai/tests/llms/bedrock/test_bedrock.py (1)

273-315: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the skipped async argument test with an executable test.

Line 273 skips the test, so it cannot detect regressions in the messages passed to acall(). Use a deterministic unit-level fixture that does not hang, then remove the skip.

As per coding guidelines, “Write unit tests for new functionality, focusing on behavior rather than implementation details.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/llms/bedrock/test_bedrock.py` around lines 273 - 315,
Replace the skip on test_bedrock_completion_call_arguments with a deterministic
unit-level setup that exercises the mocked BedrockCompletion.acall without
invoking a potentially hanging full crew execution. Keep the assertions
verifying that acall is awaited and receives messages containing “hello world,”
and make the test executable as an async test using the project’s established
async test conventions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/tests/llms/bedrock/test_bedrock.py`:
- Around line 406-408: Update the assertions in the awaited request test to
require the “tools” keyword unconditionally, then verify its value is non-null
and non-empty; remove the surrounding conditional so the test fails when acall()
omits tools.

---

Nitpick comments:
In `@lib/crewai/tests/llms/bedrock/test_bedrock.py`:
- Around line 273-315: Replace the skip on
test_bedrock_completion_call_arguments with a deterministic unit-level setup
that exercises the mocked BedrockCompletion.acall without invoking a potentially
hanging full crew execution. Keep the assertions verifying that acall is awaited
and receives messages containing “hello world,” and make the test executable as
an async test using the project’s established async test conventions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ed486e2d-07b8-4eed-8d23-1d83452acb0e

📥 Commits

Reviewing files that changed from the base of the PR and between 17f107c and b56ae6b.

📒 Files selected for processing (20)
  • lib/crewai-files/src/crewai_files/cache/upload_cache.py
  • lib/crewai-files/tests/test_upload_cache.py
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/src/crewai/utilities/agent_utils.py
  • lib/crewai/tests/agents/test_agent.py
  • lib/crewai/tests/agents/test_agent_executor.py
  • lib/crewai/tests/agents/test_async_agent_executor.py
  • lib/crewai/tests/agents/test_native_tool_calling.py
  • lib/crewai/tests/llms/anthropic/test_anthropic.py
  • lib/crewai/tests/llms/azure/conftest.py
  • lib/crewai/tests/llms/azure/test_azure.py
  • lib/crewai/tests/llms/azure/test_azure_async.py
  • lib/crewai/tests/llms/bedrock/test_bedrock.py
  • lib/crewai/tests/llms/google/test_google.py
  • lib/crewai/tests/llms/openai/test_openai.py
  • lib/crewai/tests/llms/openai/test_tools_reasoning_effort_retry.py
  • lib/crewai/tests/telemetry/test_flow_crew_span_integration.py
  • lib/crewai/tests/test_crew_multimodal.py
  • lib/crewai/tests/test_tool_cache_default.py
  • lib/crewai/tests/tools/test_tool_failure.py
🚧 Files skipped from review as they are similar to previous changes (19)
  • lib/crewai/tests/llms/openai/test_tools_reasoning_effort_retry.py
  • lib/crewai-files/tests/test_upload_cache.py
  • lib/crewai/tests/agents/test_async_agent_executor.py
  • lib/crewai/tests/llms/azure/test_azure_async.py
  • lib/crewai/tests/tools/test_tool_failure.py
  • lib/crewai/tests/telemetry/test_flow_crew_span_integration.py
  • lib/crewai/tests/test_crew_multimodal.py
  • lib/crewai/tests/agents/test_agent.py
  • lib/crewai/src/crewai/utilities/agent_utils.py
  • lib/crewai/tests/llms/google/test_google.py
  • lib/crewai/tests/test_tool_cache_default.py
  • lib/crewai/tests/agents/test_native_tool_calling.py
  • lib/crewai/tests/llms/azure/conftest.py
  • lib/crewai-files/src/crewai_files/cache/upload_cache.py
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/tests/llms/anthropic/test_anthropic.py
  • lib/crewai/tests/llms/openai/test_openai.py
  • lib/crewai/tests/agents/test_agent_executor.py
  • lib/crewai/tests/llms/azure/test_azure.py

Comment thread lib/crewai/tests/llms/bedrock/test_bedrock.py
@suqinghen
suqinghen requested a review from lorenzejay August 10, 2026 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] AgentExecutor.ainvoke routes LLM requests through LLM.call

2 participants