Incorrect comment: contents_processor.go claims the Gemini API requires role alternation
#1,218 opened on Jul 27, 2026
Repository metrics
- Stars
- (8,656 stars)
- PR merge metrics
- (Avg merge 18d 7h) (46 merged PRs in 30d)
Description
Summary
internal/llminternal/contents_processor.go:64-65 states a constraint that does not appear to hold:
// Gemini API requires role alternation (model turns cannot be consecutive).
// If the conversation history concludes on a model turn, inject a synthetic user continuation turn.
The API does accept consecutive model contents. Sending a history with two adjacent model contents mid-conversation (both plain text, and with function calls) returned HTTP 200 on gemini-flash-latest and gemini-3.5-flash.
The code at :66-70 is fine and should stay — it just isn't doing what the comment says. Its real justification is the one given for the identical repair in model/gemini/gemini.go:163-164 ("appends a user content, so that model can continue to output") and in adk-python's base_llm.py ("Insert a user content to preserve user intent and to avoid empty model response"). That is a generation-quality nudge, not an API requirement. Notably, adk-python contains no role-alternation enforcement anywhere.
Why it's worth fixing
The comment reads as a specification, and it is load-bearing in review: it is the stated basis for treating any mid-history model/model adjacency as a defect. It's also inaccurate in a second way — the pipeline already produces consecutive model contents on ordinary paths (two adjacent model events, e.g. a thought-only turn followed by an answer), and the repair at :66-70 only inspects the last content, so it never protected against the mid-history case to begin with.
Observed behaviour
Consecutive model contents were accepted in every shape tested. The one case that was rejected turned out to be unrelated to role ordering:
| shape | result |
|---|---|
consecutive model text contents mid-history |
200 |
consecutive model contents containing function calls, with thoughtSignature present |
200 |
alternating roles, function calls without thoughtSignature |
200 |
consecutive model contents with function calls and no thoughtSignature anywhere |
400 — Function call is missing a thought_signature in functionCall parts |
Adding a signature to either call in that last shape returned it to 200. So the rejection tracks thoughtSignature presence, not role alternation. This shouldn't affect normal operation, since function calls emitted by Gemini carry signatures and they are round-tripped, but it may be worth being aware of for replayed, resumed, or synthetically constructed histories.
Suggested change
Reword the comment to describe what the code actually does and why, for example:
// If the conversation history concludes on a model turn, inject a synthetic user
// continuation turn so the model keeps producing output rather than returning an
// empty response. (Mirrors maybeAppendUserContent in model/gemini and
// adk-python's _maybe_append_user_content.)
No behaviour change intended — comment only.
Environment: verified against generativelanguage.googleapis.com/v1beta, models gemini-flash-latest and gemini-3.5-flash. Not re-verified against Vertex.