fix: append trailing user turn in native Gemini provider - #6973
Conversation
GeminiCompletion._format_messages_for_gemini maps assistant messages to Gemini's 'model' role but never guards against the resulting contents list ending on a model turn. CrewAI's own agent loop (max iterations, guardrail retries) can produce exactly that history, and Gemini's generateContent API rejects it with 400 'Requests ending with a model turn are not supported'. Mirrors the existing Mistral/Ollama guard in LLM._format_messages_for_provider, which never applies to Gemini since gemini/google model strings resolve to this native provider instead of the LiteLLM fallback path. Fixes crewAIInc#6972
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughGemini formatters now append a synthetic user continuation message after assistant or model-ending history. The native formatter raises a ChangesGemini message formatting
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/crewai/src/crewai/llms/providers/gemini/completion.py`:
- Around line 675-684: Update the model-ending handling around the contents
construction to inspect the final model Content’s parts before appending the
synthetic “Please continue.” turn. If it contains an unresolved function_call,
route it through the existing function-response handling or raise a targeted
error instead; only append the continuation prompt for model turns without
pending calls. Add a regression test covering an assistant tool_calls message
with no subsequent tool response.
In `@lib/crewai/tests/llms/google/test_google.py`:
- Around line 517-519: Strengthen the assertions around
_format_messages_for_gemini by verifying the complete formatted message
sequence, including each expected role and exact text such as "Please continue."
Confirm that the original test_messages retain their original roles and "Hello"
content after formatting, covering both relevant assertion blocks without
relying only on implementation details.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f7698c15-9527-4436-8012-722c9b983ebe
📒 Files selected for processing (2)
lib/crewai/src/crewai/llms/providers/gemini/completion.pylib/crewai/tests/llms/google/test_google.py
LLM._format_messages_for_provider already guards Mistral/Ollama against a trailing assistant turn, but Gemini models routed through the LiteLLM fallback (no google-genai installed, or a model name not recognized as native) had no equivalent guard. litellm's own Vertex/Gemini transformation doesn't handle this either, so the request reaches Gemini's generateContent API unguarded and 400s. Complements the native-provider fix in GeminiCompletion, covering both dispatch paths.
Address CodeRabbit review on crewAIInc#6973: appending a plain 'Please continue.' user turn after a trailing model turn that contains an unresolved function_call violates Gemini's function-calling protocol -- it requires a matching functionResponse, not free text. Raise a targeted error instead so the caller notices rather than silently sending a malformed follow-up. Also strengthens the native-provider formatting tests to assert exact role sequence and text content (not just the last role), per review, and adds a regression test for the unresolved-function-call case.
Summary
Fixes #6984.
Gemini's
generateContentAPI rejects a request whose history ends on amodelturn (400 'Requests ending with a model turn are not supported.'). CrewAI's own agent loop (handle_max_iterations_exceeded, taskguardrail/guardrail_max_retriesretries) can produce exactly that history. This covers both places a Gemini call can be dispatched from:GeminiCompletion._format_messages_for_geminimapsassistantmessages to Gemini'smodelrole (in both the plain-text and tool-call branches) but never checked whether the resultingcontentslist ends onmodel. Now appends a trailingusertypes.Contentwhen it does.google-genaiisn't installed, or the model name isn't recognized as native (custom deployments, new previews, fine-tunes).LLM._format_messages_for_provideralready guards this exact case for Mistral and Ollama, but had no Gemini branch — and litellm's own Vertex/Gemini transformation doesn't guard it either (checkedlitellm/llms/vertex_ai/gemini/transformation.py). Added a Gemini branch mirroring the existing Mistral one.Test plan
uv run --package crewai pytest lib/crewai/tests/llms/google/test_google.py -v— 46 passed, 1 skipped, 1 pre-existing unrelated failure (test_gemini_raises_error_when_model_not_supported, fails identically onmainwithout this change; requires thelitellmextra which isn't installed for that test module).uv run --package crewai pytest lib/crewai/tests/test_llm.py— 66 passed, 3 skipped, no failures.test_gemini_message_formatting_appends_user_turn_after_trailing_model_turnandtest_gemini_message_formatting_leaves_user_terminated_history_unchangedintests/llms/google/test_google.py(native provider).test_gemini_litellm_appends_user_message_when_last_is_assistantandtest_gemini_litellm_does_not_modify_when_last_is_userintests/test_llm.py(LiteLLM fallback), mirroring the existing Ollama tests.