Skip to content

Commit 55e9c88

Browse files
fix: gemini maxOutputTokens and reasoning config (#9375)
* fix: gemini maxOutputTokens and reasoning config * test: tighten gemini reasoning typings
1 parent 0d72471 commit 55e9c88

File tree

3 files changed

+73
-11
lines changed

3 files changed

+73
-11
lines changed

src/api/providers/gemini.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
7272
this.lastThoughtSignature = undefined
7373
this.lastResponseId = undefined
7474

75+
// For hybrid/budget reasoning models (e.g. Gemini 2.5 Pro), respect user-configured
76+
// modelMaxTokens so the ThinkingBudget slider can control the cap. For effort-only or
77+
// standard models (like gemini-3-pro-preview), ignore any stale modelMaxTokens and
78+
// default to the model's computed maxTokens from getModelMaxOutputTokens.
79+
const isHybridReasoningModel = info.supportsReasoningBudget || info.requiredReasoningBudget
80+
const maxOutputTokens = isHybridReasoningModel
81+
? (this.options.modelMaxTokens ?? maxTokens ?? undefined)
82+
: (maxTokens ?? undefined)
83+
7584
// Only forward encrypted reasoning continuations (thoughtSignature) when we are
7685
// using effort-based reasoning (thinkingLevel). Budget-only configs should NOT
7786
// send thoughtSignature parts back to Gemini.
@@ -119,13 +128,12 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
119128
systemInstruction,
120129
httpOptions: this.options.googleGeminiBaseUrl ? { baseUrl: this.options.googleGeminiBaseUrl } : undefined,
121130
thinkingConfig,
122-
maxOutputTokens: this.options.modelMaxTokens ?? maxTokens ?? undefined,
131+
maxOutputTokens,
123132
temperature: temperatureConfig,
124133
...(tools.length > 0 ? { tools } : {}),
125134
}
126135

127136
const params: GenerateContentParameters = { model, contents, config }
128-
129137
try {
130138
const result = await this.client.models.generateContentStream(params)
131139

src/api/transform/__tests__/reasoning.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import {
77
getAnthropicReasoning,
88
getOpenAiReasoning,
99
getRooReasoning,
10+
getGeminiReasoning,
1011
GetModelReasoningOptions,
1112
OpenRouterReasoningParams,
1213
AnthropicReasoningParams,
1314
OpenAiReasoningParams,
1415
RooReasoningParams,
16+
GeminiReasoningParams,
1517
} from "../reasoning"
1618

1719
describe("reasoning.ts", () => {
@@ -587,6 +589,61 @@ describe("reasoning.ts", () => {
587589
})
588590
})
589591

592+
describe("Gemini reasoning (effort models)", () => {
593+
it("should return thinkingLevel when effort is set to low or high and budget is not used", () => {
594+
const geminiModel: ModelInfo = {
595+
...baseModel,
596+
// Effort-only reasoning model (no budget fields)
597+
supportsReasoningEffort: ["low", "high"] as ModelInfo["supportsReasoningEffort"],
598+
reasoningEffort: "low",
599+
}
600+
601+
const settings: ProviderSettings = {
602+
apiProvider: "gemini",
603+
enableReasoningEffort: true,
604+
reasoningEffort: "high",
605+
}
606+
607+
const options: GetModelReasoningOptions = {
608+
model: geminiModel,
609+
reasoningBudget: 2048,
610+
reasoningEffort: "high",
611+
settings,
612+
}
613+
614+
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
615+
616+
// Budget should not be used for effort-only models
617+
expect(result).toEqual({ thinkingLevel: "high", includeThoughts: true })
618+
})
619+
620+
it("should still return thinkingLevel when enableReasoningEffort is false but effort is explicitly set", () => {
621+
const geminiModel: ModelInfo = {
622+
...baseModel,
623+
// Effort-only reasoning model
624+
supportsReasoningEffort: ["low", "high"] as ModelInfo["supportsReasoningEffort"],
625+
reasoningEffort: "low",
626+
}
627+
628+
const settings: ProviderSettings = {
629+
apiProvider: "gemini",
630+
// Even with this flag false, an explicit effort selection should win
631+
enableReasoningEffort: false,
632+
reasoningEffort: "high",
633+
}
634+
635+
const options: GetModelReasoningOptions = {
636+
model: geminiModel,
637+
reasoningBudget: 2048,
638+
reasoningEffort: "high",
639+
settings,
640+
}
641+
642+
const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined
643+
expect(result).toEqual({ thinkingLevel: "high", includeThoughts: true })
644+
})
645+
})
646+
590647
describe("Integration scenarios", () => {
591648
it("should handle model with requiredReasoningBudget across all providers", () => {
592649
const modelWithRequired: ModelInfo = {

src/api/transform/reasoning.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,24 +116,21 @@ export const getGeminiReasoning = ({
116116
return { thinkingBudget: reasoningBudget!, includeThoughts: true }
117117
}
118118

119-
// If reasoning effort shouldn't be used (toggle off, unsupported capability, etc.),
120-
// do not send a thinkingConfig at all.
121-
if (!shouldUseReasoningEffort({ model, settings })) {
122-
return undefined
123-
}
124-
125-
// Effort-based models on Google GenAI: only support explicit low/high levels.
119+
// For effort-based Gemini models, rely directly on the selected effort value.
120+
// We intentionally ignore enableReasoningEffort here so that explicitly chosen
121+
// efforts in the UI (e.g. "High" for gemini-3-pro-preview) always translate
122+
// into a thinkingConfig, regardless of legacy boolean flags.
126123
const selectedEffort = (settings.reasoningEffort ?? model.reasoningEffort) as
127124
| ReasoningEffortExtended
128125
| "disable"
129126
| undefined
130127

131-
// Respect “off” / unset semantics.
128+
// Respect “off” / unset semantics from the effort selector itself.
132129
if (!selectedEffort || selectedEffort === "disable") {
133130
return undefined
134131
}
135132

136-
// Only map "low" and "high" to thinkingLevel; ignore other values.
133+
// Effort-based models on Google GenAI currently support only explicit low/high levels.
137134
if (selectedEffort !== "low" && selectedEffort !== "high") {
138135
return undefined
139136
}

0 commit comments

Comments
 (0)