Skip to content

fix(provider): preserve current user turn in Gemini history - #9738

Open
SunmiJJW wants to merge 2 commits into
AstrBotDevs:masterfrom
SunmiJJW:codex/fix-gemini-leading-model-pop
Open

fix(provider): preserve current user turn in Gemini history#9738
SunmiJJW wants to merge 2 commits into
AstrBotDevs:masterfrom
SunmiJJW:codex/fix-gemini-leading-model-pop

Conversation

@SunmiJJW

@SunmiJJW SunmiJJW commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • remove the leading converted Gemini model block instead of the final current user block
  • add regression coverage for assistant-first and normal user-first histories

Problem

When persisted history begins with an assistant turn, the Gemini adapter currently removes the final converted content. That can leave a request ending in a model turn, which Gemini rejects and which may also make a fallback provider receive stale history.

Tests

  • pytest -q tests/test_gemini_source.py: 5 passed
  • ruff check astrbot/core/provider/sources/gemini_source.py tests/test_gemini_source.py: passed
  • full Windows suite: 2152 passed, 1 skipped, 34 failed
  • unchanged upstream/master baseline on the same 34 failed nodes: the same 34 failed (plus 19 passing parametrized/class cases); failures are existing Windows path/newline/symlink/shell fixture issues and do not touch the changed files

Summary by Sourcery

Preserve valid conversation ordering when converting persisted histories for Gemini requests.

Bug Fixes:

  • Preserve the current user turn when preparing Gemini conversation history that begins with an assistant turn, preventing invalid requests and stale fallback history.

Tests:

  • Add regression coverage for assistant-first, normal user-first, and user-model Gemini histories.

@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Aug 19, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • Using ProviderGoogleGenAI.__new__(ProviderGoogleGenAI) in tests bypasses __init__, so if the constructor ever gains required setup the tests may silently misbehave; consider a small factory that constructs a minimally valid instance via the public interface instead.
  • The new gemini_contents.pop(0) logic removes only the first leading ModelContent; if histories can contain multiple consecutive assistant turns at the start, you may want to strip all leading model content to avoid leaving Gemini with an assistant-first history.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using `ProviderGoogleGenAI.__new__(ProviderGoogleGenAI)` in tests bypasses `__init__`, so if the constructor ever gains required setup the tests may silently misbehave; consider a small factory that constructs a minimally valid instance via the public interface instead.
- The new `gemini_contents.pop(0)` logic removes only the first leading `ModelContent`; if histories can contain multiple consecutive assistant turns at the start, you may want to strip all leading model content to avoid leaving Gemini with an assistant-first history.

## Individual Comments

### Comment 1
<location path="tests/test_gemini_source.py" line_range="35-44" />
<code_context>
+    assert contents[0].parts[-1].text == "current user turn"
+
+
+def test_gemini_prepare_conversation_keeps_normal_user_first_history():
+    provider = _make_gemini_provider()
+
+    contents = provider._prepare_conversation(
+        {
+            "messages": [
+                {"role": "user", "content": "first user turn"},
+                {"role": "assistant", "content": "assistant turn"},
+                {"role": "user", "content": "current user turn"},
+            ]
+        }
+    )
+
+    assert [type(content) for content in contents] == [
+        types.UserContent,
+        types.ModelContent,
+        types.UserContent,
+    ]
+    assert contents[-1].parts is not None
+    assert contents[-1].parts[-1].text == "current user turn"
+
+
</code_context>
<issue_to_address>
**question (testing):** Consider adding a test for histories that end with an assistant turn to document and lock in the expected behavior.

The PR description notes Gemini rejects conversations that end with a model turn. We currently only test histories that end with a user turn, even when they start with an assistant. Please add a test where the persisted history truly ends with an assistant turn (e.g., user → assistant) and assert the intended behavior of `_prepare_conversation` in that case (raise, normalize, or pass through). This will document the contract and prevent regressions around model-ending histories.
</issue_to_address>

### Comment 2
<location path="tests/test_gemini_source.py" line_range="13-14" />
<code_context>
 from astrbot.core.provider.sources.gemini_source import ProviderGoogleGenAI


+def _make_gemini_provider() -> ProviderGoogleGenAI:
+    return ProviderGoogleGenAI.__new__(ProviderGoogleGenAI)
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Clarify or adjust the provider construction helper to ensure test stability if `__init__` gains relevant logic.

Using `__new__` here bypasses `__init__`, which is safe only as long as `_prepare_conversation` doesn’t depend on initialized state. To keep these tests robust to future changes, either add a brief comment documenting that this is intentional and that `_prepare_conversation` is pure, or construct a fully initialized `ProviderGoogleGenAI` (mocking any external dependencies if necessary) so that changes to initialization logic are reflected in the tests.

Suggested implementation:

```python
def _make_gemini_provider() -> ProviderGoogleGenAI:
    """
    Helper to construct a fully initialized ProviderGoogleGenAI for tests.

    Using the real initializer keeps these tests sensitive to changes in
    ProviderGoogleGenAI.__init__, ensuring that any required state for
    _prepare_conversation is present. If ProviderGoogleGenAI gains required
    dependencies, update this helper to supply suitable mocks/fakes so that
    initialization logic is exercised but external side-effects are avoided.
    """
    # NOTE: If ProviderGoogleGenAI.__init__ starts requiring arguments,
    #       adapt this helper to pass in test doubles of those dependencies.
    return ProviderGoogleGenAI()

```

If `ProviderGoogleGenAI.__init__` currently requires parameters (e.g. API client, configuration, or logger), you will need to:

1. Update `_make_gemini_provider` to pass suitable test doubles, for example:
   - A mocked HTTP client or API wrapper.
   - A minimal config object with test values.
   - A stub logger or a no-op implementation.
2. Ensure any such mocks are created in this test module (or imported from shared test utilities) so that `_prepare_conversation` runs against a realistic, initialized instance without triggering real external calls.
3. If `_prepare_conversation` depends on specific instance attributes, verify that the provided constructor arguments set those attributes appropriately for the tests.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tests/test_gemini_source.py
Comment thread tests/test_gemini_source.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 90fd2e48d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/test_gemini_source.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant