From eefe0e42ac9bf0eacf52cf8e28639bb3a35c01d3 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Sat, 16 May 2026 02:46:35 +0800 Subject: [PATCH] fix: surface streamed tool calls when available_functions is absent --- lib/crewai/src/crewai/llm.py | 43 +++++++++++++++++++++++++----------- lib/crewai/tests/test_llm.py | 7 ++++-- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/crewai/src/crewai/llm.py b/lib/crewai/src/crewai/llm.py index 52e3b0b9f..e452dc394 100644 --- a/lib/crewai/src/crewai/llm.py +++ b/lib/crewai/src/crewai/llm.py @@ -940,6 +940,21 @@ class LLM(BaseLLM): self._track_token_usage_internal(usage_info) self._handle_streaming_callbacks(callbacks, usage_info, last_chunk) + if accumulated_tool_args and not available_functions: + tool_calls_list: list[ChatCompletionDeltaToolCall] = [ + ChatCompletionDeltaToolCall( + index=idx, + function=Function( + name=tool_arg.function.name, + arguments=tool_arg.function.arguments, + ), + ) + for idx, tool_arg in sorted(accumulated_tool_args.items()) + if tool_arg.function.name + ] + if tool_calls_list: + return tool_calls_list + if not tool_calls or not available_functions: if response_model and self.is_litellm: instructor_instance = InternalInstructor( @@ -1535,8 +1550,7 @@ class LLM(BaseLLM): if usage_info: self._track_token_usage_internal(usage_info) - if accumulated_tool_args and available_functions: - # Convert accumulated tool args to ChatCompletionDeltaToolCall objects + if accumulated_tool_args: tool_calls_list: list[ChatCompletionDeltaToolCall] = [ ChatCompletionDeltaToolCall( index=idx, @@ -1545,21 +1559,24 @@ class LLM(BaseLLM): arguments=tool_arg.function.arguments, ), ) - for idx, tool_arg in accumulated_tool_args.items() + for idx, tool_arg in sorted(accumulated_tool_args.items()) if tool_arg.function.name ] if tool_calls_list: - result = self._handle_streaming_tool_calls( - tool_calls=tool_calls_list, - accumulated_tool_args=accumulated_tool_args, - available_functions=available_functions, - from_task=from_task, - from_agent=from_agent, - response_id=response_id, - ) - if result is not None: - return result + if available_functions: + result = self._handle_streaming_tool_calls( + tool_calls=tool_calls_list, + accumulated_tool_args=accumulated_tool_args, + available_functions=available_functions, + from_task=from_task, + from_agent=from_agent, + response_id=response_id, + ) + if result is not None: + return result + else: + return tool_calls_list usage_dict = self._usage_to_dict(usage_info) self._handle_emit_call_events( diff --git a/lib/crewai/tests/test_llm.py b/lib/crewai/tests/test_llm.py index b4002b8e9..caf864db1 100644 --- a/lib/crewai/tests/test_llm.py +++ b/lib/crewai/tests/test_llm.py @@ -624,12 +624,15 @@ def test_handle_streaming_tool_calls_no_available_functions( ], tools=[get_weather_tool_schema], ) - assert response == "" + assert isinstance(response, list) + assert len(response) == 1 + assert response[0].function.name == "get_weather" + assert response[0].function.arguments == '{"location":"New York, NY"}' assert_event_count( mock_emit=mock_emit, expected_stream_chunk=9, - expected_completed_llm_call=1, + expected_completed_llm_call=0, expected_final_chunk_result='{"location":"New York, NY"}', )