Skip to content

Commit 425f5d0

Browse files
committed
fix(google): reset Gemini fallback on provider switch, reuse sync client
- Switching from google/google-vertex to another provider resets the auto-set Gemini fallback model to glm-4p5 unless --fallback-model is given. - call_google reuses one sync genai client per credential set instead of building a client per call; agent-loop models still get a fresh client. - The non-top-tier cluster model hint now names current model families.
1 parent 6121061 commit 425f5d0

5 files changed

Lines changed: 58 additions & 3 deletions

File tree

‎codewiki/cli/commands/config.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ def config_validate(quick: bool, verbose: bool):
733733
# Warn about non-top-tier cluster model
734734
if not is_top_tier_model(config.cluster_model):
735735
click.secho(
736-
"⚠️ Cluster model is not top-tier. Consider using claude-sonnet-4 or gpt-4.",
736+
"⚠️ Cluster model is not top-tier. Consider a Claude Sonnet/Opus, GPT-5 or Gemini 3 model.",
737737
fg="yellow",
738738
)
739739

‎codewiki/cli/config_manager.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ def save(
194194
agent_instructions=AgentInstructions(),
195195
)
196196

197+
from codewiki.src.be.backend import is_caw_provider, is_google_provider, needs_base_url
198+
199+
was_google = is_google_provider(self._config.provider)
200+
197201
# Update fields if provided
198202
if base_url is not None:
199203
self._config.base_url = base_url
@@ -238,7 +242,6 @@ def save(
238242
# Caw providers only need main_model; API providers need base_url +
239243
# cluster_model on top of that. The validate() method itself routes
240244
# by provider, so we only gate on whether enough is set to validate.
241-
from codewiki.src.be.backend import is_caw_provider, is_google_provider, needs_base_url
242245

243246
# The glm-4p5 default fallback does not exist on Google endpoints; a
244247
# fallback that 404s only after the main model failed hides the real error.
@@ -248,6 +251,11 @@ def save(
248251
and self._config.fallback_model in ("", "glm-4p5")
249252
):
250253
self._config.fallback_model = self._config.main_model
254+
# Leaving Google: the Gemini fallback set above would 404 on the new endpoint.
255+
elif (
256+
was_google and not is_google_provider(self._config.provider) and fallback_model is None
257+
):
258+
self._config.fallback_model = "glm-4p5"
251259

252260
if is_caw_provider(self._config.provider):
253261
if self._config.main_model:

‎codewiki/src/be/google_provider.py‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,24 @@ def _client_for(config) -> genai.Client:
9595
)
9696

9797

98+
# One-shot calls reuse a sync client per credential set. Agent-loop models get a
99+
# fresh client (_client_for): their async transport is bound to the event loop.
100+
_SYNC_CLIENTS: dict[tuple[str, str | None, str, str], genai.Client] = {}
101+
102+
103+
def _sync_client_for(config) -> genai.Client:
104+
key = (
105+
config.provider,
106+
config.llm_api_key,
107+
getattr(config, "google_project", ""),
108+
getattr(config, "google_location", ""),
109+
)
110+
client = _SYNC_CLIENTS.get(key)
111+
if client is None:
112+
client = _SYNC_CLIENTS[key] = _client_for(config)
113+
return client
114+
115+
98116
def create_google_model(config, model_name: str) -> GoogleModel:
99117
"""GoogleModel for the agent loop. No temperature and no thinking config:
100118
the model defaults apply (Google advises temperature 1.0 for Gemini 3)."""
@@ -159,7 +177,7 @@ def call_google(
159177
output is logged and returned as None, never raised.
160178
"""
161179
name = normalise_model_name(model)
162-
client = _client_for(config)
180+
client = _sync_client_for(config)
163181
response = client.models.generate_content(
164182
model=name,
165183
contents=prompt,

‎tests/test_google_config.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,19 @@ def test_show_vertex_lists_project_and_location(tmp_config):
233233

234234
def test_gemini_3_is_top_tier():
235235
assert is_top_tier_model(MODEL) is True
236+
237+
238+
def test_leaving_google_resets_gemini_fallback(tmp_config):
239+
manager = cm.ConfigManager()
240+
manager.save(provider="google-vertex", main_model=MODEL, cluster_model=MODEL)
241+
assert manager.get_config().fallback_model == MODEL
242+
243+
manager.save(provider="openai-compatible", base_url="https://api.example.com/v1")
244+
assert manager.get_config().fallback_model == "glm-4p5"
245+
246+
247+
def test_leaving_google_keeps_explicit_fallback(tmp_config):
248+
manager = cm.ConfigManager()
249+
manager.save(provider="google-vertex", main_model=MODEL, cluster_model=MODEL)
250+
manager.save(provider="openai-compatible", fallback_model="gpt-5-mini")
251+
assert manager.get_config().fallback_model == "gpt-5-mini"

‎tests/test_google_provider.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def generate_content(self, **kwargs):
139139
def fake_models(monkeypatch):
140140
def install(response):
141141
models = FakeModels(response)
142+
monkeypatch.setattr(google_provider, "_SYNC_CLIENTS", {})
142143
monkeypatch.setattr(
143144
google_provider, "_client_for", lambda config: SimpleNamespace(models=models)
144145
)
@@ -214,3 +215,15 @@ def test_cost_unknown_model_is_omitted(fake_models, monkeypatch):
214215
)
215216
messages = [priced, ModelRequest(parts=[UserPromptPart("x")]), priced]
216217
assert google_provider.messages_cost(messages) == 1.0
218+
219+
220+
def test_call_google_reuses_sync_client(monkeypatch):
221+
built = []
222+
monkeypatch.setattr(google_provider, "_SYNC_CLIENTS", {})
223+
monkeypatch.setattr(google_provider, "_client_for", lambda config: built.append(1) or object())
224+
config = SimpleNamespace(
225+
provider="google-vertex", llm_api_key=None, google_project="p", google_location="eu"
226+
)
227+
first = google_provider._sync_client_for(config)
228+
assert google_provider._sync_client_for(config) is first
229+
assert len(built) == 1

0 commit comments

Comments
 (0)