From ad82e52d39c4a9a098e270a68785b61b421bcd4c Mon Sep 17 00:00:00 2001 From: nicoferdi96 Date: Wed, 4 Mar 2026 12:04:23 +0100 Subject: [PATCH] fix(gemini): group parallel function_response parts in a single Content object (#4693) * fix(gemini): group parallel function_response parts in a single Content object When Gemini makes N parallel tool calls, the API requires all N function_response parts in one Content object. Previously each tool result created a separate Content, causing 400 INVALID_ARGUMENT errors. Merge consecutive function_response parts into the existing Content instead of appending new ones. * Address change requested - function_response is a declared field on the types.Part Pydantic model so hasattr can be replaced with p.function_response is not None --- .../src/crewai/llms/providers/gemini/completion.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/crewai/src/crewai/llms/providers/gemini/completion.py b/lib/crewai/src/crewai/llms/providers/gemini/completion.py index d854c150d..fd0530abe 100644 --- a/lib/crewai/src/crewai/llms/providers/gemini/completion.py +++ b/lib/crewai/src/crewai/llms/providers/gemini/completion.py @@ -634,9 +634,17 @@ class GeminiCompletion(BaseLLM): function_response_part = types.Part.from_function_response( name=tool_name, response=response_data ) - contents.append( - types.Content(role="user", parts=[function_response_part]) - ) + if ( + contents + and contents[-1].role == "user" + and contents[-1].parts + and contents[-1].parts[-1].function_response is not None + ): + contents[-1].parts.append(function_response_part) + else: + contents.append( + types.Content(role="user", parts=[function_response_part]) + ) elif role == "assistant" and message.get("tool_calls"): raw_parts: list[Any] | None = message.get("raw_tool_call_parts") if raw_parts and all(isinstance(p, types.Part) for p in raw_parts):