Skip to content

fix: prioritize tool calls over text when available_functions is None#4802

Closed
alvinttang wants to merge 1 commit into
crewAIInc:mainfrom
alvinttang:fix/prioritize-tool-calls-over-text-response
Closed

fix: prioritize tool calls over text when available_functions is None#4802
alvinttang wants to merge 1 commit into
crewAIInc:mainfrom
alvinttang:fix/prioritize-tool-calls-over-text-response

Conversation

@alvinttang
Copy link
Copy Markdown

@alvinttang alvinttang commented Mar 11, 2026

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_response returned the text response instead of the tool calls when available_functions was None. 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:

if (not tool_calls or not available_functions) and text_response:
    return text_response

When tool_calls is truthy (tool calls exist), not available_functions is True (executor passes None), and text_response is 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_functions is None, 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:

  • Responses with only text (no tool calls) — still returns text
  • Responses with tool calls and available_functions provided — still executes tools
  • Responses with no text and no tool calls — falls through to step 8

Test plan

  • Verify LLM responses with both text + tool calls return tool calls when available_functions=None
  • Verify text-only responses still work correctly
  • Verify tool execution still works when available_functions is provided
  • Run existing test suite: pytest 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=None case.

Overview
Fixes non-streaming LLM response handling so that when a provider returns both tool_calls and text in the same message and available_functions is None, the method returns the tool_calls instead of prematurely treating the text as the final answer.

This reorders the decision logic in _handle_non_streaming_response to give tool calls priority (enabling executors to run native tools) while preserving the existing paths for text-only responses and for executing tools when available_functions is provided.

Written by Cursor Bugbot for commit 10cdfd6. This will update automatically on new commits. Configure here.

…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>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

@alvinttang
Copy link
Copy Markdown
Author

Closing — superseded by #4872 which also fixes the async path.

@alvinttang alvinttang closed this Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Native tool calls are discarded if LLM returns a text response

1 participant