mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(providers): thread the session effort knob to Gemini
_GOOGLE_DEFAULT declared no reasoning_effort_values, so resolve_reasoning_effort returned None and the session effort knob was silently dropped for every Gemini model — the same bug class this branch fixed on the local lanes. Gemini's OpenAI-compat surface documents a flat reasoning_effort (2.5: thinking_budget mapping; 3.x: thinking_level), so declaring values lights up the inherited chat-completions path. Values are the safe cross-model set (minimal/low/medium/high): "none" is excluded because 2.5 Pro and the 3.x family reject disabling thinking — and the resolver never forwards the knob's none anyway (the param is omitted, server default applies). Off-list xhigh/max snap to the declared default high. Encoded from the official compatibility docs per the static-caps pattern; not live-verified.
This commit is contained in:
@@ -1802,6 +1802,39 @@ class TestProviderFactory:
|
||||
# ===========================================================================
|
||||
|
||||
|
||||
class TestGoogleEffortKnob:
|
||||
"""The session effort knob reaches Gemini as a flat reasoning_effort."""
|
||||
|
||||
def _create_kwargs(self, reasoning_effort: str) -> dict[str, Any]:
|
||||
from turnstone.core.providers._google import GoogleProvider
|
||||
|
||||
prov = GoogleProvider()
|
||||
client = MagicMock()
|
||||
client.chat.completions.create.return_value = iter([])
|
||||
list(
|
||||
prov.create_streaming(
|
||||
client=client,
|
||||
model="gemini-3-flash",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
reasoning_effort=reasoning_effort,
|
||||
)
|
||||
)
|
||||
return client.chat.completions.create.call_args[1]
|
||||
|
||||
def test_knob_values_forward_verbatim(self) -> None:
|
||||
for knob in ("minimal", "low", "medium", "high"):
|
||||
assert self._create_kwargs(knob)["reasoning_effort"] == knob
|
||||
|
||||
def test_off_list_knob_snaps_to_high(self) -> None:
|
||||
"""xhigh/max are not in Gemini's vocabulary — snap down to high."""
|
||||
for knob in ("xhigh", "max"):
|
||||
assert self._create_kwargs(knob)["reasoning_effort"] == "high"
|
||||
|
||||
def test_none_omits_the_param(self) -> None:
|
||||
"""Knob none never sends "none" — 2.5 Pro / 3.x reject disabling."""
|
||||
assert "reasoning_effort" not in self._create_kwargs("none")
|
||||
|
||||
|
||||
class TestGoogleProviderFidelity:
|
||||
"""Tests for thought_signature round-trip via provider_blocks."""
|
||||
|
||||
|
||||
@@ -43,6 +43,19 @@ _GOOGLE_DEFAULT = ModelCapabilities(
|
||||
# Gemini's OpenAI-compat endpoint accepts max_tokens (not
|
||||
# max_completion_tokens which is OpenAI Responses-specific).
|
||||
token_param="max_tokens",
|
||||
# Gemini's OpenAI-compat endpoint accepts a flat ``reasoning_effort``
|
||||
# (2.5 family: thinking_budget 1024/1024/8192/24576 for minimal/low/
|
||||
# medium/high; 3.x family: thinking_level of the same name). The
|
||||
# declared values are the safe set across ALL current Gemini models:
|
||||
# "none" is excluded because 2.5 Pro and the 3.x family reject
|
||||
# disabling thinking — and ``resolve_reasoning_effort`` never
|
||||
# forwards the knob's "none" anyway (the param is omitted and the
|
||||
# server default applies). Off-list knob values (xhigh, max) snap
|
||||
# to the default "high". Encoded from
|
||||
# ai.google.dev/gemini-api/docs/openai (2026-07); not live-verified —
|
||||
# the static-caps pattern for commercial providers.
|
||||
reasoning_effort_values=("minimal", "low", "medium", "high"),
|
||||
default_reasoning_effort="high",
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user