fix: prioritize tool calls over text when available_functions is None#4802
fix: prioritize tool calls over text when available_functions is None#4802alvinttang wants to merge 1 commit into
Conversation
…s is None When an LLM returns both a text response and tool calls in the same message (common with Anthropic models via OpenRouter/Bedrock), the previous logic returned the text response instead of the tool calls when available_functions was None. This caused the executor to treat the text as a final answer, silently discarding the tool calls. The root cause was the condition ordering: step 5 checked `(not tool_calls or not available_functions) and text_response`, which matched when both tool_calls and text_response were present but available_functions was None. Fix by checking for tool calls first (step 5) before falling back to text response (step 6). This ensures the executor receives the tool calls and can execute them properly. Fixes crewAIInc#4788 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| # This allows the caller (e.g., executor) to handle tool execution | ||
| if tool_calls and not available_functions: | ||
| return tool_calls | ||
|
|
There was a problem hiding this comment.
Async method not updated with same fix
High Severity
The fix to prioritize tool calls over text when available_functions is None was only applied to _handle_non_streaming_response but not to its async counterpart _ahandle_non_streaming_response. The async method at line 1371 still has the original buggy condition if (not tool_calls or not available_functions) and text_response:, which means the exact same bug — discarding tool calls when the LLM returns both text and tool calls — still occurs for all async code paths.
Additional Locations (1)
|
Closing — superseded by #4872 which also fixes the async path. |


Summary
Fixes #4788 - Native tool calls are discarded if LLM returns a text response alongside them.
When an LLM (e.g., Anthropic via OpenRouter/Bedrock) returns both a text response and tool calls in the same message, the previous logic in
_handle_non_streaming_responsereturned the text response instead of the tool calls whenavailable_functionswasNone. This caused the CrewAgentExecutor to treat the text as a final answer, silently discarding the tool calls that should have been executed.Root cause
The condition at step 5 was:
When
tool_callsis truthy (tool calls exist),not available_functionsis True (executor passes None), andtext_responseis truthy, this condition matches and returns the text — never reaching step 6 which would return the tool calls.Fix
Swap the order of steps 5 and 6 so that tool calls are returned first when
available_functionsisNone, before falling back to text response. The new step 6 now only returns text when there are genuinely no tool calls (not tool_calls).This is a minimal, focused change (4 lines of logic) that preserves all existing behavior for:
available_functionsprovided — still executes toolsTest plan
available_functions=Noneavailable_functionsis providedpytest lib/crewai/tests/test_llm.py🤖 Generated with Claude Code
Note
Medium Risk
Changes core LLM response selection logic and could affect downstream callers that previously received text in mixed text+tool responses, though the change is small and narrowly scoped to the
available_functions=Nonecase.Overview
Fixes non-streaming LLM response handling so that when a provider returns both
tool_callsand text in the same message andavailable_functionsisNone, the method returns thetool_callsinstead of prematurely treating the text as the final answer.This reorders the decision logic in
_handle_non_streaming_responseto give tool calls priority (enabling executors to run native tools) while preserving the existing paths for text-only responses and for executing tools whenavailable_functionsis provided.Written by Cursor Bugbot for commit 10cdfd6. This will update automatically on new commits. Configure here.