From 22392e014e77fb10d4aa45e430d32043d60e5a9f Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Thu, 5 Jun 2025 22:55:56 -0700 Subject: [PATCH] =?UTF-8?q?Fix=20"The=20thinking=20budget=20(52428)=20is?= =?UTF-8?q?=20invalid"=20Gemini=202.5=20Pro=20Preview=20e=E2=80=A6=20(#440?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/slick-knives-invent.md | 5 ++ ...el-params.test.ts => model-params.spec.ts} | 67 +++++++++++-------- src/api/transform/model-params.ts | 27 ++++++-- 3 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 .changeset/slick-knives-invent.md rename src/api/transform/__tests__/{model-params.test.ts => model-params.spec.ts} (92%) diff --git a/.changeset/slick-knives-invent.md b/.changeset/slick-knives-invent.md new file mode 100644 index 0000000000..b353211ece --- /dev/null +++ b/.changeset/slick-knives-invent.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Fix Gemini 2.5 Pro Preview thinking budget bug diff --git a/src/api/transform/__tests__/model-params.test.ts b/src/api/transform/__tests__/model-params.spec.ts similarity index 92% rename from src/api/transform/__tests__/model-params.test.ts rename to src/api/transform/__tests__/model-params.spec.ts index a1132e2886..c29d17559e 100644 --- a/src/api/transform/__tests__/model-params.test.ts +++ b/src/api/transform/__tests__/model-params.spec.ts @@ -1,8 +1,12 @@ -// npx jest src/api/transform/__tests__/model-params.test.ts +// npx vitest run api/transform/__tests__/model-params.spec.ts import { type ModelInfo, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types" import { getModelParams } from "../model-params" +import { + DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS, + DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS, +} from "../../../shared/api" describe("getModelParams", () => { const baseModel: ModelInfo = { @@ -188,16 +192,15 @@ describe("getModelParams", () => { it("should handle requiredReasoningBudget models correctly", () => { const model: ModelInfo = { ...baseModel, - maxTokens: 2000, requiredReasoningBudget: true, } - expect(getModelParams({ ...anthropicParams, settings: {}, model })).toEqual({ + expect(getModelParams({ ...anthropicParams, settings: { modelMaxTokens: 2000 }, model })).toEqual({ format: anthropicParams.format, maxTokens: 2000, temperature: 1.0, // Thinking models require temperature 1.0. reasoningEffort: undefined, - reasoningBudget: 1600, // 80% of 2000, + reasoningBudget: 0.8 * 2000, reasoning: { type: "enabled", budget_tokens: 1600, @@ -208,13 +211,12 @@ describe("getModelParams", () => { it("should handle supportsReasoningBudget with enableReasoningEffort setting", () => { const model: ModelInfo = { ...baseModel, - maxTokens: 2000, supportsReasoningBudget: true, } const result = getModelParams({ ...anthropicParams, - settings: { enableReasoningEffort: true }, + settings: { enableReasoningEffort: true, modelMaxTokens: 2000 }, model, }) @@ -266,11 +268,16 @@ describe("getModelParams", () => { it("should honor customMaxThinkingTokens for reasoning budget models", () => { const model: ModelInfo = { ...baseModel, - maxTokens: 4000, requiredReasoningBudget: true, } - expect(getModelParams({ ...anthropicParams, settings: { modelMaxThinkingTokens: 1500 }, model })).toEqual({ + expect( + getModelParams({ + ...anthropicParams, + settings: { modelMaxTokens: 4000, modelMaxThinkingTokens: 1500 }, + model, + }), + ).toEqual({ format: anthropicParams.format, maxTokens: 4000, temperature: 1.0, @@ -302,11 +309,16 @@ describe("getModelParams", () => { it("should clamp thinking budget to at least 1024 tokens", () => { const model: ModelInfo = { ...baseModel, - maxTokens: 2000, requiredReasoningBudget: true, } - expect(getModelParams({ ...anthropicParams, settings: { modelMaxThinkingTokens: 500 }, model })).toEqual({ + expect( + getModelParams({ + ...anthropicParams, + settings: { modelMaxTokens: 2000, modelMaxThinkingTokens: 500 }, + model, + }), + ).toEqual({ format: anthropicParams.format, maxTokens: 2000, temperature: 1.0, @@ -322,16 +334,21 @@ describe("getModelParams", () => { it("should clamp thinking budget to at most 80% of max tokens", () => { const model: ModelInfo = { ...baseModel, - maxTokens: 4000, requiredReasoningBudget: true, } - expect(getModelParams({ ...anthropicParams, settings: { modelMaxThinkingTokens: 5000 }, model })).toEqual({ + expect( + getModelParams({ + ...anthropicParams, + settings: { modelMaxTokens: 4000, modelMaxThinkingTokens: 5000 }, + model, + }), + ).toEqual({ format: anthropicParams.format, maxTokens: 4000, temperature: 1.0, reasoningEffort: undefined, - reasoningBudget: 3200, // 80% of 4000 + reasoningBudget: 0.8 * 4000, reasoning: { type: "enabled", budget_tokens: 3200, @@ -339,7 +356,7 @@ describe("getModelParams", () => { }) }) - it("should use ANTHROPIC_DEFAULT_MAX_TOKENS when no maxTokens is provided for reasoning budget models", () => { + it("should use DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS when no maxTokens is provided for reasoning budget models", () => { const model: ModelInfo = { ...baseModel, requiredReasoningBudget: true, @@ -347,13 +364,13 @@ describe("getModelParams", () => { expect(getModelParams({ ...anthropicParams, settings: {}, model })).toEqual({ format: anthropicParams.format, - maxTokens: ANTHROPIC_DEFAULT_MAX_TOKENS, + maxTokens: DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS, temperature: 1.0, reasoningEffort: undefined, - reasoningBudget: Math.floor(ANTHROPIC_DEFAULT_MAX_TOKENS * 0.8), + reasoningBudget: DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS, reasoning: { type: "enabled", - budget_tokens: Math.floor(ANTHROPIC_DEFAULT_MAX_TOKENS * 0.8), + budget_tokens: DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS, }, }) }) @@ -539,9 +556,8 @@ describe("getModelParams", () => { model, }) - // Should keep model's maxTokens when using reasoning - expect(result.maxTokens).toBe(8000) - expect(result.reasoningBudget).toBe(6400) // 80% of 8000 + expect(result.maxTokens).toBe(16384) // Default value. + expect(result.reasoningBudget).toBe(8192) // Default value. }) }) @@ -549,7 +565,6 @@ describe("getModelParams", () => { it("should handle model with both reasoning capabilities but only one enabled", () => { const model: ModelInfo = { ...baseModel, - maxTokens: 4000, supportsReasoningBudget: true, supportsReasoningEffort: true, reasoningEffort: "medium", @@ -558,7 +573,7 @@ describe("getModelParams", () => { // Only reasoning budget should be used (takes precedence) const result = getModelParams({ ...anthropicParams, - settings: { enableReasoningEffort: true }, + settings: { enableReasoningEffort: true, modelMaxTokens: 4000 }, model, }) @@ -585,18 +600,17 @@ describe("getModelParams", () => { it("should handle very small maxTokens for reasoning budget models", () => { const model: ModelInfo = { ...baseModel, - maxTokens: 1000, // Less than minimum reasoning budget requiredReasoningBudget: true, } const result = getModelParams({ ...anthropicParams, - settings: {}, + settings: { modelMaxTokens: 1000 }, // Less than minimum reasoning budget. model, }) expect(result.maxTokens).toBe(1000) - expect(result.reasoningBudget).toBe(1024) // Clamped to minimum + expect(result.reasoningBudget).toBe(1024) // Clamped to minimum. }) it("should handle undefined settings", () => { @@ -694,13 +708,12 @@ describe("getModelParams", () => { it("should return correct reasoning format for openrouter with reasoning budget", () => { const model: ModelInfo = { ...baseModel, - maxTokens: 4000, requiredReasoningBudget: true, } const result = getModelParams({ ...openrouterParams, - settings: {}, + settings: { modelMaxTokens: 4000 }, model, }) diff --git a/src/api/transform/model-params.ts b/src/api/transform/model-params.ts index 9f33afbb96..8b6069666c 100644 --- a/src/api/transform/model-params.ts +++ b/src/api/transform/model-params.ts @@ -1,6 +1,11 @@ import { type ModelInfo, type ProviderSettings, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types" -import { shouldUseReasoningBudget, shouldUseReasoningEffort } from "../../shared/api" +import { + DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS, + DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS, + shouldUseReasoningBudget, + shouldUseReasoningEffort, +} from "../../shared/api" import { type AnthropicReasoningParams, @@ -77,13 +82,21 @@ export function getModelParams({ let reasoningEffort: ModelParams["reasoningEffort"] = undefined if (shouldUseReasoningBudget({ model, settings })) { - // "Hybrid" reasoning models use the `reasoningBudget` parameter. - maxTokens = customMaxTokens ?? maxTokens + // If `customMaxTokens` is not specified use the default. + maxTokens = customMaxTokens ?? DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS - // Clamp the thinking budget to be at most 80% of max tokens and at - // least 1024 tokens. - const maxBudgetTokens = Math.floor((maxTokens || ANTHROPIC_DEFAULT_MAX_TOKENS) * 0.8) - reasoningBudget = Math.max(Math.min(customMaxThinkingTokens ?? maxBudgetTokens, maxBudgetTokens), 1024) + // If `customMaxThinkingTokens` is not specified use the default. + reasoningBudget = customMaxThinkingTokens ?? DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS + + // Reasoning cannot exceed 80% of the `maxTokens` value. + if (reasoningBudget > Math.floor(maxTokens * 0.8)) { + reasoningBudget = Math.floor(maxTokens * 0.8) + } + + // Reasoning cannot be less than 1024 tokens. + if (reasoningBudget < 1024) { + reasoningBudget = 1024 + } // Let's assume that "Hybrid" reasoning models require a temperature of // 1.0 since Anthropic does.