fix(provider): preserve current user turn in Gemini history - #9738
Open
SunmiJJW wants to merge 2 commits into
Open
fix(provider): preserve current user turn in Gemini history#9738SunmiJJW wants to merge 2 commits into
SunmiJJW wants to merge 2 commits into
Conversation
Contributor
There was a problem hiding this comment.
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 leadingModelContent; 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Contributor
There was a problem hiding this comment.
💡 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".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
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 passedruff check astrbot/core/provider/sources/gemini_source.py tests/test_gemini_source.py: passedupstream/masterbaseline 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 filesSummary by Sourcery
Preserve valid conversation ordering when converting persisted histories for Gemini requests.
Bug Fixes:
Tests: