Skip to content

Commit 54012fd

Browse files
committed
fix(gemini): send the lowest thinking level when thinking is 'none' on Gemini 3
1 parent f839cc2 commit 54012fd

3 files changed

Lines changed: 30 additions & 8 deletions

File tree

‎apps/sim/providers/gemini/core.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ describe('Vertex Gemini request compatibility', () => {
139139
})
140140
})
141141

142+
/** Omitting thinkingConfig leaves Gemini 3 thinking at its default, so 'none' must send a level. */
143+
it.each([
144+
['vertex/gemini-3.6-flash', 'MINIMAL'],
145+
['vertex/gemini-3.8-flash', 'LOW'],
146+
])("sends the lowest thinking level for 'none' on %s", async (model, thinkingLevel) => {
147+
const generateContent = vi.fn().mockResolvedValue(textTurn())
148+
await run(model, generateContent, { thinkingLevel: 'none' })
149+
150+
expect(generateContent.mock.calls[0][0].config.thinkingConfig).toEqual({
151+
thinkingLevel,
152+
includeThoughts: false,
153+
})
154+
})
155+
142156
it.each(['vertex/gemini-2.5-flash', 'vertex/Custom-Gemini-Model'])(
143157
'preserves temperature for supported or uncataloged %s',
144158
async (model) => {

‎apps/sim/providers/gemini/core.ts‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
mapToThinkingLevel,
3737
supportsDisablingGemini25Thinking,
3838
} from '@/providers/google/utils'
39-
import { getModelCapabilities, isKnownModelId } from '@/providers/models'
39+
import { getModelCapabilities, getThinkingLevelsForModel, isKnownModelId } from '@/providers/models'
4040
import { withProviderRetry } from '@/providers/retry'
4141
import { executeProviderTool } from '@/providers/runtime-context'
4242
import { createSettledAgentEventStream } from '@/providers/stream-events'
@@ -1010,17 +1010,24 @@ export async function executeGeminiRequest(
10101010
// Gemini 3 and later take thinkingLevel directly; Gemini 2.5-series rejects it and needs thinkingBudget.
10111011
// includeThoughts is required for thought parts to appear; it is requested
10121012
// only on agent-events runs so legacy runs keep the pre-agent-events payload.
1013-
if (request.thinkingLevel && request.thinkingLevel !== 'none') {
1013+
const isGemini3 = isGemini3OrLaterModel(model)
1014+
// Gemini 3 has no off switch and omitting thinkingConfig means the model's default level, so
1015+
// 'none' sends the lowest declared level (levels are listed lowest-first; MINIMAL is closest to off).
1016+
const thinkingLevel =
1017+
isGemini3 && request.thinkingLevel === 'none'
1018+
? getThinkingLevelsForModel(request.model)?.[0]
1019+
: request.thinkingLevel
1020+
if (thinkingLevel && thinkingLevel !== 'none') {
10141021
const thinkingConfig: ThinkingConfig = { includeThoughts: request.agentEvents === true }
1015-
if (isGemini3OrLaterModel(model)) {
1016-
thinkingConfig.thinkingLevel = mapToThinkingLevel(request.thinkingLevel)
1022+
if (isGemini3) {
1023+
thinkingConfig.thinkingLevel = mapToThinkingLevel(thinkingLevel)
10171024
} else {
1018-
thinkingConfig.thinkingBudget = mapToThinkingBudget(model, request.thinkingLevel)
1025+
thinkingConfig.thinkingBudget = mapToThinkingBudget(model, thinkingLevel)
10191026
}
10201027
geminiConfig.thinkingConfig = thinkingConfig
10211028
} else if (
1029+
!isGemini3 &&
10221030
request.thinkingLevel === 'none' &&
1023-
!isGemini3OrLaterModel(model) &&
10241031
supportsDisablingGemini25Thinking(model)
10251032
) {
10261033
// Omitting thinkingConfig falls back to the API's dynamic default (ON for gemini-2.5-flash),
@@ -1085,7 +1092,7 @@ export async function executeGeminiRequest(
10851092
*/
10861093
const hasActiveTools = Boolean(geminiConfig.tools?.length)
10871094
const responseFormatNeedsFinalPass =
1088-
Boolean(request.responseFormat) && hasActiveTools && !isGemini3OrLaterModel(model)
1095+
Boolean(request.responseFormat) && hasActiveTools && !isGemini3
10891096
const liveToolLoopSupported = !responseFormatNeedsFinalPass
10901097
const shouldStream = request.stream && !hasActiveTools
10911098

‎apps/sim/providers/models.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export interface ModelCapabilities {
8989
streamed?: ThinkingStreamVisibility
9090
/**
9191
* Explicit API mode for the pickers' `none` level on models that think by
92-
* default. Omitted, `none` sends no thinking config.
92+
* default. Omitted, `none` sends no thinking config, except on Gemini 3+, which
93+
* has no off switch and sends its lowest declared level instead.
9394
*/
9495
noneMode?: 'between_tools' | 'disabled'
9596
/** Whether forced tool choice can be combined with configured thinking. */

0 commit comments

Comments
 (0)