From e36b7017c984c5b3f07d696ae6f7f9d33cd7a44c Mon Sep 17 00:00:00 2001 From: Dresden Date: Thu, 13 Aug 2026 04:47:25 -0700 Subject: [PATCH 1/2] fix(gemini): append user after model turn Gemini rejects requests whose formatted history ends with a model role. Add a synthetic continuation turn for text and tool-call histories.\n\nRefs #6984 --- .../llms/providers/gemini/completion.py | 11 +++- lib/crewai/tests/llms/google/test_google.py | 50 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/lib/crewai/src/crewai/llms/providers/gemini/completion.py b/lib/crewai/src/crewai/llms/providers/gemini/completion.py index b099fe2373..84573c6c0f 100644 --- a/lib/crewai/src/crewai/llms/providers/gemini/completion.py +++ b/lib/crewai/src/crewai/llms/providers/gemini/completion.py @@ -7,8 +7,6 @@ import re from typing import Any, Literal, cast -from pydantic import BaseModel, Field, PrivateAttr, model_validator - from crewai.events.types.llm_events import LLMCallType from crewai.llms.base_llm import BaseLLM, llm_call_context from crewai.llms.hooks.base import BaseInterceptor @@ -18,6 +16,7 @@ ) from crewai.utilities.pydantic_schema_utils import generate_model_description from crewai.utilities.types import LLMMessage +from pydantic import BaseModel, Field, PrivateAttr, model_validator try: @@ -672,6 +671,14 @@ def _format_messages_for_gemini( gemini_content = types.Content(role=gemini_role, parts=parts) contents.append(gemini_content) + if contents and contents[-1].role == "model": + contents.append( + types.Content( + role="user", + parts=[types.Part.from_text(text="Please continue.")], + ) + ) + return contents, system_instruction def _validate_and_emit_structured_output( diff --git a/lib/crewai/tests/llms/google/test_google.py b/lib/crewai/tests/llms/google/test_google.py index 2c03dcd23b..6313166e8f 100644 --- a/lib/crewai/tests/llms/google/test_google.py +++ b/lib/crewai/tests/llms/google/test_google.py @@ -8,6 +8,7 @@ from crewai.crew import Crew from crewai.agent import Agent from crewai.task import Task +from crewai.utilities.types import LLMMessage @pytest.fixture(autouse=True) @@ -500,6 +501,55 @@ def test_gemini_message_formatting(): assert formatted_contents[1].role == "model" +@pytest.mark.parametrize( + "assistant_message", + [ + {"role": "assistant", "content": "Partial response"}, + { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": {"name": "search", "arguments": "{}"}, + } + ], + }, + ], +) +def test_gemini_message_formatting_appends_user_after_model( + assistant_message: LLMMessage, +): + llm = LLM(model="google/gemini-2.0-flash-001") + + formatted_contents, _ = llm._format_messages_for_gemini( + [ + {"role": "user", "content": "Continue the task"}, + assistant_message, + ] + ) + + assert [content.role for content in formatted_contents] == [ + "user", + "model", + "user", + ] + assert formatted_contents[-1].parts[0].text == "Please continue." + + +def test_gemini_message_formatting_preserves_user_ending(): + llm = LLM(model="google/gemini-2.0-flash-001") + + formatted_contents, _ = llm._format_messages_for_gemini( + [{"role": "user", "content": "Hello"}] + ) + + assert len(formatted_contents) == 1 + assert formatted_contents[-1].role == "user" + assert formatted_contents[-1].parts[0].text == "Hello" + + def test_gemini_streaming_parameter(): """ Test that streaming parameter is properly handled From 1c72421db460bc68dab15336f7dc094ac9445864 Mon Sep 17 00:00:00 2001 From: Dresden Date: Thu, 13 Aug 2026 21:59:20 -0700 Subject: [PATCH 2/2] test(gemini): verify model payload --- lib/crewai/tests/llms/google/test_google.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/crewai/tests/llms/google/test_google.py b/lib/crewai/tests/llms/google/test_google.py index 6313166e8f..da68bec69b 100644 --- a/lib/crewai/tests/llms/google/test_google.py +++ b/lib/crewai/tests/llms/google/test_google.py @@ -535,6 +535,12 @@ def test_gemini_message_formatting_appends_user_after_model( "model", "user", ] + model_parts = formatted_contents[1].parts + if assistant_message.get("tool_calls"): + assert model_parts[0].function_call.name == "search" + assert model_parts[0].function_call.args == {} + else: + assert model_parts[0].text == "Partial response" assert formatted_contents[-1].parts[0].text == "Please continue."